Files
tubestation/layout/style/nsCSSParser.cpp
Sebastian Hengst df2429a1ad Backed out 15 changesets (bug 951793) because it landed before the necessary servo changes. r=backout requested by emilio on a CLOSED TREE
Backed out changeset ca8c86e229df (bug 951793)
Backed out changeset 6eef6403fa71 (bug 951793)
Backed out changeset a5e529f52fb1 (bug 951793)
Backed out changeset 054e837609d0 (bug 951793)
Backed out changeset 713a3c9617ce (bug 951793)
Backed out changeset 884913aa1668 (bug 951793)
Backed out changeset c3340b84e534 (bug 951793)
Backed out changeset 50fe3c6ac486 (bug 951793)
Backed out changeset be4e22e5c257 (bug 951793)
Backed out changeset 7055bd5dfc4e (bug 951793)
Backed out changeset fa6da1e723cf (bug 951793)
Backed out changeset 386f77004d89 (bug 951793)
Backed out changeset fa82cdc01408 (bug 951793)
Backed out changeset 867d8ea5355c (bug 951793)
Backed out changeset e61ac8e48971 (bug 951793)
2017-11-25 01:01:41 +02:00

18204 lines
594 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* parsing of CSS stylesheets, based on a token stream from the CSS scanner */
#include "nsCSSParser.h"
#include "mozilla/Attributes.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Maybe.h"
#include "mozilla/Move.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/TypedEnumBits.h"
#include "mozilla/Unused.h"
#include <algorithm> // for std::stable_sort
#include <limits> // for std::numeric_limits
#include "nsAlgorithm.h"
#include "nsCSSProps.h"
#include "nsCSSKeywords.h"
#include "nsCSSScanner.h"
#include "mozilla/css/ErrorReporter.h"
#include "mozilla/css/Loader.h"
#include "mozilla/css/StyleRule.h"
#include "mozilla/css/ImportRule.h"
#include "mozilla/css/URLMatchingFunction.h"
#include "nsCSSRules.h"
#include "nsCSSCounterStyleRule.h"
#include "nsCSSFontFaceRule.h"
#include "mozilla/css/NameSpaceRule.h"
#include "nsTArray.h"
#include "mozilla/StyleSheetInlines.h"
#include "mozilla/css/Declaration.h"
#include "nsStyleConsts.h"
#include "nsNetUtil.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsAtom.h"
#include "nsColor.h"
#include "nsCSSPseudoClasses.h"
#include "nsCSSPseudoElements.h"
#include "nsCSSAnonBoxes.h"
#include "nsNameSpaceManager.h"
#include "nsXMLNameSpaceMap.h"
#include "nsError.h"
#include "nsMediaList.h"
#include "nsStyleUtil.h"
#include "nsIPrincipal.h"
#include "mozilla/Sprintf.h"
#include "nsContentUtils.h"
#include "nsAutoPtr.h"
#include "CSSCalc.h"
#include "nsMediaFeatures.h"
#include "nsLayoutUtils.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/Preferences.h"
#include "mozilla/StylePrefs.h"
#include "nsRuleData.h"
#include "mozilla/CSSVariableValues.h"
#include "mozilla/dom/AnimationEffectReadOnlyBinding.h"
#include "mozilla/dom/URL.h"
#include "gfxFontFamilyList.h"
using namespace mozilla;
using namespace mozilla::css;
typedef nsCSSProps::KTableEntry KTableEntry;
const uint32_t
nsCSSProps::kParserVariantTable[eCSSProperty_COUNT_no_shorthands] = {
#define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \
stylestruct_, stylestructoffset_, animtype_) \
parsevariant_,
#define CSS_PROP_LIST_INCLUDE_LOGICAL
#include "nsCSSPropList.h"
#undef CSS_PROP_LIST_INCLUDE_LOGICAL
#undef CSS_PROP
};
// Maximum number of repetitions for the repeat() function
// in the grid-template-rows and grid-template-columns properties,
// to limit high memory usage from small stylesheets.
// Must be a positive integer. Should be large-ish.
#define GRID_TEMPLATE_MAX_REPETITIONS 10000
// End-of-array marker for mask arguments to ParseBitmaskValues
#define MASK_END_VALUE (-1)
enum class CSSParseResult : int32_t {
// Parsed something successfully:
Ok,
// Did not find what we were looking for, but did not consume any token:
NotFound,
// Unexpected token or token value, too late for UngetToken() to be enough:
Error
};
enum class GridTrackSizeFlags {
eDefaultTrackSize = 0x0,
eFixedTrackSize = 0x1, // parse a <fixed-size> instead of <track-size>
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(GridTrackSizeFlags)
enum class GridTrackListFlags {
eDefaultTrackList = 0x0, // parse a <track-list>
eExplicitTrackList = 0x1, // parse an <explicit-track-list> instead
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(GridTrackListFlags)
namespace {
// Rule processing function
typedef void (* RuleAppendFunc) (css::Rule* aRule, void* aData);
static void AssignRuleToPointer(css::Rule* aRule, void* aPointer);
static void AppendRuleToSheet(css::Rule* aRule, void* aParser);
struct CSSParserInputState {
nsCSSScannerPosition mPosition;
nsCSSToken mToken;
bool mHavePushBack;
};
static_assert(css::eAuthorSheetFeatures == 0 &&
css::eUserSheetFeatures == 1 &&
css::eAgentSheetFeatures == 2,
"sheet parsing mode constants won't fit "
"in CSSParserImpl::mParsingMode");
// Your basic top-down recursive descent style parser
// The exposed methods and members of this class are precisely those
// needed by nsCSSParser, far below.
class CSSParserImpl {
public:
CSSParserImpl();
~CSSParserImpl();
nsresult SetStyleSheet(CSSStyleSheet* aSheet);
nsIDocument* GetDocument();
nsresult SetQuirkMode(bool aQuirkMode);
nsresult SetChildLoader(mozilla::css::Loader* aChildLoader);
// Clears everything set by the above Set*() functions.
void Reset();
nsresult ParseSheet(const nsAString& aInput,
nsIURI* aSheetURI,
nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
uint32_t aLineNumber,
css::LoaderReusableStyleSheets* aReusableSheets);
already_AddRefed<css::Declaration>
ParseStyleAttribute(const nsAString& aAttributeValue,
nsIURI* aDocURL,
nsIURI* aBaseURL,
nsIPrincipal* aNodePrincipal);
nsresult ParseDeclarations(const nsAString& aBuffer,
nsIURI* aSheetURL,
nsIURI* aBaseURL,
nsIPrincipal* aSheetPrincipal,
css::Declaration* aDeclaration,
bool* aChanged);
nsresult ParseRule(const nsAString& aRule,
nsIURI* aSheetURL,
nsIURI* aBaseURL,
nsIPrincipal* aSheetPrincipal,
css::Rule** aResult);
void ParseProperty(const nsCSSPropertyID aPropID,
const nsAString& aPropValue,
nsIURI* aSheetURL,
nsIURI* aBaseURL,
nsIPrincipal* aSheetPrincipal,
css::Declaration* aDeclaration,
bool* aChanged,
bool aIsImportant,
bool aIsSVGMode);
void ParseLonghandProperty(const nsCSSPropertyID aPropID,
const nsAString& aPropValue,
nsIURI* aSheetURL,
nsIURI* aBaseURL,
nsIPrincipal* aSheetPrincipal,
nsCSSValue& aValue);
bool ParseTransformProperty(const nsAString& aPropValue,
bool aDisallowRelativeValues,
nsCSSValue& aResult);
void ParseMediaList(const nsAString& aBuffer,
nsIURI* aURL, // for error reporting
uint32_t aLineNumber, // for error reporting
nsMediaList* aMediaList,
mozilla::dom::CallerType aCallerType);
bool ParseSourceSizeList(const nsAString& aBuffer,
nsIURI* aURI, // for error reporting
uint32_t aLineNumber, // for error reporting
InfallibleTArray< nsAutoPtr<nsMediaQuery> >& aQueries,
InfallibleTArray<nsCSSValue>& aValues);
void ParseVariable(const nsAString& aVariableName,
const nsAString& aPropValue,
nsIURI* aSheetURL,
nsIURI* aBaseURL,
nsIPrincipal* aSheetPrincipal,
css::Declaration* aDeclaration,
bool* aChanged,
bool aIsImportant);
bool ParseFontFamilyListString(const nsAString& aBuffer,
nsIURI* aURL, // for error reporting
uint32_t aLineNumber, // for error reporting
nsCSSValue& aValue);
bool ParseColorString(const nsAString& aBuffer,
nsIURI* aURL, // for error reporting
uint32_t aLineNumber, // for error reporting
nsCSSValue& aValue,
bool aSuppressErrors /* false */);
bool ParseMarginString(const nsAString& aBuffer,
nsIURI* aURL, // for error reporting
uint32_t aLineNumber, // for error reporting
nsCSSValue& aValue,
bool aSuppressErrors /* false */);
nsresult ParseSelectorString(const nsAString& aSelectorString,
nsIURI* aURL, // for error reporting
uint32_t aLineNumber, // for error reporting
nsCSSSelectorList **aSelectorList);
already_AddRefed<nsCSSKeyframeRule>
ParseKeyframeRule(const nsAString& aBuffer,
nsIURI* aURL,
uint32_t aLineNumber);
bool ParseKeyframeSelectorString(const nsAString& aSelectorString,
nsIURI* aURL, // for error reporting
uint32_t aLineNumber, // for error reporting
InfallibleTArray<float>& aSelectorList);
bool EvaluateSupportsDeclaration(const nsAString& aProperty,
const nsAString& aValue,
nsIURI* aDocURL,
nsIURI* aBaseURL,
nsIPrincipal* aDocPrincipal);
bool EvaluateSupportsCondition(const nsAString& aCondition,
nsIURI* aDocURL,
nsIURI* aBaseURL,
nsIPrincipal* aDocPrincipal,
SupportsParsingSettings aSettings
= SupportsParsingSettings::Normal);
already_AddRefed<nsAtom> ParseCounterStyleName(const nsAString& aBuffer,
nsIURI* aURL);
bool ParseCounterDescriptor(nsCSSCounterDesc aDescID,
const nsAString& aBuffer,
nsIURI* aSheetURL,
nsIURI* aBaseURL,
nsIPrincipal* aSheetPrincipal,
nsCSSValue& aValue);
bool ParseFontFaceDescriptor(nsCSSFontDesc aDescID,
const nsAString& aBuffer,
nsIURI* aSheetURL,
nsIURI* aBaseURL,
nsIPrincipal* aSheetPrincipal,
nsCSSValue& aValue);
bool IsValueValidForProperty(const nsCSSPropertyID aPropID,
const nsAString& aPropValue);
typedef nsCSSParser::VariableEnumFunc VariableEnumFunc;
/**
* Parses a CSS token stream value and invokes a callback function for each
* variable reference that is encountered.
*
* @param aPropertyValue The CSS token stream value.
* @param aFunc The callback function to invoke; its parameters are the
* variable name found and the aData argument passed in to this function.
* @param aData User data to pass in to the callback.
* @return Whether aPropertyValue could be parsed as a valid CSS token stream
* value (e.g., without syntactic errors in variable references).
*/
bool EnumerateVariableReferences(const nsAString& aPropertyValue,
VariableEnumFunc aFunc,
void* aData);
/**
* Parses aPropertyValue as a CSS token stream value and resolves any
* variable references using the variables in aVariables.
*
* @param aPropertyValue The CSS token stream value.
* @param aVariables The set of variable values to use when resolving variable
* references.
* @param aResult Out parameter that gets the resolved value.
* @param aFirstToken Out parameter that gets the type of the first token in
* aResult.
* @param aLastToken Out parameter that gets the type of the last token in
* aResult.
* @return Whether aResult could be parsed successfully and variable reference
* substitution succeeded.
*/
bool ResolveVariableValue(const nsAString& aPropertyValue,
const CSSVariableValues* aVariables,
nsString& aResult,
nsCSSTokenSerializationType& aFirstToken,
nsCSSTokenSerializationType& aLastToken);
/**
* Parses a string as a CSS token stream value for particular property,
* resolving any variable references. The parsed property value is stored
* in the specified nsRuleData object. If aShorthandPropertyID has a value
* other than eCSSProperty_UNKNOWN, this is the property that will be parsed;
* otherwise, aPropertyID will be parsed. Either way, only aPropertyID,
* a longhand property, will be copied over to the rule data.
*
* If the property cannot be parsed, it will be treated as if 'initial' or
* 'inherit' were specified, for non-inherited and inherited properties
* respectively.
*
* @param aPropertyID The ID of the longhand property whose value is to be
* copied to the rule data.
* @param aShorthandPropertyID The ID of the shorthand property to be parsed.
* If a longhand property is to be parsed, aPropertyID is that property,
* and aShorthandPropertyID must be eCSSProperty_UNKNOWN.
* @param aValue The CSS token stream value.
* @param aVariables The set of variable values to use when resolving variable
* references.
* @param aRuleData The rule data object into which parsed property value for
* aPropertyID will be stored.
*/
void ParsePropertyWithVariableReferences(nsCSSPropertyID aPropertyID,
nsCSSPropertyID aShorthandPropertyID,
const nsAString& aValue,
const CSSVariableValues* aVariables,
nsRuleData* aRuleData,
nsIURI* aDocURL,
nsIURI* aBaseURL,
nsIPrincipal* aDocPrincipal,
CSSStyleSheet* aSheet,
uint32_t aLineNumber,
uint32_t aLineOffset);
bool AgentRulesEnabled() const {
return mParsingMode == css::eAgentSheetFeatures;
}
bool ChromeRulesEnabled() const {
return mIsChrome || mParsingMode == css::eUserSheetFeatures;
}
CSSEnabledState EnabledState() const {
static_assert(int(CSSEnabledState::eForAllContent) == 0,
"CSSEnabledState::eForAllContent should be zero for "
"this bitfield to work");
CSSEnabledState enabledState = CSSEnabledState::eForAllContent;
if (AgentRulesEnabled()) {
enabledState |= CSSEnabledState::eInUASheets;
}
if (ChromeRulesEnabled()) {
enabledState |= CSSEnabledState::eInChrome;
}
return enabledState;
}
nsCSSPropertyID LookupEnabledProperty(const nsAString& aProperty) {
return nsCSSProps::LookupProperty(aProperty, EnabledState());
}
protected:
class nsAutoParseCompoundProperty;
friend class nsAutoParseCompoundProperty;
class nsAutoFailingSupportsRule;
friend class nsAutoFailingSupportsRule;
class nsAutoSuppressErrors;
friend class nsAutoSuppressErrors;
void AppendRule(css::Rule* aRule);
friend void AppendRuleToSheet(css::Rule*, void*); // calls AppendRule
/**
* This helper class automatically calls SetParsingCompoundProperty in its
* constructor and takes care of resetting it to false in its destructor.
*/
class nsAutoParseCompoundProperty {
public:
explicit nsAutoParseCompoundProperty(CSSParserImpl* aParser) : mParser(aParser)
{
NS_ASSERTION(!aParser->IsParsingCompoundProperty(),
"already parsing compound property");
NS_ASSERTION(aParser, "Null parser?");
aParser->SetParsingCompoundProperty(true);
}
~nsAutoParseCompoundProperty()
{
mParser->SetParsingCompoundProperty(false);
}
private:
CSSParserImpl* mParser;
};
/**
* This helper class conditionally sets mInFailingSupportsRule to
* true if aCondition = false, and resets it to its original value in its
* destructor. If we are already somewhere within a failing @supports
* rule, passing in aCondition = true does not change mInFailingSupportsRule.
*/
class nsAutoFailingSupportsRule {
public:
nsAutoFailingSupportsRule(CSSParserImpl* aParser,
bool aCondition)
: mParser(aParser),
mOriginalValue(aParser->mInFailingSupportsRule)
{
if (!aCondition) {
mParser->mInFailingSupportsRule = true;
}
}
~nsAutoFailingSupportsRule()
{
mParser->mInFailingSupportsRule = mOriginalValue;
}
private:
CSSParserImpl* mParser;
bool mOriginalValue;
};
/**
* Auto class to set aParser->mSuppressErrors to the specified value
* and restore it to its original value later.
*/
class nsAutoSuppressErrors {
public:
explicit nsAutoSuppressErrors(CSSParserImpl* aParser,
bool aSuppressErrors = true)
: mParser(aParser),
mOriginalValue(aParser->mSuppressErrors)
{
mParser->mSuppressErrors = aSuppressErrors;
}
~nsAutoSuppressErrors()
{
mParser->mSuppressErrors = mOriginalValue;
}
private:
CSSParserImpl* mParser;
bool mOriginalValue;
};
/**
* RAII class to set aParser->mInSupportsCondition to true and restore it
* to false later.
*/
class MOZ_RAII nsAutoInSupportsCondition
{
public:
explicit nsAutoInSupportsCondition(CSSParserImpl* aParser)
: mParser(aParser)
{
MOZ_ASSERT(!aParser->mInSupportsCondition,
"nsAutoInSupportsCondition is not designed to be used "
"re-entrantly");
mParser->mInSupportsCondition = true;
}
~nsAutoInSupportsCondition()
{
mParser->mInSupportsCondition = false;
}
private:
CSSParserImpl* const mParser;
};
// the caller must hold on to aString until parsing is done
void InitScanner(nsCSSScanner& aScanner,
css::ErrorReporter& aReporter,
nsIURI* aSheetURI, nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
dom::CallerType aCallerType = dom::CallerType::NonSystem);
void ReleaseScanner(void);
/**
* Saves the current input state, which includes any currently pushed
* back token, and the current position of the scanner.
*/
void SaveInputState(CSSParserInputState& aState);
/**
* Restores the saved input state by pushing back any saved pushback
* token and calling RestoreSavedPosition on the scanner.
*/
void RestoreSavedInputState(const CSSParserInputState& aState);
bool GetToken(bool aSkipWS);
void UngetToken();
bool GetNextTokenLocation(bool aSkipWS, uint32_t *linenum, uint32_t *colnum);
void AssertNextTokenAt(uint32_t aLine, uint32_t aCol)
{
// Beware that this method will call GetToken/UngetToken (in
// GetNextTokenLocation) in DEBUG builds, but not in non-DEBUG builds.
DebugOnly<uint32_t> lineAfter, colAfter;
MOZ_ASSERT(GetNextTokenLocation(true, &lineAfter, &colAfter) &&
lineAfter == aLine && colAfter == aCol,
"shouldn't have consumed any tokens");
}
bool ExpectSymbol(char16_t aSymbol, bool aSkipWS);
bool ExpectEndProperty();
bool CheckEndProperty();
nsAString* NextIdent();
// returns true when the stop symbol is found, and false for EOF
bool SkipUntil(char16_t aStopSymbol);
void SkipUntilOneOf(const char16_t* aStopSymbolChars);
// For each character in aStopSymbolChars from the end of the array
// to the start, calls SkipUntil with that character.
typedef AutoTArray<char16_t, 16> StopSymbolCharStack;
void SkipUntilAllOf(const StopSymbolCharStack& aStopSymbolChars);
// returns true if the stop symbol or EOF is found, and false for an
// unexpected ')', ']' or '}'; this not safe to call outside variable
// resolution, as it doesn't handle mismatched content
bool SkipBalancedContentUntil(char16_t aStopSymbol);
void SkipRuleSet(bool aInsideBraces);
bool SkipAtRule(bool aInsideBlock);
MOZ_MUST_USE bool SkipDeclaration(bool aCheckForBraces);
void PushGroup(css::GroupRule* aRule);
void PopGroup();
bool ParseRuleSet(RuleAppendFunc aAppendFunc, void* aProcessData,
bool aInsideBraces = false);
bool ParseAtRule(RuleAppendFunc aAppendFunc, void* aProcessData,
bool aInAtRule);
bool ParseCharsetRule(RuleAppendFunc aAppendFunc, void* aProcessData);
bool ParseImportRule(RuleAppendFunc aAppendFunc, void* aProcessData);
bool ParseURLOrString(nsString& aURL);
bool GatherMedia(nsMediaList* aMedia, bool aInAtRule);
enum eMediaQueryType { eMediaQueryNormal,
// Parsing an at rule
eMediaQueryAtRule,
// Attempt to consume a single media-condition and
// stop. Note that the spec defines "expression and/or
// expression" as one condition but "expression,
// expression" as two.
eMediaQuerySingleCondition };
bool ParseMediaQuery(eMediaQueryType aMode, nsMediaQuery **aQuery,
bool *aHitStop);
bool ParseMediaQueryExpression(nsMediaQuery* aQuery);
void ProcessImport(const nsString& aURLSpec,
nsMediaList* aMedia,
RuleAppendFunc aAppendFunc,
void* aProcessData,
uint32_t aLineNumber,
uint32_t aColumnNumber);
bool ParseGroupRule(css::GroupRule* aRule, RuleAppendFunc aAppendFunc,
void* aProcessData);
bool ParseMediaRule(RuleAppendFunc aAppendFunc, void* aProcessData);
bool ParseMozDocumentRule(RuleAppendFunc aAppendFunc, void* aProcessData);
bool ParseNameSpaceRule(RuleAppendFunc aAppendFunc, void* aProcessData);
void ProcessNameSpace(const nsString& aPrefix,
const nsString& aURLSpec, RuleAppendFunc aAppendFunc,
void* aProcessData,
uint32_t aLineNumber, uint32_t aColumnNumber);
bool ParseFontFaceRule(RuleAppendFunc aAppendFunc, void* aProcessData);
bool ParseFontFeatureValuesRule(RuleAppendFunc aAppendFunc,
void* aProcessData);
bool ParseFontFeatureValueSet(nsCSSFontFeatureValuesRule *aRule);
bool ParseFontDescriptor(nsCSSFontFaceRule* aRule);
bool ParseFontDescriptorValue(nsCSSFontDesc aDescID,
nsCSSValue& aValue);
bool ParsePageRule(RuleAppendFunc aAppendFunc, void* aProcessData);
bool ParseKeyframesRule(RuleAppendFunc aAppendFunc, void* aProcessData);
already_AddRefed<nsCSSKeyframeRule> ParseKeyframeRule();
bool ParseKeyframeSelectorList(InfallibleTArray<float>& aSelectorList);
bool ParseSupportsRule(RuleAppendFunc aAppendFunc, void* aProcessData);
bool ParseSupportsCondition(bool& aConditionMet);
bool ParseSupportsConditionNegation(bool& aConditionMet);
bool ParseSupportsConditionInParens(bool& aConditionMet);
bool ParseSupportsMozBoolPrefName(bool& aConditionMet);
bool ParseSupportsConditionInParensInsideParens(bool& aConditionMet);
bool ParseSupportsConditionTerms(bool& aConditionMet);
enum SupportsConditionTermOperator { eAnd, eOr };
bool ParseSupportsConditionTermsAfterOperator(
bool& aConditionMet,
SupportsConditionTermOperator aOperator);
bool ParseCounterStyleRule(RuleAppendFunc aAppendFunc, void* aProcessData);
already_AddRefed<nsAtom> ParseCounterStyleName(bool aForDefinition);
bool ParseCounterStyleNameValue(nsCSSValue& aValue);
bool ParseCounterDescriptor(nsCSSCounterStyleRule *aRule);
bool ParseCounterDescriptorValue(nsCSSCounterDesc aDescID,
nsCSSValue& aValue);
bool ParseCounterRange(nsCSSValuePair& aPair);
/**
* Parses the current input stream for a CSS token stream value and resolves
* any variable references using the variables in aVariables.
*
* @param aVariables The set of variable values to use when resolving variable
* references.
* @param aResult Out parameter that, if the function returns true, will be
* replaced with the resolved value.
* @return Whether aResult could be parsed successfully and variable reference
* substitution succeeded.
*/
bool ResolveValueWithVariableReferences(
const CSSVariableValues* aVariables,
nsString& aResult,
nsCSSTokenSerializationType& aResultFirstToken,
nsCSSTokenSerializationType& aResultLastToken);
// Helper function for ResolveValueWithVariableReferences.
bool ResolveValueWithVariableReferencesRec(
nsString& aResult,
nsCSSTokenSerializationType& aResultFirstToken,
nsCSSTokenSerializationType& aResultLastToken,
const CSSVariableValues* aVariables);
enum nsSelectorParsingStatus {
// we have parsed a selector and we saw a token that cannot be
// part of a selector:
eSelectorParsingStatus_Done,
// we should continue parsing the selector:
eSelectorParsingStatus_Continue,
// we saw an unexpected token or token value,
// or we saw end-of-file with an unfinished selector:
eSelectorParsingStatus_Error
};
nsSelectorParsingStatus ParseIDSelector(int32_t& aDataMask,
nsCSSSelector& aSelector);
nsSelectorParsingStatus ParseClassSelector(int32_t& aDataMask,
nsCSSSelector& aSelector);
// aPseudoElement and aPseudoElementArgs are the location where
// pseudo-elements (as opposed to pseudo-classes) are stored;
// pseudo-classes are stored on aSelector. aPseudoElement and
// aPseudoElementArgs must be non-null iff !aIsNegated.
nsSelectorParsingStatus ParsePseudoSelector(int32_t& aDataMask,
nsCSSSelector& aSelector,
bool aIsNegated,
nsAtom** aPseudoElement,
nsAtomList** aPseudoElementArgs,
CSSPseudoElementType* aPseudoElementType);
nsSelectorParsingStatus ParseAttributeSelector(int32_t& aDataMask,
nsCSSSelector& aSelector);
nsSelectorParsingStatus ParseTypeOrUniversalSelector(int32_t& aDataMask,
nsCSSSelector& aSelector,
bool aIsNegated);
nsSelectorParsingStatus ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector,
CSSPseudoClassType aType);
nsSelectorParsingStatus ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
CSSPseudoClassType aType);
nsSelectorParsingStatus ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
CSSPseudoClassType aType);
nsSelectorParsingStatus ParseNegatedSimpleSelector(int32_t& aDataMask,
nsCSSSelector& aSelector);
// If aStopChar is non-zero, the selector list is done when we hit
// aStopChar. Otherwise, it's done when we hit EOF.
bool ParseSelectorList(nsCSSSelectorList*& aListHead,
char16_t aStopChar);
bool ParseSelectorGroup(nsCSSSelectorList*& aListHead);
bool ParseSelector(nsCSSSelectorList* aList, char16_t aPrevCombinator);
enum {
eParseDeclaration_InBraces = 1 << 0,
eParseDeclaration_AllowImportant = 1 << 1
};
enum nsCSSContextType {
eCSSContext_General,
eCSSContext_Page
};
already_AddRefed<css::Declaration>
ParseDeclarationBlock(uint32_t aFlags,
nsCSSContextType aContext = eCSSContext_General);
bool ParseDeclaration(css::Declaration* aDeclaration,
uint32_t aFlags,
bool aMustCallValueAppended,
bool* aChanged,
nsCSSContextType aContext = eCSSContext_General);
// A "prefix-aware" wrapper for nsCSSKeywords::LookupKeyword().
// Use this instead of LookupKeyword() if you might be parsing an unprefixed
// property (like "display") for which we emulate a vendor-prefixed value
// (like "-webkit-box").
nsCSSKeyword LookupKeywordPrefixAware(nsAString& aKeywordStr,
const KTableEntry aKeywordTable[]);
bool ParseProperty(nsCSSPropertyID aPropID);
bool ParsePropertyByFunction(nsCSSPropertyID aPropID);
CSSParseResult ParseSingleValueProperty(nsCSSValue& aValue,
nsCSSPropertyID aPropID);
bool ParseSingleValuePropertyByFunction(nsCSSValue& aValue,
nsCSSPropertyID aPropID);
// This is similar to ParseSingleValueProperty but only works for
// properties that are parsed with ParseBoxProperties or
// ParseGroupedBoxProperty.
//
// Only works with variants with the following flags:
// A, C, H, K, L, N, P, CALC.
CSSParseResult ParseBoxProperty(nsCSSValue& aValue,
nsCSSPropertyID aPropID);
enum PriorityParsingStatus {
ePriority_None,
ePriority_Important,
ePriority_Error
};
PriorityParsingStatus ParsePriority();
#ifdef MOZ_XUL
bool ParseTreePseudoElement(nsAtomList **aPseudoElementArgs);
#endif
// Property specific parsing routines
bool ParseImageLayers(const nsCSSPropertyID aTable[]);
struct ImageLayersShorthandParseState {
nsCSSValue& mColor;
nsCSSValueList* mImage;
nsCSSValuePairList* mRepeat;
nsCSSValueList* mAttachment; // A property for background layer only
nsCSSValueList* mClip;
nsCSSValueList* mOrigin;
nsCSSValueList* mPositionX;
nsCSSValueList* mPositionY;
nsCSSValuePairList* mSize;
nsCSSValueList* mComposite; // A property for mask layer only
nsCSSValueList* mMode; // A property for mask layer only
ImageLayersShorthandParseState(
nsCSSValue& aColor, nsCSSValueList* aImage, nsCSSValuePairList* aRepeat,
nsCSSValueList* aAttachment, nsCSSValueList* aClip,
nsCSSValueList* aOrigin,
nsCSSValueList* aPositionX, nsCSSValueList* aPositionY,
nsCSSValuePairList* aSize, nsCSSValueList* aComposite,
nsCSSValueList* aMode) :
mColor(aColor), mImage(aImage), mRepeat(aRepeat),
mAttachment(aAttachment), mClip(aClip), mOrigin(aOrigin),
mPositionX(aPositionX), mPositionY(aPositionY),
mSize(aSize), mComposite(aComposite),
mMode(aMode) {};
};
bool IsFunctionTokenValidForImageLayerImage(const nsCSSToken& aToken) const;
bool ParseImageLayersItem(ImageLayersShorthandParseState& aState,
const nsCSSPropertyID aTable[]);
bool ParseValueList(nsCSSPropertyID aPropID); // a single value prop-id
bool ParseImageLayerRepeat(nsCSSPropertyID aPropID);
bool ParseImageLayerRepeatValues(nsCSSValuePair& aValue);
bool ParseImageLayerPosition(const nsCSSPropertyID aTable[]);
bool ParseImageLayerPositionCoord(nsCSSPropertyID aPropID, bool aIsHorizontal);
// ParseBoxPositionValues parses the CSS 2.1 background-position syntax,
// which is still used by some properties. See ParsePositionValue
// for the css3-background syntax.
bool ParseBoxPositionValues(nsCSSValuePair& aOut, bool aAcceptsInherit,
bool aAllowExplicitCenter = true); // deprecated
// ParsePositionValue parses a CSS <position> value, which is used by
// the 'background-position' property.
bool ParsePositionValue(nsCSSValue& aOut);
bool ParsePositionValueSeparateCoords(nsCSSValue& aOutX, nsCSSValue& aOutY);
bool ParseImageLayerPositionCoordItem(nsCSSValue& aOut, bool aIsHorizontal);
bool ParseImageLayerSize(nsCSSPropertyID aPropID);
bool ParseImageLayerSizeValues(nsCSSValuePair& aOut);
bool ParseBorderColor();
bool ParseBorderColors(nsCSSPropertyID aProperty);
void SetBorderImageInitialValues();
bool ParseBorderImageRepeat(bool aAcceptsInherit);
// If ParseBorderImageSlice returns false, aConsumedTokens indicates
// whether or not any tokens were consumed (in other words, was the property
// in error or just not present). If ParseBorderImageSlice returns true
// aConsumedTokens is always true.
bool ParseBorderImageSlice(bool aAcceptsInherit, bool* aConsumedTokens);
bool ParseBorderImageWidth(bool aAcceptsInherit);
bool ParseBorderImageOutset(bool aAcceptsInherit);
bool ParseBorderImage();
bool ParseBorderSpacing();
bool ParseBorderSide(const nsCSSPropertyID aPropIDs[],
bool aSetAllSides);
bool ParseBorderStyle();
bool ParseBorderWidth();
bool ParseCalc(nsCSSValue &aValue, uint32_t aVariantMask);
bool ParseCalcAdditiveExpression(nsCSSValue& aValue,
uint32_t& aVariantMask);
bool ParseCalcMultiplicativeExpression(nsCSSValue& aValue,
uint32_t& aVariantMask,
bool *aHadFinalWS);
bool ParseCalcTerm(nsCSSValue& aValue, uint32_t& aVariantMask);
bool ParseContextProperties();
bool RequireWhitespace();
// For "flex" shorthand property, defined in CSS Flexbox spec
bool ParseFlex();
// For "flex-flow" shorthand property, defined in CSS Flexbox spec
bool ParseFlexFlow();
// CSS Grid
bool ParseGridAutoFlow();
// Parse a <line-names> expression.
// If successful, either leave aValue untouched,
// to indicate that we parsed the empty list,
// or set it to a eCSSUnit_List of eCSSUnit_Ident.
//
// To parse an optional <line-names> (ie. if not finding an open bracket
// is considered the same as an empty list),
// treat CSSParseResult::NotFound the same as CSSParseResult::Ok.
//
// If aValue is already a eCSSUnit_List, append to that list.
CSSParseResult ParseGridLineNames(nsCSSValue& aValue);
bool ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr);
bool ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue);
CSSParseResult ParseGridTrackBreadth(nsCSSValue& aValue);
// eFixedTrackSize in aFlags makes it parse a <fixed-size>.
CSSParseResult ParseGridTrackSize(nsCSSValue& aValue,
GridTrackSizeFlags aFlags = GridTrackSizeFlags::eDefaultTrackSize);
bool ParseGridAutoColumnsRows(nsCSSPropertyID aPropID);
bool ParseGridTrackListRepeat(nsCSSValueList** aTailPtr);
bool ParseGridTrackRepeatIntro(bool aForSubgrid,
int32_t* aRepetitions,
Maybe<int32_t>* aRepeatAutoEnum);
// Assuming a [ <line-names>? ] has already been parsed,
// parse the rest of a <track-list>.
//
// This exists because [ <line-names>? ] is ambiguous in the 'grid-template'
// shorthand: it can be either the start of a <track-list> (in
// a <'grid-template-rows'>) or of the intertwined syntax that sets both
// grid-template-rows and grid-template-areas.
//
// On success, |aValue| will be a list of odd length >= 3,
// starting with a <line-names> (which is itself a list)
// and alternating between that and <track-size>.
bool ParseGridTrackListWithFirstLineNames(nsCSSValue& aValue,
const nsCSSValue& aFirstLineNames,
GridTrackListFlags aFlags = GridTrackListFlags::eDefaultTrackList);
bool ParseGridTrackList(nsCSSPropertyID aPropID,
GridTrackListFlags aFlags = GridTrackListFlags::eDefaultTrackList);
bool ParseGridTemplateColumnsRows(nsCSSPropertyID aPropID);
// |aAreaIndices| is a lookup table to help us parse faster,
// mapping area names to indices in |aResult.mNamedAreas|.
bool ParseGridTemplateAreasLine(const nsAutoString& aInput,
css::GridTemplateAreasValue* aResult,
nsDataHashtable<nsStringHashKey, uint32_t>& aAreaIndices);
bool ParseGridTemplateAreas();
bool ParseGridTemplateColumnsOrAutoFlow(bool aForGridShorthand);
bool ParseGridTemplate(bool aForGridShorthand = false);
bool ParseGridTemplateAfterString(const nsCSSValue& aFirstLineNames);
bool ParseGrid();
CSSParseResult ParseGridShorthandAutoProps(int32_t aAutoFlowAxis);
bool ParseGridLine(nsCSSValue& aValue);
bool ParseGridColumnRowStartEnd(nsCSSPropertyID aPropID);
bool ParseGridColumnRow(nsCSSPropertyID aStartPropID,
nsCSSPropertyID aEndPropID);
bool ParseGridArea();
bool ParseGridGap();
bool ParseInitialLetter();
// parsing 'align/justify-items/self' from the css-align spec
bool ParseAlignJustifyPosition(nsCSSValue& aResult,
const KTableEntry aTable[]);
bool ParseJustifyItems();
bool ParseAlignItems();
bool ParseAlignJustifySelf(nsCSSPropertyID aPropID);
// parsing 'align/justify-content' from the css-align spec
bool ParseAlignJustifyContent(nsCSSPropertyID aPropID);
bool ParsePlaceContent();
bool ParsePlaceItems();
bool ParsePlaceSelf();
// for 'clip' and '-moz-image-region'
bool ParseRect(nsCSSPropertyID aPropID);
bool ParseColumns();
bool ParseContain(nsCSSValue& aValue);
bool ParseContent();
bool ParseCounterData(nsCSSPropertyID aPropID);
bool ParseCursor();
bool ParseFont();
bool ParseFontSynthesis(nsCSSValue& aValue);
bool ParseSingleAlternate(int32_t& aWhichFeature, nsCSSValue& aValue);
bool ParseFontVariantAlternates(nsCSSValue& aValue);
bool MergeBitmaskValue(int32_t aNewValue, const int32_t aMasks[],
int32_t& aMergedValue);
bool ParseBitmaskValues(nsCSSValue& aValue,
const KTableEntry aKeywordTable[],
const int32_t aMasks[]);
bool ParseFontVariantEastAsian(nsCSSValue& aValue);
bool ParseFontVariantLigatures(nsCSSValue& aValue);
bool ParseFontVariantNumeric(nsCSSValue& aValue);
bool ParseFontVariant();
bool ParseFontWeight(nsCSSValue& aValue);
bool ParseOneFamily(nsAString& aFamily, bool& aOneKeyword, bool& aQuoted);
bool ParseFamily(nsCSSValue& aValue);
bool ParseFontFeatureSettings(nsCSSValue& aValue);
bool ParseFontVariationSettings(nsCSSValue& aValue);
bool ParseFontSrc(nsCSSValue& aValue);
bool ParseFontSrcFormat(InfallibleTArray<nsCSSValue>& values);
bool ParseFontRanges(nsCSSValue& aValue);
bool ParseListStyle();
bool ParseListStyleType(nsCSSValue& aValue);
bool ParseMargin();
bool ParseClipPath(nsCSSValue& aValue);
bool ParseTransform(bool aIsPrefixed, nsCSSPropertyID aProperty,
bool aDisallowRelativeValues = false);
bool ParseObjectPosition();
bool ParseOutline();
bool ParseOverflow();
bool ParsePadding();
bool ParseQuotes();
bool ParseTextAlign(nsCSSValue& aValue,
const KTableEntry aTable[]);
bool ParseTextAlign(nsCSSValue& aValue);
bool ParseTextAlignLast(nsCSSValue& aValue);
bool ParseTextDecoration();
bool ParseTextDecorationLine(nsCSSValue& aValue);
bool ParseTextEmphasis();
bool ParseTextEmphasisPosition(nsCSSValue& aValue);
bool ParseTextEmphasisStyle(nsCSSValue& aValue);
bool ParseTextCombineUpright(nsCSSValue& aValue);
bool ParseTextOverflow(nsCSSValue& aValue);
bool ParseTouchAction(nsCSSValue& aValue);
bool ParseShadowItem(nsCSSValue& aValue, bool aIsBoxShadow);
bool ParseShadowList(nsCSSPropertyID aProperty);
bool ParseShapeOutside(nsCSSValue& aValue);
bool ParseTransitionProperty();
bool ParseTransitionTimingFunctionValues(nsCSSValue& aValue);
bool ParseTransitionTimingFunctionValueComponent(float& aComponent,
char aStop,
bool aIsXPoint);
bool ParseTransitionStepTimingFunctionValues(nsCSSValue& aValue);
bool ParseTransitionFramesTimingFunctionValues(nsCSSValue& aValue);
enum ParseAnimationOrTransitionShorthandResult {
eParseAnimationOrTransitionShorthand_Values,
eParseAnimationOrTransitionShorthand_Inherit,
eParseAnimationOrTransitionShorthand_Error
};
ParseAnimationOrTransitionShorthandResult
ParseAnimationOrTransitionShorthand(const nsCSSPropertyID* aProperties,
const nsCSSValue* aInitialValues,
nsCSSValue* aValues,
size_t aNumProperties);
bool ParseTransition();
bool ParseAnimation();
bool ParseWillChange();
bool ParsePaint(nsCSSPropertyID aPropID);
bool ParseDasharray();
bool ParseMarker();
bool ParsePaintOrder();
bool ParseAll();
bool ParseScrollSnapType();
bool ParseScrollSnapPoints(nsCSSValue& aValue, nsCSSPropertyID aPropID);
bool ParseScrollSnapDestination(nsCSSValue& aValue);
bool ParseScrollSnapCoordinate(nsCSSValue& aValue);
bool ParseWebkitTextStroke();
/**
* Parses a variable value from a custom property declaration.
*
* @param aType Out parameter into which will be stored the type of variable
* value, indicating whether the parsed value was a token stream or one of
* the CSS-wide keywords.
* @param aValue Out parameter into which will be stored the token stream
* as a string, if the parsed custom property did take a token stream.
* @return Whether parsing succeeded.
*/
bool ParseVariableDeclaration(CSSVariableDeclarations::Type* aType,
nsString& aValue);
/**
* Parses a CSS variable value. This could be 'initial', 'inherit', 'unset'
* or a token stream, which may or may not include variable references.
*
* @param aType Out parameter into which the type of the variable value
* will be stored.
* @param aDropBackslash Out parameter indicating whether during variable
* value parsing there was a trailing backslash before EOF that must
* be dropped when serializing the variable value.
* @param aImpliedCharacters Out parameter appended to which will be any
* characters that were implied when encountering EOF and which
* must be included at the end of the serialized variable value.
* @param aFunc A callback function to invoke when a variable reference
* is encountered. May be null. Arguments are the variable name
* and the aData argument passed in to this function.
* @param User data to pass in to the callback.
* @return Whether parsing succeeded.
*/
bool ParseValueWithVariables(CSSVariableDeclarations::Type* aType,
bool* aDropBackslash,
nsString& aImpliedCharacters,
void (*aFunc)(const nsAString&, void*),
void* aData);
/**
* Returns whether the scanner dropped a backslash just before EOF.
*/
bool BackslashDropped();
/**
* Calls AppendImpliedEOFCharacters on mScanner.
*/
void AppendImpliedEOFCharacters(nsAString& aResult);
// Reused utility parsing routines
void AppendValue(nsCSSPropertyID aPropID, const nsCSSValue& aValue);
bool ParseBoxProperties(const nsCSSPropertyID aPropIDs[]);
bool ParseGroupedBoxProperty(int32_t aVariantMask,
nsCSSValue& aValue,
uint32_t aRestrictions);
bool ParseBoxCornerRadius(const nsCSSPropertyID aPropID);
bool ParseBoxCornerRadiiInternals(nsCSSValue array[]);
bool ParseBoxCornerRadii(const nsCSSPropertyID aPropIDs[]);
int32_t ParseChoice(nsCSSValue aValues[],
const nsCSSPropertyID aPropIDs[], int32_t aNumIDs);
CSSParseResult ParseColor(nsCSSValue& aValue);
template<typename ComponentType>
bool ParseRGBColor(ComponentType& aR,
ComponentType& aG,
ComponentType& aB,
ComponentType& aA);
bool ParseHSLColor(float& aHue, float& aSaturation, float& aLightness,
float& aOpacity);
// The ParseColorOpacityAndCloseParen methods below attempt to parse an
// optional [ separator <alpha-value> ] expression, followed by a
// close-parenthesis, at the end of a css color function (e.g. "rgba()" or
// "hsla()"). If these functions simply encounter a close-parenthesis (without
// any [separator <alpha-value>]), they will still succeed (i.e. return true),
// with outparam 'aOpacity' set to a default opacity value (fully-opaque).
//
// The range of opacity component is [0, 255], and the default opacity value
// is 255 (fully-opaque) for this function.
bool ParseColorOpacityAndCloseParen(uint8_t& aOpacity,
char aSeparator);
// Similar to the previous one, but the range of opacity component is
// [0.0f, 1.0f] and the default opacity value is 1.0f (fully-opaque).
bool ParseColorOpacityAndCloseParen(float& aOpacity,
char aSeparator);
// Parse a <number> color component. The range of color component is [0, 255].
// If |aSeparator| is provided, this function will also attempt to parse that
// character after parsing the color component.
bool ParseColorComponent(uint8_t& aComponent, const Maybe<char>& aSeparator);
// Similar to the previous one, but parse a <percentage> color component.
// The range of color component is [0.0f, 1.0f].
bool ParseColorComponent(float& aComponent, const Maybe<char>& aSeparator);
// Parse a <hue> component.
// <hue> = <number> | <angle>
// The unit of outparam 'aAngle' is degree. Assume the unit to be degree if an
// unitless <number> is parsed.
bool ParseHue(float& aAngle);
bool ParseEnum(nsCSSValue& aValue,
const KTableEntry aKeywordTable[]);
// A special ParseEnum for the CSS Box Alignment properties that have
// 'baseline' values. In addition to the keywords in aKeywordTable, it also
// parses 'first baseline' and 'last baseline' as a single value.
// (aKeywordTable must contain 'baseline')
bool ParseAlignEnum(nsCSSValue& aValue, const KTableEntry aKeywordTable[]);
// Variant parsing methods
CSSParseResult ParseVariant(nsCSSValue& aValue,
uint32_t aVariantMask,
const KTableEntry aKeywordTable[]);
CSSParseResult ParseVariantWithRestrictions(nsCSSValue& aValue,
int32_t aVariantMask,
const KTableEntry aKeywordTable[],
uint32_t aRestrictions);
CSSParseResult ParseNonNegativeVariant(nsCSSValue& aValue,
int32_t aVariantMask,
const KTableEntry aKeywordTable[]);
CSSParseResult ParseOneOrLargerVariant(nsCSSValue& aValue,
int32_t aVariantMask,
const KTableEntry aKeywordTable[]);
// Variant parsing methods that are guaranteed to UngetToken any token
// consumed on failure
MOZ_MUST_USE bool ParseSingleTokenVariant(nsCSSValue& aValue,
int32_t aVariantMask,
const KTableEntry aKeywordTable[])
{
MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS),
"use ParseVariant for variants in VARIANT_MULTIPLE_TOKENS");
CSSParseResult result = ParseVariant(aValue, aVariantMask, aKeywordTable);
MOZ_ASSERT(result != CSSParseResult::Error);
return result == CSSParseResult::Ok;
}
bool ParseSingleTokenNonNegativeVariant(nsCSSValue& aValue,
int32_t aVariantMask,
const KTableEntry aKeywordTable[])
{
MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS),
"use ParseNonNegativeVariant for variants in "
"VARIANT_MULTIPLE_TOKENS");
CSSParseResult result =
ParseNonNegativeVariant(aValue, aVariantMask, aKeywordTable);
MOZ_ASSERT(result != CSSParseResult::Error);
return result == CSSParseResult::Ok;
}
bool ParseSingleTokenOneOrLargerVariant(nsCSSValue& aValue,
int32_t aVariantMask,
const KTableEntry aKeywordTable[])
{
MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS),
"use ParseOneOrLargerVariant for variants in "
"VARIANT_MULTIPLE_TOKENS");
CSSParseResult result =
ParseOneOrLargerVariant(aValue, aVariantMask, aKeywordTable);
MOZ_ASSERT(result != CSSParseResult::Error);
return result == CSSParseResult::Ok;
}
// Helpers for some common ParseSingleTokenNonNegativeVariant calls.
bool ParseNonNegativeInteger(nsCSSValue& aValue)
{
return ParseSingleTokenNonNegativeVariant(aValue, VARIANT_INTEGER, nullptr);
}
bool ParseNonNegativeNumber(nsCSSValue& aValue)
{
return ParseSingleTokenNonNegativeVariant(aValue, VARIANT_NUMBER, nullptr);
}
// Helpers for some common ParseSingleTokenOneOrLargerVariant calls.
bool ParseOneOrLargerInteger(nsCSSValue& aValue)
{
return ParseSingleTokenOneOrLargerVariant(aValue, VARIANT_INTEGER, nullptr);
}
bool ParseOneOrLargerNumber(nsCSSValue& aValue)
{
return ParseSingleTokenOneOrLargerVariant(aValue, VARIANT_NUMBER, nullptr);
}
// http://dev.w3.org/csswg/css-values/#custom-idents
// Parse an identifier that is none of:
// * a CSS-wide keyword
// * "default"
// * a keyword in |aExcludedKeywords|
// * a keyword in |aPropertyKTable|
//
// |aExcludedKeywords| is an array of nsCSSKeyword
// that ends with a eCSSKeyword_UNKNOWN marker.
//
// |aPropertyKTable| can be used if some of the keywords to exclude
// also appear in an existing nsCSSProps::KTableEntry,
// to avoid duplicating them.
bool ParseCustomIdent(nsCSSValue& aValue,
const nsAutoString& aIdentValue,
const nsCSSKeyword aExcludedKeywords[] = nullptr,
const nsCSSProps::KTableEntry aPropertyKTable[] = nullptr);
bool ParseCounter(nsCSSValue& aValue);
bool ParseAttr(nsCSSValue& aValue);
bool ParseSymbols(nsCSSValue& aValue);
bool SetValueToURL(nsCSSValue& aValue, const nsString& aURL);
bool TranslateDimension(nsCSSValue& aValue, uint32_t aVariantMask,
float aNumber, const nsString& aUnit);
bool ParseImageOrientation(nsCSSValue& aAngle);
bool ParseImageRect(nsCSSValue& aImage);
bool ParseElement(nsCSSValue& aValue);
bool ParseColorStop(nsCSSValueGradient* aGradient);
enum GradientParsingFlags {
eGradient_Repeating = 1 << 0, // repeating-{linear|radial}-gradient
eGradient_MozLegacy = 1 << 1, // -moz-{linear|radial}-gradient
eGradient_WebkitLegacy = 1 << 2, // -webkit-{linear|radial}-gradient
// Mask to catch both "legacy" flags:
eGradient_AnyLegacy = eGradient_MozLegacy | eGradient_WebkitLegacy
};
bool ParseLinearGradient(nsCSSValue& aValue, uint8_t aFlags);
bool ParseRadialGradient(nsCSSValue& aValue, uint8_t aFlags);
bool IsLegacyGradientLine(const nsCSSTokenType& aType,
const nsString& aId);
bool ParseGradientColorStops(nsCSSValueGradient* aGradient,
nsCSSValue& aValue);
// For the ancient "-webkit-gradient(linear|radial, ...)" syntax:
bool ParseWebkitGradientPointComponent(nsCSSValue& aComponent,
bool aIsHorizontal);
bool ParseWebkitGradientPoint(nsCSSValuePair& aPoint);
bool ParseWebkitGradientRadius(float& aRadius);
bool ParseWebkitGradientColorStop(nsCSSValueGradient* aGradient);
bool ParseWebkitGradientColorStops(nsCSSValueGradient* aGradient);
void FinalizeLinearWebkitGradient(nsCSSValueGradient* aGradient,
const nsCSSValuePair& aStartPoint,
const nsCSSValuePair& aSecondPoint);
void FinalizeRadialWebkitGradient(nsCSSValueGradient* aGradient,
const nsCSSValuePair& aFirstCenter,
const nsCSSValuePair& aSecondCenter,
const float aFirstRadius,
const float aSecondRadius);
bool ParseWebkitGradient(nsCSSValue& aValue);
void SetParsingCompoundProperty(bool aBool) {
mParsingCompoundProperty = aBool;
}
bool IsParsingCompoundProperty(void) const {
return mParsingCompoundProperty;
}
/* Functions for basic shapes */
bool ParseReferenceBoxAndBasicShape(nsCSSValue& aValue,
const KTableEntry aBoxKeywordTable[]);
bool ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens);
bool ParsePolygonFunction(nsCSSValue& aValue);
bool ParseCircleOrEllipseFunction(nsCSSKeyword, nsCSSValue& aValue);
bool ParseInsetFunction(nsCSSValue& aValue);
// We parse position values differently for basic-shape, by expanding defaults
// and replacing keywords with percentages
bool ParsePositionValueForBasicShape(nsCSSValue& aOut);
/* Functions for transform Parsing */
bool ParseSingleTransform(bool aIsPrefixed, bool aDisallowRelativeValues,
nsCSSValue& aValue);
bool ParseFunction(nsCSSKeyword aFunction, const uint32_t aAllowedTypes[],
uint32_t aVariantMaskAll, uint16_t aMinElems,
uint16_t aMaxElems, nsCSSValue &aValue);
bool ParseFunctionInternals(const uint32_t aVariantMask[],
uint32_t aVariantMaskAll,
uint16_t aMinElems,
uint16_t aMaxElems,
InfallibleTArray<nsCSSValue>& aOutput);
/* Functions for transform-origin/perspective-origin Parsing */
bool ParseTransformOrigin(nsCSSPropertyID aProperty);
/* Functions for filter parsing */
bool ParseFilter();
bool ParseSingleFilter(nsCSSValue* aValue);
bool ParseDropShadow(nsCSSValue* aValue);
/* Find and return the namespace ID associated with aPrefix.
If aPrefix has not been declared in an @namespace rule, returns
kNameSpaceID_Unknown. */
int32_t GetNamespaceIdForPrefix(const nsString& aPrefix);
/* Find the correct default namespace, and set it on aSelector. */
void SetDefaultNamespaceOnSelector(nsCSSSelector& aSelector);
// Current token. The value is valid after calling GetToken and invalidated
// by UngetToken.
nsCSSToken mToken;
// Our scanner.
nsCSSScanner* mScanner;
// Our error reporter.
css::ErrorReporter* mReporter;
// The URI to be used as a base for relative URIs.
nsCOMPtr<nsIURI> mBaseURI;
// The URI to be used as an HTTP "Referer" and for error reporting.
nsCOMPtr<nsIURI> mSheetURI;
// The principal of the sheet involved
nsCOMPtr<nsIPrincipal> mSheetPrincipal;
// The sheet we're parsing into
RefPtr<CSSStyleSheet> mSheet;
// Used for @import rules
css::Loader* mChildLoader; // not ref counted, it owns us
// Any sheets we may reuse when parsing an @import.
css::LoaderReusableStyleSheets* mReusableSheets;
// Sheet section we're in. This is used to enforce correct ordering of the
// various rule types (eg the fact that a @charset rule must come before
// anything else). Note that there are checks of similar things in various
// places in CSSStyleSheet.cpp (e.g in insertRule, RebuildChildList).
enum nsCSSSection {
eCSSSection_Charset,
eCSSSection_Import,
eCSSSection_NameSpace,
eCSSSection_General
};
nsCSSSection mSection;
nsXMLNameSpaceMap *mNameSpaceMap; // weak, mSheet owns it
// After an UngetToken is done this flag is true. The next call to
// GetToken clears the flag.
bool mHavePushBack : 1;
// True if we are in quirks mode; false in standards or almost standards mode
bool mNavQuirkMode : 1;
// True when the hashless color quirk applies.
bool mHashlessColorQuirk : 1;
// True when the unitless length quirk applies.
bool mUnitlessLengthQuirk : 1;
// True if we are in parsing rules for the chrome.
bool mIsChrome : 1;
// True if we're parsing SVG presentation attributes
// These attributes allow non-calc lengths to be unitless (mapping to px)
bool mIsSVGMode : 1;
// True if viewport units should be allowed.
bool mViewportUnitsEnabled : 1;
// This flag is set when parsing a non-box shorthand; it's used to not apply
// some quirks during shorthand parsing
bool mParsingCompoundProperty : 1;
// True if we are in the middle of parsing an @supports condition.
// This is used to avoid recording the input stream when variable references
// are encountered in a property declaration in the @supports condition.
bool mInSupportsCondition : 1;
// True if we are somewhere within a @supports rule whose condition is
// false.
bool mInFailingSupportsRule : 1;
// True if we will suppress all parse errors (except unexpected EOFs).
// This is used to prevent errors for declarations inside a failing
// @supports rule.
bool mSuppressErrors : 1;
// True if any parsing of URL values requires a sheet principal to have
// been passed in the nsCSSScanner constructor. This is usually the case.
// It can be set to false, for example, when we create an nsCSSParser solely
// to parse a property value to test it for syntactic correctness. When
// false, an assertion that mSheetPrincipal is non-null is skipped. Should
// not be set to false if any nsCSSValues created during parsing can escape
// out of the parser.
bool mSheetPrincipalRequired;
// Controls access to nonstandard style constructs that are not safe
// for use on the public Web but necessary in UA sheets and/or
// useful in user sheets.
css::SheetParsingMode mParsingMode;
// This enum helps us track whether we've unprefixed "display: -webkit-box"
// (treating it as "display: flex") in an earlier declaration within a series
// of declarations. (This only impacts behavior if
// sWebkitPrefixedAliasesEnabled is true.)
enum WebkitBoxUnprefixState : uint8_t {
eNotParsingDecls, // We are *not* currently parsing a sequence of
// CSS declarations. (default state)
// The next two enum values indicate that we *are* currently parsing a
// sequence of declarations (in ParseDeclarations or ParseDeclarationBlock)
// and...
eHaveNotUnprefixed, // ...we have not unprefixed 'display:-webkit-box' in
// this sequence of CSS declarations.
eHaveUnprefixed // ...we *have* unprefixed 'display:-webkit-box' earlier in
// this sequence of CSS declarations.
};
WebkitBoxUnprefixState mWebkitBoxUnprefixState;
// Stack of rule groups; used for @media and such.
InfallibleTArray<RefPtr<css::GroupRule> > mGroupStack;
// During the parsing of a property (which may be a shorthand), the data
// are stored in |mTempData|. (It is needed to ensure that parser
// errors cause the data to be ignored, and to ensure that a
// non-'!important' declaration does not override an '!important'
// one.)
nsCSSExpandedDataBlock mTempData;
// All data from successfully parsed properties are placed into |mData|.
nsCSSExpandedDataBlock mData;
public:
// Used from nsCSSParser constructors and destructors
CSSParserImpl* mNextFree;
};
static void AssignRuleToPointer(css::Rule* aRule, void* aPointer)
{
css::Rule **pointer = static_cast<css::Rule**>(aPointer);
NS_ADDREF(*pointer = aRule);
}
static void AppendRuleToSheet(css::Rule* aRule, void* aParser)
{
CSSParserImpl* parser = (CSSParserImpl*) aParser;
parser->AppendRule(aRule);
}
#define REPORT_UNEXPECTED(msg_) \
{ if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_); }
#define REPORT_UNEXPECTED_P(msg_, param_) \
{ if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, param_); }
#define REPORT_UNEXPECTED_P_V(msg_, param_, value_) \
{ if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, param_, value_); }
#define REPORT_UNEXPECTED_TOKEN(msg_) \
{ if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, mToken); }
#define REPORT_UNEXPECTED_TOKEN_CHAR(msg_, ch_) \
{ if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, mToken, ch_); }
#define REPORT_UNEXPECTED_EOF(lf_) \
mReporter->ReportUnexpectedEOF(#lf_)
#define REPORT_UNEXPECTED_EOF_CHAR(ch_) \
mReporter->ReportUnexpectedEOF(ch_)
#define OUTPUT_ERROR() \
mReporter->OutputError()
#define OUTPUT_ERROR_WITH_POSITION(linenum_, lineoff_) \
mReporter->OutputError(linenum_, lineoff_)
#define CLEAR_ERROR() \
mReporter->ClearError()
CSSParserImpl::CSSParserImpl()
: mToken(),
mScanner(nullptr),
mReporter(nullptr),
mChildLoader(nullptr),
mReusableSheets(nullptr),
mSection(eCSSSection_Charset),
mNameSpaceMap(nullptr),
mHavePushBack(false),
mNavQuirkMode(false),
mHashlessColorQuirk(false),
mUnitlessLengthQuirk(false),
mIsChrome(false),
mIsSVGMode(false),
mViewportUnitsEnabled(true),
mParsingCompoundProperty(false),
mInSupportsCondition(false),
mInFailingSupportsRule(false),
mSuppressErrors(false),
mSheetPrincipalRequired(true),
mParsingMode(css::eAuthorSheetFeatures),
mWebkitBoxUnprefixState(eNotParsingDecls),
mNextFree(nullptr)
{
}
CSSParserImpl::~CSSParserImpl()
{
mData.AssertInitialState();
mTempData.AssertInitialState();
}
nsresult
CSSParserImpl::SetStyleSheet(CSSStyleSheet* aSheet)
{
if (aSheet != mSheet) {
// Switch to using the new sheet, if any
mGroupStack.Clear();
mSheet = aSheet;
if (mSheet) {
mNameSpaceMap = mSheet->GetNameSpaceMap();
} else {
mNameSpaceMap = nullptr;
}
} else if (mSheet) {
mNameSpaceMap = mSheet->GetNameSpaceMap();
}
return NS_OK;
}
nsIDocument*
CSSParserImpl::GetDocument()
{
if (!mSheet) {
return nullptr;
}
return mSheet->GetAssociatedDocument();
}
nsresult
CSSParserImpl::SetQuirkMode(bool aQuirkMode)
{
mNavQuirkMode = aQuirkMode;
return NS_OK;
}
nsresult
CSSParserImpl::SetChildLoader(mozilla::css::Loader* aChildLoader)
{
mChildLoader = aChildLoader; // not ref counted, it owns us
return NS_OK;
}
void
CSSParserImpl::Reset()
{
NS_ASSERTION(!mScanner, "resetting with scanner active");
SetStyleSheet(nullptr);
SetQuirkMode(false);
SetChildLoader(nullptr);
}
void
CSSParserImpl::InitScanner(nsCSSScanner& aScanner,
css::ErrorReporter& aReporter,
nsIURI* aSheetURI, nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
dom::CallerType aCallerType)
{
NS_PRECONDITION(!mParsingCompoundProperty, "Bad initial state");
NS_PRECONDITION(!mScanner, "already have scanner");
mScanner = &aScanner;
mReporter = &aReporter;
mScanner->SetErrorReporter(mReporter);
mBaseURI = aBaseURI;
mSheetURI = aSheetURI;
mSheetPrincipal = aSheetPrincipal;
mHavePushBack = false;
mIsChrome =
aCallerType == dom::CallerType::System ||
(aSheetURI && dom::IsChromeURI(aSheetURI));
}
void
CSSParserImpl::ReleaseScanner()
{
mScanner = nullptr;
mReporter = nullptr;
mBaseURI = nullptr;
mSheetURI = nullptr;
mSheetPrincipal = nullptr;
mIsChrome = false;
}
nsresult
CSSParserImpl::ParseSheet(const nsAString& aInput,
nsIURI* aSheetURI,
nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
uint32_t aLineNumber,
css::LoaderReusableStyleSheets* aReusableSheets)
{
NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
NS_PRECONDITION(aBaseURI, "need base URI");
NS_PRECONDITION(aSheetURI, "need sheet URI");
NS_PRECONDITION(mSheet, "Must have sheet to parse into");
NS_ENSURE_STATE(mSheet);
#ifdef DEBUG
nsIURI* uri = mSheet->GetSheetURI();
bool equal;
NS_ASSERTION(NS_SUCCEEDED(aSheetURI->Equals(uri, &equal)) && equal,
"Sheet URI does not match passed URI");
NS_ASSERTION(NS_SUCCEEDED(mSheet->Principal()->Equals(aSheetPrincipal,
&equal)) &&
equal,
"Sheet principal does not match passed principal");
#endif
nsCSSScanner scanner(aInput, aLineNumber);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
int32_t ruleCount = mSheet->StyleRuleCount();
if (0 < ruleCount) {
const css::Rule* lastRule = mSheet->GetStyleRuleAt(ruleCount - 1);
if (lastRule) {
switch (lastRule->GetType()) {
case css::Rule::CHARSET_RULE:
case css::Rule::IMPORT_RULE:
mSection = eCSSSection_Import;
break;
case css::Rule::NAMESPACE_RULE:
mSection = eCSSSection_NameSpace;
break;
default:
mSection = eCSSSection_General;
break;
}
}
}
else {
mSection = eCSSSection_Charset; // sheet is empty, any rules are fair
}
mParsingMode = mSheet->ParsingMode();
mReusableSheets = aReusableSheets;
nsCSSToken* tk = &mToken;
for (;;) {
// Get next non-whitespace token
if (!GetToken(true)) {
OUTPUT_ERROR();
break;
}
if (eCSSToken_HTMLComment == tk->mType) {
continue; // legal here only
}
if (eCSSToken_AtKeyword == tk->mType) {
ParseAtRule(AppendRuleToSheet, this, false);
continue;
}
UngetToken();
if (ParseRuleSet(AppendRuleToSheet, this)) {
mSection = eCSSSection_General;
}
}
mSheet->SetSourceMapURLFromComment(scanner.GetSourceMapURL());
mSheet->SetSourceURL(scanner.GetSourceURL());
ReleaseScanner();
mParsingMode = css::eAuthorSheetFeatures;
mReusableSheets = nullptr;
return NS_OK;
}
/**
* Determines whether the identifier contained in the given string is a
* vendor-specific identifier, as described in CSS 2.1 section 4.1.2.1.
*/
static bool
NonMozillaVendorIdentifier(const nsAString& ident)
{
return (ident.First() == char16_t('-') &&
!StringBeginsWith(ident, NS_LITERAL_STRING("-moz-"))) ||
ident.First() == char16_t('_');
}
already_AddRefed<css::Declaration>
CSSParserImpl::ParseStyleAttribute(const nsAString& aAttributeValue,
nsIURI* aDocURI,
nsIURI* aBaseURI,
nsIPrincipal* aNodePrincipal)
{
NS_PRECONDITION(aNodePrincipal, "Must have principal here!");
NS_PRECONDITION(aBaseURI, "need base URI");
// XXX line number?
nsCSSScanner scanner(aAttributeValue, 0);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURI);
InitScanner(scanner, reporter, aDocURI, aBaseURI, aNodePrincipal);
mSection = eCSSSection_General;
uint32_t parseFlags = eParseDeclaration_AllowImportant;
RefPtr<css::Declaration> declaration = ParseDeclarationBlock(parseFlags);
ReleaseScanner();
return declaration.forget();
}
nsresult
CSSParserImpl::ParseDeclarations(const nsAString& aBuffer,
nsIURI* aSheetURI,
nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
css::Declaration* aDeclaration,
bool* aChanged)
{
*aChanged = false;
NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
nsCSSScanner scanner(aBuffer, 0);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
MOZ_ASSERT(mWebkitBoxUnprefixState == eNotParsingDecls,
"Someone forgot to clear mWebkitBoxUnprefixState!");
AutoRestore<WebkitBoxUnprefixState> autoRestore(mWebkitBoxUnprefixState);
mWebkitBoxUnprefixState = eHaveNotUnprefixed;
mSection = eCSSSection_General;
mData.AssertInitialState();
aDeclaration->ClearData();
// We could check if it was already empty, but...
*aChanged = true;
for (;;) {
// If we cleared the old decl, then we want to be calling
// ValueAppended as we parse.
if (!ParseDeclaration(aDeclaration, eParseDeclaration_AllowImportant,
true, aChanged)) {
if (!SkipDeclaration(false)) {
break;
}
}
}
aDeclaration->CompressFrom(&mData);
ReleaseScanner();
return NS_OK;
}
nsresult
CSSParserImpl::ParseRule(const nsAString& aRule,
nsIURI* aSheetURI,
nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
css::Rule** aResult)
{
NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
NS_PRECONDITION(aBaseURI, "need base URI");
*aResult = nullptr;
nsCSSScanner scanner(aRule, 0);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
mSection = eCSSSection_Charset; // callers are responsible for rejecting invalid rules.
nsCSSToken* tk = &mToken;
// Get first non-whitespace token
nsresult rv = NS_OK;
if (!GetToken(true)) {
REPORT_UNEXPECTED(PEParseRuleWSOnly);
OUTPUT_ERROR();
rv = NS_ERROR_DOM_SYNTAX_ERR;
} else {
if (eCSSToken_AtKeyword == tk->mType) {
// FIXME: perhaps aInsideBlock should be true when we are?
ParseAtRule(AssignRuleToPointer, aResult, false);
} else {
UngetToken();
ParseRuleSet(AssignRuleToPointer, aResult);
}
if (*aResult && GetToken(true)) {
// garbage after rule
REPORT_UNEXPECTED_TOKEN(PERuleTrailing);
NS_RELEASE(*aResult);
}
if (!*aResult) {
rv = NS_ERROR_DOM_SYNTAX_ERR;
OUTPUT_ERROR();
}
}
ReleaseScanner();
return rv;
}
void
CSSParserImpl::ParseLonghandProperty(const nsCSSPropertyID aPropID,
const nsAString& aPropValue,
nsIURI* aSheetURL,
nsIURI* aBaseURL,
nsIPrincipal* aSheetPrincipal,
nsCSSValue& aValue)
{
MOZ_ASSERT(aPropID < eCSSProperty_COUNT_no_shorthands,
"ParseLonghandProperty must only take a longhand property");
RefPtr<css::Declaration> declaration = new css::Declaration;
declaration->InitializeEmpty();
bool changed;
ParseProperty(aPropID, aPropValue, aSheetURL, aBaseURL, aSheetPrincipal,
declaration, &changed,
/* aIsImportant */ false,
/* aIsSVGMode */ false);
if (changed) {
aValue = *declaration->GetNormalBlock()->ValueFor(aPropID);
} else {
aValue.Reset();
}
}
bool
CSSParserImpl::ParseTransformProperty(const nsAString& aPropValue,
bool aDisallowRelativeValues,
nsCSSValue& aValue)
{
RefPtr<css::Declaration> declaration = new css::Declaration();
declaration->InitializeEmpty();
mData.AssertInitialState();
mTempData.AssertInitialState();
nsCSSScanner scanner(aPropValue, 0);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, nullptr);
InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
bool parsedOK = ParseTransform(false, eCSSProperty_transform,
aDisallowRelativeValues);
// We should now be at EOF
if (parsedOK && GetToken(true)) {
parsedOK = false;
}
bool changed = false;
if (parsedOK) {
declaration->ExpandTo(&mData);
changed = mData.TransferFromBlock(mTempData, eCSSProperty_transform,
EnabledState(), false,
true, false, declaration,
GetDocument());
declaration->CompressFrom(&mData);
}
if (changed) {
aValue = *declaration->GetNormalBlock()->ValueFor(eCSSProperty_transform);
} else {
aValue.Reset();
}
ReleaseScanner();
return parsedOK;
}
void
CSSParserImpl::ParseProperty(const nsCSSPropertyID aPropID,
const nsAString& aPropValue,
nsIURI* aSheetURI,
nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
css::Declaration* aDeclaration,
bool* aChanged,
bool aIsImportant,
bool aIsSVGMode)
{
NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
NS_PRECONDITION(aBaseURI, "need base URI");
NS_PRECONDITION(aDeclaration, "Need declaration to parse into!");
MOZ_ASSERT(aPropID != eCSSPropertyExtra_variable);
mData.AssertInitialState();
mTempData.AssertInitialState();
aDeclaration->AssertMutable();
nsCSSScanner scanner(aPropValue, 0);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
mSection = eCSSSection_General;
*aChanged = false;
// Check for unknown or preffed off properties
if (eCSSProperty_UNKNOWN == aPropID ||
!nsCSSProps::IsEnabled(aPropID, EnabledState())) {
NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropID));
REPORT_UNEXPECTED_P(PEUnknownProperty, propName);
REPORT_UNEXPECTED(PEDeclDropped);
OUTPUT_ERROR();
ReleaseScanner();
return;
}
mIsSVGMode = aIsSVGMode;
bool parsedOK = ParseProperty(aPropID);
// We should now be at EOF
if (parsedOK && GetToken(true)) {
REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
parsedOK = false;
}
if (!parsedOK) {
NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropID));
REPORT_UNEXPECTED_P(PEValueParsingError, propName);
REPORT_UNEXPECTED(PEDeclDropped);
OUTPUT_ERROR();
mTempData.ClearProperty(aPropID);
} else {
// We know we don't need to force a ValueAppended call for the new
// value. So if we are not processing a shorthand, and there's
// already a value for this property in the declaration at the
// same importance level, then we can just copy our parsed value
// directly into the declaration without going through the whole
// expand/compress thing.
if (!aDeclaration->TryReplaceValue(aPropID, aIsImportant, mTempData,
aChanged)) {
// Do it the slow way
aDeclaration->ExpandTo(&mData);
*aChanged = mData.TransferFromBlock(mTempData, aPropID,
EnabledState(), aIsImportant,
true, false, aDeclaration,
GetDocument());
aDeclaration->CompressFrom(&mData);
}
CLEAR_ERROR();
}
mTempData.AssertInitialState();
mIsSVGMode = false;
ReleaseScanner();
}
void
CSSParserImpl::ParseVariable(const nsAString& aVariableName,
const nsAString& aPropValue,
nsIURI* aSheetURI,
nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
css::Declaration* aDeclaration,
bool* aChanged,
bool aIsImportant)
{
NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
NS_PRECONDITION(aBaseURI, "need base URI");
NS_PRECONDITION(aDeclaration, "Need declaration to parse into!");
mData.AssertInitialState();
mTempData.AssertInitialState();
aDeclaration->AssertMutable();
nsCSSScanner scanner(aPropValue, 0);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
mSection = eCSSSection_General;
*aChanged = false;
CSSVariableDeclarations::Type variableType;
nsString variableValue;
bool parsedOK = ParseVariableDeclaration(&variableType, variableValue);
// We should now be at EOF
if (parsedOK && GetToken(true)) {
REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
parsedOK = false;
}
if (!parsedOK) {
REPORT_UNEXPECTED_P(PEValueParsingError, NS_LITERAL_STRING("--") +
aVariableName);
REPORT_UNEXPECTED(PEDeclDropped);
OUTPUT_ERROR();
} else {
CLEAR_ERROR();
aDeclaration->AddVariable(aVariableName, variableType,
variableValue, aIsImportant, true);
*aChanged = true;
}
mTempData.AssertInitialState();
ReleaseScanner();
}
void
CSSParserImpl::ParseMediaList(const nsAString& aBuffer,
nsIURI* aURI, // for error reporting
uint32_t aLineNumber, // for error reporting
nsMediaList* aMediaList,
dom::CallerType aCallerType)
{
// XXX Are there cases where the caller wants to keep what it already
// has in case of parser error? If GatherMedia ever changes to return
// a value other than true, we probably should avoid modifying aMediaList.
aMediaList->Clear();
// fake base URI since media lists don't have URIs in them
nsCSSScanner scanner(aBuffer, aLineNumber);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
InitScanner(scanner, reporter, aURI, aURI, nullptr, aCallerType);
DebugOnly<bool> parsedOK = GatherMedia(aMediaList, false);
NS_ASSERTION(parsedOK, "GatherMedia returned false; we probably want to avoid "
"trashing aMediaList");
CLEAR_ERROR();
ReleaseScanner();
}
// <source-size-list> = <source-size>#?
// <source-size> = <media-condition>? <length>
bool
CSSParserImpl::ParseSourceSizeList(const nsAString& aBuffer,
nsIURI* aURI, // for error reporting
uint32_t aLineNumber, // for error reporting
InfallibleTArray< nsAutoPtr<nsMediaQuery> >& aQueries,
InfallibleTArray<nsCSSValue>& aValues)
{
aQueries.Clear();
aValues.Clear();
// fake base URI since media value lists don't have URIs in them
nsCSSScanner scanner(aBuffer, aLineNumber);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
InitScanner(scanner, reporter, aURI, aURI, nullptr);
// https://html.spec.whatwg.org/multipage/embedded-content.html#parse-a-sizes-attribute
bool hitEnd = false;
do {
bool hitError = false;
// Parse single <media-condition> <source-size-value>
do {
nsAutoPtr<nsMediaQuery> query;
nsCSSValue value;
bool hitStop;
if (!ParseMediaQuery(eMediaQuerySingleCondition, getter_Transfers(query),
&hitStop)) {
NS_ASSERTION(!hitStop, "should return true when hit stop");
hitError = true;
break;
}
if (!query) {
REPORT_UNEXPECTED_EOF(PEParseSourceSizeListEOF);
NS_ASSERTION(hitStop,
"should return hitStop or an error if returning no query");
hitError = true;
break;
}
if (hitStop) {
// Empty conditions (e.g. just a bare value) should be treated as always
// matching (a query with no expressions fails to match, so a negated one
// always matches.)
query->SetNegated();
}
// https://html.spec.whatwg.org/multipage/embedded-content.html#source-size-value
// Percentages are not allowed in a <source-size-value>, to avoid
// confusion about what it would be relative to.
if (ParseNonNegativeVariant(value, VARIANT_LCALC, nullptr) !=
CSSParseResult::Ok) {
hitError = true;
break;
}
if (GetToken(true)) {
if (!mToken.IsSymbol(',')) {
REPORT_UNEXPECTED_TOKEN(PEParseSourceSizeListNotComma);
hitError = true;
break;
}
} else {
hitEnd = true;
}
aQueries.AppendElement(query.forget());
aValues.AppendElement(value);
} while(0);
if (hitError) {
OUTPUT_ERROR();
// Per spec, we just skip the current entry if there was a parse error.
// Jumps to next entry of <source-size-list> which is a comma-separated list.
if (!SkipUntil(',')) {
hitEnd = true;
}
}
} while (!hitEnd);
CLEAR_ERROR();
ReleaseScanner();
return !aQueries.IsEmpty();
}
bool
CSSParserImpl::ParseColorString(const nsAString& aBuffer,
nsIURI* aURI, // for error reporting
uint32_t aLineNumber, // for error reporting
nsCSSValue& aValue,
bool aSuppressErrors /* false */)
{
nsCSSScanner scanner(aBuffer, aLineNumber);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
InitScanner(scanner, reporter, aURI, aURI, nullptr);
nsAutoSuppressErrors suppressErrors(this, aSuppressErrors);
// Parse a color, and check that there's nothing else after it.
bool colorParsed = ParseColor(aValue) == CSSParseResult::Ok &&
!GetToken(true);
if (aSuppressErrors) {
CLEAR_ERROR();
} else {
OUTPUT_ERROR();
}
ReleaseScanner();
return colorParsed;
}
bool
CSSParserImpl::ParseMarginString(const nsAString& aBuffer,
nsIURI* aURI, // for error reporting
uint32_t aLineNumber, // for error reporting
nsCSSValue& aValue,
bool aSuppressErrors /* false */)
{
nsCSSScanner scanner(aBuffer, aLineNumber);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
InitScanner(scanner, reporter, aURI, aURI, nullptr);
nsAutoSuppressErrors suppressErrors(this, aSuppressErrors);
// Parse a margin, and check that there's nothing else after it.
bool marginParsed = ParseGroupedBoxProperty(VARIANT_LP, aValue, 0) && !GetToken(true);
if (aSuppressErrors) {
CLEAR_ERROR();
} else {
OUTPUT_ERROR();
}
ReleaseScanner();
return marginParsed;
}
bool
CSSParserImpl::ParseFontFamilyListString(const nsAString& aBuffer,
nsIURI* aURI, // for error reporting
uint32_t aLineNumber, // for error reporting
nsCSSValue& aValue)
{
nsCSSScanner scanner(aBuffer, aLineNumber);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
InitScanner(scanner, reporter, aURI, aURI, nullptr);
// Parse a font family list, and check that there's nothing else after it.
bool familyParsed = ParseFamily(aValue) && !GetToken(true);
OUTPUT_ERROR();
ReleaseScanner();
return familyParsed;
}
nsresult
CSSParserImpl::ParseSelectorString(const nsAString& aSelectorString,
nsIURI* aURI, // for error reporting
uint32_t aLineNumber, // for error reporting
nsCSSSelectorList **aSelectorList)
{
nsCSSScanner scanner(aSelectorString, aLineNumber);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
InitScanner(scanner, reporter, aURI, aURI, nullptr);
bool success = ParseSelectorList(*aSelectorList, char16_t(0));
// We deliberately do not call OUTPUT_ERROR here, because all our
// callers map a failure return to a JS exception, and if that JS
// exception is caught, people don't want to see parser diagnostics;
// see e.g. http://bugs.jquery.com/ticket/7535
// It would be nice to be able to save the parser diagnostics into
// the exception, so that if it _isn't_ caught we can report them
// along with the usual uncaught-exception message, but we don't
// have any way to do that at present; see bug 631621.
CLEAR_ERROR();
ReleaseScanner();
if (success) {
NS_ASSERTION(*aSelectorList, "Should have list!");
return NS_OK;
}
NS_ASSERTION(!*aSelectorList, "Shouldn't have list!");
return NS_ERROR_DOM_SYNTAX_ERR;
}
already_AddRefed<nsCSSKeyframeRule>
CSSParserImpl::ParseKeyframeRule(const nsAString& aBuffer,
nsIURI* aURI,
uint32_t aLineNumber)
{
nsCSSScanner scanner(aBuffer, aLineNumber);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
InitScanner(scanner, reporter, aURI, aURI, nullptr);
RefPtr<nsCSSKeyframeRule> result = ParseKeyframeRule();
if (GetToken(true)) {
// extra garbage at the end
result = nullptr;
}
OUTPUT_ERROR();
ReleaseScanner();
return result.forget();
}
bool
CSSParserImpl::ParseKeyframeSelectorString(const nsAString& aSelectorString,
nsIURI* aURI, // for error reporting
uint32_t aLineNumber, // for error reporting
InfallibleTArray<float>& aSelectorList)
{
MOZ_ASSERT(aSelectorList.IsEmpty(), "given list should start empty");
nsCSSScanner scanner(aSelectorString, aLineNumber);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
InitScanner(scanner, reporter, aURI, aURI, nullptr);
bool success = ParseKeyframeSelectorList(aSelectorList) &&
// must consume entire input string
!GetToken(true);
OUTPUT_ERROR();
ReleaseScanner();
if (success) {
NS_ASSERTION(!aSelectorList.IsEmpty(), "should not be empty");
} else {
aSelectorList.Clear();
}
return success;
}
bool
CSSParserImpl::EvaluateSupportsDeclaration(const nsAString& aProperty,
const nsAString& aValue,
nsIURI* aDocURL,
nsIURI* aBaseURL,
nsIPrincipal* aDocPrincipal)
{
nsCSSPropertyID propID = LookupEnabledProperty(aProperty);
if (propID == eCSSProperty_UNKNOWN) {
return false;
}
nsCSSScanner scanner(aValue, 0);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL);
InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
nsAutoSuppressErrors suppressErrors(this);
bool parsedOK;
if (propID == eCSSPropertyExtra_variable) {
MOZ_ASSERT(Substring(aProperty, 0,
CSS_CUSTOM_NAME_PREFIX_LENGTH).EqualsLiteral("--"));
const nsDependentSubstring varName =
Substring(aProperty, CSS_CUSTOM_NAME_PREFIX_LENGTH); // remove '--'
CSSVariableDeclarations::Type variableType;
nsString variableValue;
parsedOK = ParseVariableDeclaration(&variableType, variableValue) &&
!GetToken(true);
} else {
parsedOK = ParseProperty(propID) && !GetToken(true);
mTempData.ClearProperty(propID);
mTempData.AssertInitialState();
}
CLEAR_ERROR();
ReleaseScanner();
return parsedOK;
}
bool
CSSParserImpl::EvaluateSupportsCondition(const nsAString& aDeclaration,
nsIURI* aDocURL,
nsIURI* aBaseURL,
nsIPrincipal* aDocPrincipal,
SupportsParsingSettings aSettings)
{
nsCSSScanner scanner(aDeclaration, 0);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL);
InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
nsAutoSuppressErrors suppressErrors(this);
bool conditionMet;
bool parsedOK;
if (aSettings == SupportsParsingSettings::ImpliedParentheses) {
parsedOK = ParseSupportsConditionInParensInsideParens(conditionMet) && !GetToken(true);
} else {
parsedOK = ParseSupportsCondition(conditionMet) && !GetToken(true);
}
CLEAR_ERROR();
ReleaseScanner();
return parsedOK && conditionMet;
}
bool
CSSParserImpl::EnumerateVariableReferences(const nsAString& aPropertyValue,
VariableEnumFunc aFunc,
void* aData)
{
nsCSSScanner scanner(aPropertyValue, 0);
css::ErrorReporter reporter(scanner, nullptr, nullptr, nullptr);
InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
nsAutoSuppressErrors suppressErrors(this);
CSSVariableDeclarations::Type type;
bool dropBackslash;
nsString impliedCharacters;
bool result = ParseValueWithVariables(&type, &dropBackslash,
impliedCharacters, aFunc, aData) &&
!GetToken(true);
ReleaseScanner();
return result;
}
static bool
SeparatorRequiredBetweenTokens(nsCSSTokenSerializationType aToken1,
nsCSSTokenSerializationType aToken2)
{
// The two lines marked with (*) do not correspond to entries in
// the table in the css-syntax spec but which we need to handle,
// as we treat them as whole tokens.
switch (aToken1) {
case eCSSTokenSerialization_Ident:
return aToken2 == eCSSTokenSerialization_Ident ||
aToken2 == eCSSTokenSerialization_Function ||
aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
aToken2 == eCSSTokenSerialization_Symbol_Minus ||
aToken2 == eCSSTokenSerialization_Number ||
aToken2 == eCSSTokenSerialization_Percentage ||
aToken2 == eCSSTokenSerialization_Dimension ||
aToken2 == eCSSTokenSerialization_URange ||
aToken2 == eCSSTokenSerialization_CDC ||
aToken2 == eCSSTokenSerialization_Symbol_OpenParen;
case eCSSTokenSerialization_AtKeyword_or_Hash:
case eCSSTokenSerialization_Dimension:
return aToken2 == eCSSTokenSerialization_Ident ||
aToken2 == eCSSTokenSerialization_Function ||
aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
aToken2 == eCSSTokenSerialization_Symbol_Minus ||
aToken2 == eCSSTokenSerialization_Number ||
aToken2 == eCSSTokenSerialization_Percentage ||
aToken2 == eCSSTokenSerialization_Dimension ||
aToken2 == eCSSTokenSerialization_URange ||
aToken2 == eCSSTokenSerialization_CDC;
case eCSSTokenSerialization_Symbol_Hash:
case eCSSTokenSerialization_Symbol_Minus:
return aToken2 == eCSSTokenSerialization_Ident ||
aToken2 == eCSSTokenSerialization_Function ||
aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
aToken2 == eCSSTokenSerialization_Symbol_Minus ||
aToken2 == eCSSTokenSerialization_Number ||
aToken2 == eCSSTokenSerialization_Percentage ||
aToken2 == eCSSTokenSerialization_Dimension ||
aToken2 == eCSSTokenSerialization_URange;
case eCSSTokenSerialization_Number:
return aToken2 == eCSSTokenSerialization_Ident ||
aToken2 == eCSSTokenSerialization_Function ||
aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
aToken2 == eCSSTokenSerialization_Number ||
aToken2 == eCSSTokenSerialization_Percentage ||
aToken2 == eCSSTokenSerialization_Dimension ||
aToken2 == eCSSTokenSerialization_URange;
case eCSSTokenSerialization_Symbol_At:
return aToken2 == eCSSTokenSerialization_Ident ||
aToken2 == eCSSTokenSerialization_Function ||
aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
aToken2 == eCSSTokenSerialization_Symbol_Minus ||
aToken2 == eCSSTokenSerialization_URange;
case eCSSTokenSerialization_URange:
return aToken2 == eCSSTokenSerialization_Ident ||
aToken2 == eCSSTokenSerialization_Function ||
aToken2 == eCSSTokenSerialization_Number ||
aToken2 == eCSSTokenSerialization_Percentage ||
aToken2 == eCSSTokenSerialization_Dimension ||
aToken2 == eCSSTokenSerialization_Symbol_Question;
case eCSSTokenSerialization_Symbol_Dot_or_Plus:
return aToken2 == eCSSTokenSerialization_Number ||
aToken2 == eCSSTokenSerialization_Percentage ||
aToken2 == eCSSTokenSerialization_Dimension;
case eCSSTokenSerialization_Symbol_Assorted:
case eCSSTokenSerialization_Symbol_Asterisk:
return aToken2 == eCSSTokenSerialization_Symbol_Equals;
case eCSSTokenSerialization_Symbol_Bar:
return aToken2 == eCSSTokenSerialization_Symbol_Equals ||
aToken2 == eCSSTokenSerialization_Symbol_Bar ||
aToken2 == eCSSTokenSerialization_DashMatch; // (*)
case eCSSTokenSerialization_Symbol_Slash:
return aToken2 == eCSSTokenSerialization_Symbol_Asterisk ||
aToken2 == eCSSTokenSerialization_ContainsMatch; // (*)
default:
MOZ_ASSERT(aToken1 == eCSSTokenSerialization_Nothing ||
aToken1 == eCSSTokenSerialization_Whitespace ||
aToken1 == eCSSTokenSerialization_Percentage ||
aToken1 == eCSSTokenSerialization_URL_or_BadURL ||
aToken1 == eCSSTokenSerialization_Function ||
aToken1 == eCSSTokenSerialization_CDC ||
aToken1 == eCSSTokenSerialization_Symbol_OpenParen ||
aToken1 == eCSSTokenSerialization_Symbol_Question ||
aToken1 == eCSSTokenSerialization_Symbol_Assorted ||
aToken1 == eCSSTokenSerialization_Symbol_Asterisk ||
aToken1 == eCSSTokenSerialization_Symbol_Equals ||
aToken1 == eCSSTokenSerialization_Symbol_Bar ||
aToken1 == eCSSTokenSerialization_Symbol_Slash ||
aToken1 == eCSSTokenSerialization_Other,
"unexpected nsCSSTokenSerializationType value");
return false;
}
}
/**
* Appends aValue to aResult, possibly inserting an empty CSS
* comment between the two to ensure that tokens from both strings
* remain separated.
*/
static void
AppendTokens(nsAString& aResult,
nsCSSTokenSerializationType& aResultFirstToken,
nsCSSTokenSerializationType& aResultLastToken,
nsCSSTokenSerializationType aValueFirstToken,
nsCSSTokenSerializationType aValueLastToken,
const nsAString& aValue)
{
if (SeparatorRequiredBetweenTokens(aResultLastToken, aValueFirstToken)) {
aResult.AppendLiteral("/**/");
}
aResult.Append(aValue);
if (aResultFirstToken == eCSSTokenSerialization_Nothing) {
aResultFirstToken = aValueFirstToken;
}
if (aValueLastToken != eCSSTokenSerialization_Nothing) {
aResultLastToken = aValueLastToken;
}
}
/**
* Stops the given scanner recording, and appends the recorded result
* to aResult, possibly inserting an empty CSS comment between the two to
* ensure that tokens from both strings remain separated.
*/
static void
StopRecordingAndAppendTokens(nsString& aResult,
nsCSSTokenSerializationType& aResultFirstToken,
nsCSSTokenSerializationType& aResultLastToken,
nsCSSTokenSerializationType aValueFirstToken,
nsCSSTokenSerializationType aValueLastToken,
nsCSSScanner* aScanner)
{
if (SeparatorRequiredBetweenTokens(aResultLastToken, aValueFirstToken)) {
aResult.AppendLiteral("/**/");
}
aScanner->StopRecording(aResult);
if (aResultFirstToken == eCSSTokenSerialization_Nothing) {
aResultFirstToken = aValueFirstToken;
}
if (aValueLastToken != eCSSTokenSerialization_Nothing) {
aResultLastToken = aValueLastToken;
}
}
bool
CSSParserImpl::ResolveValueWithVariableReferencesRec(
nsString& aResult,
nsCSSTokenSerializationType& aResultFirstToken,
nsCSSTokenSerializationType& aResultLastToken,
const CSSVariableValues* aVariables)
{
// This function assumes we are already recording, and will leave the scanner
// recording when it returns.
MOZ_ASSERT(mScanner->IsRecording());
MOZ_ASSERT(aResult.IsEmpty());
// Stack of closing characters for currently open constructs.
AutoTArray<char16_t, 16> stack;
// The resolved value for this ResolveValueWithVariableReferencesRec call.
nsString value;
// The length of the scanner's recording before the currently parsed token.
// This is used so that when we encounter a "var(" token, we can strip
// it off the end of the recording, regardless of how long the token was.
// (With escapes, it could be longer than four characters.)
uint32_t lengthBeforeVar = 0;
// Tracking the type of token that appears at the start and end of |value|
// and that appears at the start and end of the scanner recording. These are
// used to determine whether we need to insert "/**/" when pasting token
// streams together.
nsCSSTokenSerializationType valueFirstToken = eCSSTokenSerialization_Nothing,
valueLastToken = eCSSTokenSerialization_Nothing,
recFirstToken = eCSSTokenSerialization_Nothing,
recLastToken = eCSSTokenSerialization_Nothing;
#define UPDATE_RECORDING_TOKENS(type) \
if (recFirstToken == eCSSTokenSerialization_Nothing) { \
recFirstToken = type; \
} \
recLastToken = type;
while (GetToken(false)) {
switch (mToken.mType) {
case eCSSToken_Symbol: {
nsCSSTokenSerializationType type = eCSSTokenSerialization_Other;
if (mToken.mSymbol == '(') {
stack.AppendElement(')');
type = eCSSTokenSerialization_Symbol_OpenParen;
} else if (mToken.mSymbol == '[') {
stack.AppendElement(']');
} else if (mToken.mSymbol == '{') {
stack.AppendElement('}');
} else if (mToken.mSymbol == ';') {
if (stack.IsEmpty()) {
// A ';' that is at the top level of the value or at the top level
// of a variable reference's fallback is invalid.
return false;
}
} else if (mToken.mSymbol == '!') {
if (stack.IsEmpty()) {
// An '!' that is at the top level of the value or at the top level
// of a variable reference's fallback is invalid.
return false;
}
} else if (mToken.mSymbol == ')' &&
stack.IsEmpty()) {
// We're closing a "var(".
nsString finalTokens;
mScanner->StopRecording(finalTokens);
MOZ_ASSERT(finalTokens[finalTokens.Length() - 1] == ')');
finalTokens.Truncate(finalTokens.Length() - 1);
aResult.Append(value);
AppendTokens(aResult, valueFirstToken, valueLastToken,
recFirstToken, recLastToken, finalTokens);
mScanner->StartRecording();
UngetToken();
aResultFirstToken = valueFirstToken;
aResultLastToken = valueLastToken;
return true;
} else if (mToken.mSymbol == ')' ||
mToken.mSymbol == ']' ||
mToken.mSymbol == '}') {
if (stack.IsEmpty() ||
stack.LastElement() != mToken.mSymbol) {
// A mismatched closing bracket is invalid.
return false;
}
stack.TruncateLength(stack.Length() - 1);
} else if (mToken.mSymbol == '#') {
type = eCSSTokenSerialization_Symbol_Hash;
} else if (mToken.mSymbol == '@') {
type = eCSSTokenSerialization_Symbol_At;
} else if (mToken.mSymbol == '.' ||
mToken.mSymbol == '+') {
type = eCSSTokenSerialization_Symbol_Dot_or_Plus;
} else if (mToken.mSymbol == '-') {
type = eCSSTokenSerialization_Symbol_Minus;
} else if (mToken.mSymbol == '?') {
type = eCSSTokenSerialization_Symbol_Question;
} else if (mToken.mSymbol == '$' ||
mToken.mSymbol == '^' ||
mToken.mSymbol == '~') {
type = eCSSTokenSerialization_Symbol_Assorted;
} else if (mToken.mSymbol == '=') {
type = eCSSTokenSerialization_Symbol_Equals;
} else if (mToken.mSymbol == '|') {
type = eCSSTokenSerialization_Symbol_Bar;
} else if (mToken.mSymbol == '/') {
type = eCSSTokenSerialization_Symbol_Slash;
} else if (mToken.mSymbol == '*') {
type = eCSSTokenSerialization_Symbol_Asterisk;
}
UPDATE_RECORDING_TOKENS(type);
break;
}
case eCSSToken_Function:
if (mToken.mIdent.LowerCaseEqualsLiteral("var")) {
// Save the tokens before the "var(" to our resolved value.
nsString recording;
mScanner->StopRecording(recording);
recording.Truncate(lengthBeforeVar);
AppendTokens(value, valueFirstToken, valueLastToken,
recFirstToken, recLastToken, recording);
recFirstToken = eCSSTokenSerialization_Nothing;
recLastToken = eCSSTokenSerialization_Nothing;
if (!GetToken(true) ||
mToken.mType != eCSSToken_Ident ||
!nsCSSProps::IsCustomPropertyName(mToken.mIdent)) {
// "var(" must be followed by an identifier, and it must be a
// custom property name.
return false;
}
// Turn the custom property name into a variable name by removing the
// '--' prefix.
MOZ_ASSERT(Substring(mToken.mIdent, 0,
CSS_CUSTOM_NAME_PREFIX_LENGTH).
EqualsLiteral("--"));
nsDependentString variableName(mToken.mIdent,
CSS_CUSTOM_NAME_PREFIX_LENGTH);
// Get the value of the identified variable. Note that we
// check if the variable value is the empty string, as that means
// that the variable was invalid at computed value time due to
// unresolveable variable references or cycles.
nsString variableValue;
nsCSSTokenSerializationType varFirstToken, varLastToken;
bool valid = aVariables->Get(variableName, variableValue,
varFirstToken, varLastToken) &&
!variableValue.IsEmpty();
if (!GetToken(true) ||
mToken.IsSymbol(')')) {
mScanner->StartRecording();
if (!valid) {
// Invalid variable with no fallback.
return false;
}
// Valid variable with no fallback.
AppendTokens(value, valueFirstToken, valueLastToken,
varFirstToken, varLastToken, variableValue);
} else if (mToken.IsSymbol(',')) {
mScanner->StartRecording();
if (!GetToken(false) ||
mToken.IsSymbol(')')) {
// Comma must be followed by at least one fallback token.
return false;
}
UngetToken();
if (valid) {
// Valid variable with ignored fallback.
mScanner->StopRecording();
AppendTokens(value, valueFirstToken, valueLastToken,
varFirstToken, varLastToken, variableValue);
bool ok = SkipBalancedContentUntil(')');
mScanner->StartRecording();
if (!ok) {
return false;
}
} else {
nsString fallback;
if (!ResolveValueWithVariableReferencesRec(fallback,
varFirstToken,
varLastToken,
aVariables)) {
// Fallback value had invalid tokens or an invalid variable reference
// that itself had no fallback.
return false;
}
AppendTokens(value, valueFirstToken, valueLastToken,
varFirstToken, varLastToken, fallback);
// Now we're either at the pushed back ')' that finished the
// fallback or at EOF.
DebugOnly<bool> gotToken = GetToken(false);
MOZ_ASSERT(!gotToken || mToken.IsSymbol(')'));
}
} else {
// Expected ',' or ')' after the variable name.
mScanner->StartRecording();
return false;
}
} else {
stack.AppendElement(')');
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Function);
}
break;
case eCSSToken_Bad_String:
case eCSSToken_Bad_URL:
return false;
case eCSSToken_Whitespace:
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Whitespace);
break;
case eCSSToken_AtKeyword:
case eCSSToken_Hash:
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_AtKeyword_or_Hash);
break;
case eCSSToken_Number:
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Number);
break;
case eCSSToken_Dimension:
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Dimension);
break;
case eCSSToken_Ident:
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Ident);
break;
case eCSSToken_Percentage:
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Percentage);
break;
case eCSSToken_URange:
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_URange);
break;
case eCSSToken_URL:
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_URL_or_BadURL);
break;
case eCSSToken_HTMLComment:
if (mToken.mIdent[0] == '-') {
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_CDC);
} else {
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Other);
}
break;
case eCSSToken_Dashmatch:
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_DashMatch);
break;
case eCSSToken_Containsmatch:
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_ContainsMatch);
break;
default:
MOZ_FALLTHROUGH_ASSERT("unexpected token type");
case eCSSToken_ID:
case eCSSToken_String:
case eCSSToken_Includes:
case eCSSToken_Beginsmatch:
case eCSSToken_Endsmatch:
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Other);
break;
}
lengthBeforeVar = mScanner->RecordingLength();
}
#undef UPDATE_RECORDING_TOKENS
aResult.Append(value);
StopRecordingAndAppendTokens(aResult, valueFirstToken, valueLastToken,
recFirstToken, recLastToken, mScanner);
// Append any implicitly closed brackets.
if (!stack.IsEmpty()) {
do {
aResult.Append(stack.LastElement());
stack.TruncateLength(stack.Length() - 1);
} while (!stack.IsEmpty());
valueLastToken = eCSSTokenSerialization_Other;
}
mScanner->StartRecording();
aResultFirstToken = valueFirstToken;
aResultLastToken = valueLastToken;
return true;
}
bool
CSSParserImpl::ResolveValueWithVariableReferences(
const CSSVariableValues* aVariables,
nsString& aResult,
nsCSSTokenSerializationType& aFirstToken,
nsCSSTokenSerializationType& aLastToken)
{
aResult.Truncate(0);
// Start recording before we read the first token.
mScanner->StartRecording();
if (!GetToken(false)) {
// Value was empty since we reached EOF.
mScanner->StopRecording();
return false;
}
UngetToken();
nsString value;
nsCSSTokenSerializationType firstToken, lastToken;
bool ok = ResolveValueWithVariableReferencesRec(value, firstToken, lastToken, aVariables) &&
!GetToken(true);
mScanner->StopRecording();
if (ok) {
aResult = value;
aFirstToken = firstToken;
aLastToken = lastToken;
}
return ok;
}
bool
CSSParserImpl::ResolveVariableValue(const nsAString& aPropertyValue,
const CSSVariableValues* aVariables,
nsString& aResult,
nsCSSTokenSerializationType& aFirstToken,
nsCSSTokenSerializationType& aLastToken)
{
nsCSSScanner scanner(aPropertyValue, 0);
// At this point, we know that aPropertyValue is syntactically correct
// for a token stream that has variable references. We also won't be
// interpreting any of the stream as we parse it, apart from expanding
// var() references, so we don't need a base URL etc. or any useful
// error reporting.
css::ErrorReporter reporter(scanner, nullptr, nullptr, nullptr);
InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
bool valid = ResolveValueWithVariableReferences(aVariables, aResult,
aFirstToken, aLastToken);
ReleaseScanner();
return valid;
}
void
CSSParserImpl::ParsePropertyWithVariableReferences(
nsCSSPropertyID aPropertyID,
nsCSSPropertyID aShorthandPropertyID,
const nsAString& aValue,
const CSSVariableValues* aVariables,
nsRuleData* aRuleData,
nsIURI* aDocURL,
nsIURI* aBaseURL,
nsIPrincipal* aDocPrincipal,
CSSStyleSheet* aSheet,
uint32_t aLineNumber,
uint32_t aLineOffset)
{
mTempData.AssertInitialState();
bool valid;
nsString expandedValue;
// Resolve any variable references in the property value.
{
nsCSSScanner scanner(aValue, 0);
css::ErrorReporter reporter(scanner, aSheet, mChildLoader, aDocURL);
InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
nsCSSTokenSerializationType firstToken, lastToken;
valid = ResolveValueWithVariableReferences(aVariables, expandedValue,
firstToken, lastToken);
if (!valid) {
NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropertyID));
REPORT_UNEXPECTED(PEInvalidVariableReference);
REPORT_UNEXPECTED_P(PEValueParsingError, propName);
if (nsCSSProps::IsInherited(aPropertyID)) {
REPORT_UNEXPECTED(PEValueWithVariablesFallbackInherit);
} else {
REPORT_UNEXPECTED(PEValueWithVariablesFallbackInitial);
}
OUTPUT_ERROR_WITH_POSITION(aLineNumber, aLineOffset);
}
ReleaseScanner();
}
nsCSSPropertyID propertyToParse =
aShorthandPropertyID != eCSSProperty_UNKNOWN ? aShorthandPropertyID :
aPropertyID;
// Parse the property with that resolved value.
if (valid) {
nsCSSScanner scanner(expandedValue, 0);
css::ErrorReporter reporter(scanner, aSheet, mChildLoader, aDocURL);
InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
valid = ParseProperty(propertyToParse);
if (valid && GetToken(true)) {
REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
valid = false;
}
if (!valid) {
NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(
propertyToParse));
REPORT_UNEXPECTED_P_V(PEValueWithVariablesParsingErrorInValue,
propName, expandedValue);
if (nsCSSProps::IsInherited(aPropertyID)) {
REPORT_UNEXPECTED(PEValueWithVariablesFallbackInherit);
} else {
REPORT_UNEXPECTED(PEValueWithVariablesFallbackInitial);
}
OUTPUT_ERROR_WITH_POSITION(aLineNumber, aLineOffset);
}
ReleaseScanner();
}
// If the property could not be parsed with the resolved value, then we
// treat it as if the value were 'initial' or 'inherit', depending on whether
// the property is an inherited property.
if (!valid) {
nsCSSValue defaultValue;
if (nsCSSProps::IsInherited(aPropertyID)) {
defaultValue.SetInheritValue();
} else {
defaultValue.SetInitialValue();
}
mTempData.AddLonghandProperty(aPropertyID, defaultValue);
}
// Copy the property value into the rule data.
mTempData.MapRuleInfoInto(aPropertyID, aRuleData);
mTempData.ClearProperty(propertyToParse);
mTempData.AssertInitialState();
}
already_AddRefed<nsAtom>
CSSParserImpl::ParseCounterStyleName(const nsAString& aBuffer, nsIURI* aURL)
{
nsCSSScanner scanner(aBuffer, 0);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURL);
InitScanner(scanner, reporter, aURL, aURL, nullptr);
RefPtr<nsAtom> name = ParseCounterStyleName(true);
bool success = name && !GetToken(true);
OUTPUT_ERROR();
ReleaseScanner();
return success ? name.forget() : nullptr;
}
bool
CSSParserImpl::ParseCounterDescriptor(nsCSSCounterDesc aDescID,
const nsAString& aBuffer,
nsIURI* aSheetURL,
nsIURI* aBaseURL,
nsIPrincipal* aSheetPrincipal,
nsCSSValue& aValue)
{
nsCSSScanner scanner(aBuffer, 0);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURL);
InitScanner(scanner, reporter, aSheetURL, aBaseURL, aSheetPrincipal);
bool success = ParseCounterDescriptorValue(aDescID, aValue) &&
!GetToken(true);
OUTPUT_ERROR();
ReleaseScanner();
return success;
}
bool
CSSParserImpl::ParseFontFaceDescriptor(nsCSSFontDesc aDescID,
const nsAString& aBuffer,
nsIURI* aSheetURL,
nsIURI* aBaseURL,
nsIPrincipal* aSheetPrincipal,
nsCSSValue& aValue)
{
nsCSSScanner scanner(aBuffer, 0);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURL);
InitScanner(scanner, reporter, aSheetURL, aBaseURL, aSheetPrincipal);
bool success = ParseFontDescriptorValue(aDescID, aValue) &&
!GetToken(true);
OUTPUT_ERROR();
ReleaseScanner();
return success;
}
//----------------------------------------------------------------------
bool
CSSParserImpl::GetToken(bool aSkipWS)
{
if (mHavePushBack) {
mHavePushBack = false;
if (!aSkipWS || mToken.mType != eCSSToken_Whitespace) {
return true;
}
}
return mScanner->Next(mToken, aSkipWS ?
eCSSScannerExclude_WhitespaceAndComments :
eCSSScannerExclude_Comments);
}
void
CSSParserImpl::UngetToken()
{
NS_PRECONDITION(!mHavePushBack, "double pushback");
mHavePushBack = true;
}
bool
CSSParserImpl::GetNextTokenLocation(bool aSkipWS, uint32_t *linenum, uint32_t *colnum)
{
// Peek at next token so that mScanner updates line and column vals
if (!GetToken(aSkipWS)) {
return false;
}
UngetToken();
// The scanner uses one-indexing for line numbers but zero-indexing
// for column numbers.
*linenum = mScanner->GetLineNumber();
*colnum = 1 + mScanner->GetColumnNumber();
return true;
}
bool
CSSParserImpl::ExpectSymbol(char16_t aSymbol,
bool aSkipWS)
{
if (!GetToken(aSkipWS)) {
// CSS2.1 specifies that all "open constructs" are to be closed at
// EOF. It simplifies higher layers if we claim to have found an
// ), ], }, or ; if we encounter EOF while looking for one of them.
// Do still issue a diagnostic, to aid debugging.
if (aSymbol == ')' || aSymbol == ']' ||
aSymbol == '}' || aSymbol == ';') {
REPORT_UNEXPECTED_EOF_CHAR(aSymbol);
return true;
}
else
return false;
}
if (mToken.IsSymbol(aSymbol)) {
return true;
}
UngetToken();
return false;
}
// Checks to see if we're at the end of a property. If an error occurs during
// the check, does not signal a parse error.
bool
CSSParserImpl::CheckEndProperty()
{
if (!GetToken(true)) {
return true; // properties may end with eof
}
if ((eCSSToken_Symbol == mToken.mType) &&
((';' == mToken.mSymbol) ||
('!' == mToken.mSymbol) ||
('}' == mToken.mSymbol) ||
(')' == mToken.mSymbol))) {
// XXX need to verify that ! is only followed by "important [;|}]
// XXX this requires a multi-token pushback buffer
UngetToken();
return true;
}
UngetToken();
return false;
}
// Checks if we're at the end of a property, raising an error if we're not.
bool
CSSParserImpl::ExpectEndProperty()
{
if (CheckEndProperty())
return true;
// If we're here, we read something incorrect, so we should report it.
REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
return false;
}
// Parses the priority suffix on a property, which at present may be
// either '!important' or nothing.
CSSParserImpl::PriorityParsingStatus
CSSParserImpl::ParsePriority()
{
if (!GetToken(true)) {
return ePriority_None; // properties may end with EOF
}
if (!mToken.IsSymbol('!')) {
UngetToken();
return ePriority_None; // dunno what it is, but it's not a priority
}
if (!GetToken(true)) {
// EOF is not ok after !
REPORT_UNEXPECTED_EOF(PEImportantEOF);
return ePriority_Error;
}
if (mToken.mType != eCSSToken_Ident ||
!mToken.mIdent.LowerCaseEqualsLiteral("important")) {
REPORT_UNEXPECTED_TOKEN(PEExpectedImportant);
UngetToken();
return ePriority_Error;
}
return ePriority_Important;
}
nsAString*
CSSParserImpl::NextIdent()
{
// XXX Error reporting?
if (!GetToken(true)) {
return nullptr;
}
if (eCSSToken_Ident != mToken.mType) {
UngetToken();
return nullptr;
}
return &mToken.mIdent;
}
bool
CSSParserImpl::SkipAtRule(bool aInsideBlock)
{
for (;;) {
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PESkipAtRuleEOF2);
return false;
}
if (eCSSToken_Symbol == mToken.mType) {
char16_t symbol = mToken.mSymbol;
if (symbol == ';') {
break;
}
if (aInsideBlock && symbol == '}') {
// The closing } doesn't belong to us.
UngetToken();
break;
}
if (symbol == '{') {
SkipUntil('}');
break;
} else if (symbol == '(') {
SkipUntil(')');
} else if (symbol == '[') {
SkipUntil(']');
}
} else if (eCSSToken_Function == mToken.mType ||
eCSSToken_Bad_URL == mToken.mType) {
SkipUntil(')');
}
}
return true;
}
bool
CSSParserImpl::ParseAtRule(RuleAppendFunc aAppendFunc,
void* aData,
bool aInAtRule)
{
nsCSSSection newSection;
bool (CSSParserImpl::*parseFunc)(RuleAppendFunc, void*);
if ((mSection <= eCSSSection_Charset) &&
(mToken.mIdent.LowerCaseEqualsLiteral("charset"))) {
parseFunc = &CSSParserImpl::ParseCharsetRule;
newSection = eCSSSection_Import; // only one charset allowed
} else if ((mSection <= eCSSSection_Import) &&
mToken.mIdent.LowerCaseEqualsLiteral("import")) {
parseFunc = &CSSParserImpl::ParseImportRule;
newSection = eCSSSection_Import;
} else if ((mSection <= eCSSSection_NameSpace) &&
mToken.mIdent.LowerCaseEqualsLiteral("namespace")) {
parseFunc = &CSSParserImpl::ParseNameSpaceRule;
newSection = eCSSSection_NameSpace;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("media")) {
parseFunc = &CSSParserImpl::ParseMediaRule;
newSection = eCSSSection_General;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("-moz-document")) {
parseFunc = &CSSParserImpl::ParseMozDocumentRule;
newSection = eCSSSection_General;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("font-face")) {
parseFunc = &CSSParserImpl::ParseFontFaceRule;
newSection = eCSSSection_General;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("font-feature-values")) {
parseFunc = &CSSParserImpl::ParseFontFeatureValuesRule;
newSection = eCSSSection_General;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("page")) {
parseFunc = &CSSParserImpl::ParsePageRule;
newSection = eCSSSection_General;
} else if ((nsCSSProps::IsEnabled(eCSSPropertyAlias_MozAnimation,
EnabledState()) &&
mToken.mIdent.LowerCaseEqualsLiteral("-moz-keyframes")) ||
(nsCSSProps::IsEnabled(eCSSPropertyAlias_WebkitAnimation) &&
mToken.mIdent.LowerCaseEqualsLiteral("-webkit-keyframes")) ||
mToken.mIdent.LowerCaseEqualsLiteral("keyframes")) {
parseFunc = &CSSParserImpl::ParseKeyframesRule;
newSection = eCSSSection_General;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("supports")) {
parseFunc = &CSSParserImpl::ParseSupportsRule;
newSection = eCSSSection_General;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("counter-style")) {
parseFunc = &CSSParserImpl::ParseCounterStyleRule;
newSection = eCSSSection_General;
} else {
if (!NonMozillaVendorIdentifier(mToken.mIdent)) {
REPORT_UNEXPECTED_TOKEN(PEUnknownAtRule);
OUTPUT_ERROR();
}
// Skip over unsupported at rule, don't advance section
return SkipAtRule(aInAtRule);
}
// Inside of @-rules, only the rules that can occur anywhere
// are allowed.
bool unnestable = aInAtRule && newSection != eCSSSection_General;
if (unnestable) {
REPORT_UNEXPECTED_TOKEN(PEGroupRuleNestedAtRule);
}
if (unnestable || !(this->*parseFunc)(aAppendFunc, aData)) {
// Skip over invalid at rule, don't advance section
OUTPUT_ERROR();
return SkipAtRule(aInAtRule);
}
// Nested @-rules don't affect the top-level rule chain requirement
if (!aInAtRule) {
mSection = newSection;
}
return true;
}
bool
CSSParserImpl::ParseCharsetRule(RuleAppendFunc aAppendFunc,
void* aData)
{
uint32_t linenum, colnum;
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PECharsetRuleEOF);
return false;
}
if (eCSSToken_String != mToken.mType) {
UngetToken();
REPORT_UNEXPECTED_TOKEN(PECharsetRuleNotString);
return false;
}
nsAutoString charset = mToken.mIdent;
if (!ExpectSymbol(';', true)) {
return false;
}
// It's intentional that we don't create a rule object for @charset rules.
return true;
}
bool
CSSParserImpl::ParseURLOrString(nsString& aURL)
{
if (!GetToken(true)) {
return false;
}
if (eCSSToken_String == mToken.mType || eCSSToken_URL == mToken.mType) {
aURL = mToken.mIdent;
return true;
}
UngetToken();
return false;
}
bool
CSSParserImpl::ParseMediaQuery(eMediaQueryType aQueryType,
nsMediaQuery **aQuery,
bool *aHitStop)
{
*aQuery = nullptr;
*aHitStop = false;
bool inAtRule = aQueryType == eMediaQueryAtRule;
// Attempt to parse a single condition and stop
bool singleCondition = aQueryType == eMediaQuerySingleCondition;
// "If the comma-separated list is the empty list it is assumed to
// specify the media query 'all'." (css3-mediaqueries, section
// "Media Queries")
if (!GetToken(true)) {
*aHitStop = true;
// expected termination by EOF
if (!inAtRule)
return true;
// unexpected termination by EOF
REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
return true;
}
if (eCSSToken_Symbol == mToken.mType && inAtRule &&
(mToken.mSymbol == ';' || mToken.mSymbol == '{' || mToken.mSymbol == '}' )) {
*aHitStop = true;
UngetToken();
return true;
}
UngetToken();
nsMediaQuery* query = new nsMediaQuery;
*aQuery = query;
if (ExpectSymbol('(', true)) {
// we got an expression without a media type
UngetToken(); // so ParseMediaQueryExpression can handle it
query->SetType(nsGkAtoms::all);
query->SetTypeOmitted();
// Just parse the first expression here.
if (!ParseMediaQueryExpression(query)) {
OUTPUT_ERROR();
query->SetHadUnknownExpression();
}
} else if (singleCondition) {
// Since we are only trying to consume a single condition, which precludes
// media types and not/only, this should be the same as reaching immediate
// EOF (no condition to parse)
*aHitStop = true;
return true;
} else {
RefPtr<nsAtom> mediaType;
bool gotNotOrOnly = false;
for (;;) {
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
return false;
}
if (eCSSToken_Ident != mToken.mType) {
REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotIdent);
UngetToken();
return false;
}
// case insensitive from CSS - must be lower cased
nsContentUtils::ASCIIToLower(mToken.mIdent);
mediaType = NS_Atomize(mToken.mIdent);
if (!gotNotOrOnly && mediaType == nsGkAtoms::_not) {
gotNotOrOnly = true;
query->SetNegated();
} else if (!gotNotOrOnly && mediaType == nsGkAtoms::only) {
gotNotOrOnly = true;
query->SetHasOnly();
} else if (mediaType == nsGkAtoms::_not ||
mediaType == nsGkAtoms::only ||
mediaType == nsGkAtoms::_and ||
mediaType == nsGkAtoms::_or) {
REPORT_UNEXPECTED_TOKEN(PEGatherMediaReservedMediaType);
UngetToken();
return false;
} else {
// valid media type
break;
}
}
query->SetType(mediaType);
}
for (;;) {
if (!GetToken(true)) {
*aHitStop = true;
// expected termination by EOF
if (!inAtRule)
break;
// unexpected termination by EOF
REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
break;
}
if (eCSSToken_Symbol == mToken.mType && inAtRule &&
(mToken.mSymbol == ';' || mToken.mSymbol == '{' || mToken.mSymbol == '}')) {
*aHitStop = true;
UngetToken();
break;
}
if (!singleCondition &&
eCSSToken_Symbol == mToken.mType && mToken.mSymbol == ',') {
// Done with the expressions for this query
break;
}
if (eCSSToken_Ident != mToken.mType ||
!mToken.mIdent.LowerCaseEqualsLiteral("and")) {
if (singleCondition) {
// We have a condition at this point -- if we're not chained to other
// conditions with and/or, we're done.
UngetToken();
break;
} else {
REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotComma);
UngetToken();
return false;
}
}
if (!ParseMediaQueryExpression(query)) {
OUTPUT_ERROR();
query->SetHadUnknownExpression();
}
}
return true;
}
// Returns false only when there is a low-level error in the scanner
// (out-of-memory).
bool
CSSParserImpl::GatherMedia(nsMediaList* aMedia,
bool aInAtRule)
{
eMediaQueryType type = aInAtRule ? eMediaQueryAtRule : eMediaQueryNormal;
for (;;) {
nsAutoPtr<nsMediaQuery> query;
bool hitStop;
if (!ParseMediaQuery(type, getter_Transfers(query), &hitStop)) {
NS_ASSERTION(!hitStop, "should return true when hit stop");
OUTPUT_ERROR();
if (query) {
query->SetHadUnknownExpression();
}
if (aInAtRule) {
const char16_t stopChars[] =
{ char16_t(','), char16_t('{'), char16_t(';'), char16_t('}'), char16_t(0) };
SkipUntilOneOf(stopChars);
} else {
SkipUntil(',');
}
// Rely on SkipUntilOneOf leaving mToken around as the last token read.
if (mToken.mType == eCSSToken_Symbol && aInAtRule &&
(mToken.mSymbol == '{' || mToken.mSymbol == ';' || mToken.mSymbol == '}')) {
UngetToken();
hitStop = true;
}
}
if (query) {
aMedia->AppendQuery(query);
}
if (hitStop) {
break;
}
}
return true;
}
bool
CSSParserImpl::ParseMediaQueryExpression(nsMediaQuery* aQuery)
{
if (!ExpectSymbol('(', true)) {
REPORT_UNEXPECTED_TOKEN(PEMQExpectedExpressionStart);
return false;
}
if (! GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEMQExpressionEOF);
return false;
}
if (eCSSToken_Ident != mToken.mType) {
REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureName);
UngetToken();
SkipUntil(')');
return false;
}
nsMediaExpression *expr = aQuery->NewExpression();
// case insensitive from CSS - must be lower cased
nsContentUtils::ASCIIToLower(mToken.mIdent);
nsDependentString featureString(mToken.mIdent, 0);
uint8_t satisfiedReqFlags = 0;
if (EnabledState() & (CSSEnabledState::eInUASheets |
CSSEnabledState::eInChrome)) {
satisfiedReqFlags |= nsMediaFeature::eUserAgentAndChromeOnly;
}
// Strip off "-webkit-" prefix from featureString:
if (StylePrefs::sWebkitPrefixedAliasesEnabled &&
StringBeginsWith(featureString, NS_LITERAL_STRING("-webkit-"))) {
satisfiedReqFlags |= nsMediaFeature::eHasWebkitPrefix;
featureString.Rebind(featureString, 8);
}
if (StylePrefs::sWebkitDevicePixelRatioEnabled) {
satisfiedReqFlags |= nsMediaFeature::eWebkitDevicePixelRatioPrefEnabled;
}
// Strip off "min-"/"max-" prefix from featureString:
if (StringBeginsWith(featureString, NS_LITERAL_STRING("min-"))) {
expr->mRange = nsMediaExpression::eMin;
featureString.Rebind(featureString, 4);
} else if (StringBeginsWith(featureString, NS_LITERAL_STRING("max-"))) {
expr->mRange = nsMediaExpression::eMax;
featureString.Rebind(featureString, 4);
} else {
expr->mRange = nsMediaExpression::eEqual;
}
RefPtr<nsAtom> mediaFeatureAtom = NS_Atomize(featureString);
const nsMediaFeature *feature = nsMediaFeatures::features;
for (; feature->mName; ++feature) {
// See if name matches & all requirement flags are satisfied:
// (We check requirements by turning off all of the flags that have been
// satisfied, and then see if the result is 0.)
if (*(feature->mName) == mediaFeatureAtom &&
!(feature->mReqFlags & ~satisfiedReqFlags)) {
break;
}
}
if (!feature->mName ||
(expr->mRange != nsMediaExpression::eEqual &&
feature->mRangeType != nsMediaFeature::eMinMaxAllowed)) {
REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureName);
SkipUntil(')');
return false;
}
expr->mFeature = feature;
if (!GetToken(true) || mToken.IsSymbol(')')) {
// Query expressions for any feature can be given without a value.
// However, min/max prefixes are not allowed.
if (expr->mRange != nsMediaExpression::eEqual) {
REPORT_UNEXPECTED(PEMQNoMinMaxWithoutValue);
return false;
}
expr->mValue.Reset();
return true;
}
if (!mToken.IsSymbol(':')) {
REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureNameEnd);
UngetToken();
SkipUntil(')');
return false;
}
bool rv = false;
switch (feature->mValueType) {
case nsMediaFeature::eLength:
rv = ParseSingleTokenNonNegativeVariant(expr->mValue, VARIANT_LENGTH,
nullptr);
break;
case nsMediaFeature::eInteger:
case nsMediaFeature::eBoolInteger:
rv = ParseNonNegativeInteger(expr->mValue);
// Enforce extra restrictions for eBoolInteger
if (rv &&
feature->mValueType == nsMediaFeature::eBoolInteger &&
expr->mValue.GetIntValue() > 1)
rv = false;
break;
case nsMediaFeature::eFloat:
rv = ParseNonNegativeNumber(expr->mValue);
break;
case nsMediaFeature::eIntRatio:
{
// Two integers separated by '/', with optional whitespace on
// either side of the '/'.
RefPtr<nsCSSValue::Array> a = nsCSSValue::Array::Create(2);
expr->mValue.SetArrayValue(a, eCSSUnit_Array);
// We don't bother with ParseNonNegativeVariant since we have to
// check for != 0 as well; no need to worry about the UngetToken
// since we're throwing out up to the next ')' anyway.
rv = ParseSingleTokenVariant(a->Item(0), VARIANT_INTEGER, nullptr) &&
a->Item(0).GetIntValue() > 0 &&
ExpectSymbol('/', true) &&
ParseSingleTokenVariant(a->Item(1), VARIANT_INTEGER, nullptr) &&
a->Item(1).GetIntValue() > 0;
}
break;
case nsMediaFeature::eResolution:
rv = GetToken(true);
if (!rv)
break;
rv = mToken.mType == eCSSToken_Dimension && mToken.mNumber > 0.0f;
if (!rv) {
UngetToken();
break;
}
// No worries about whether unitless zero is allowed, since the
// value must be positive (and we checked that above).
NS_ASSERTION(!mToken.mIdent.IsEmpty(), "unit lied");
if (mToken.mIdent.LowerCaseEqualsLiteral("dpi")) {
expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Inch);
} else if (mToken.mIdent.LowerCaseEqualsLiteral("dppx")) {
expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Pixel);
} else if (mToken.mIdent.LowerCaseEqualsLiteral("dpcm")) {
expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Centimeter);
} else {
rv = false;
}
break;
case nsMediaFeature::eEnumerated:
rv = ParseSingleTokenVariant(expr->mValue, VARIANT_KEYWORD,
feature->mData.mKeywordTable);
break;
case nsMediaFeature::eIdent:
rv = ParseSingleTokenVariant(expr->mValue, VARIANT_IDENTIFIER, nullptr);
break;
}
if (!rv || !ExpectSymbol(')', true)) {
REPORT_UNEXPECTED(PEMQExpectedFeatureValue);
SkipUntil(')');
return false;
}
return true;
}
// Parse a CSS2 import rule: "@import STRING | URL [medium [, medium]]"
bool
CSSParserImpl::ParseImportRule(RuleAppendFunc aAppendFunc, void* aData)
{
RefPtr<nsMediaList> media = new nsMediaList();
uint32_t linenum, colnum;
nsAutoString url;
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
!ParseURLOrString(url)) {
REPORT_UNEXPECTED_TOKEN(PEImportNotURI);
return false;
}
if (!ExpectSymbol(';', true)) {
if (!GatherMedia(media, true) ||
!ExpectSymbol(';', true)) {
REPORT_UNEXPECTED_TOKEN(PEImportUnexpected);
// don't advance section, simply ignore invalid @import
return false;
}
// Safe to assert this, since we ensured that there is something
// other than the ';' coming after the @import's url() token.
NS_ASSERTION(media->Length() != 0, "media list must be nonempty");
}
ProcessImport(url, media, aAppendFunc, aData, linenum, colnum);
return true;
}
void
CSSParserImpl::ProcessImport(const nsString& aURLSpec,
nsMediaList* aMedia,
RuleAppendFunc aAppendFunc,
void* aData,
uint32_t aLineNumber,
uint32_t aColumnNumber)
{
RefPtr<css::ImportRule> rule = new css::ImportRule(aMedia, aURLSpec,
aLineNumber,
aColumnNumber);
(*aAppendFunc)(rule, aData);
// Diagnose bad URIs even if we don't have a child loader.
nsCOMPtr<nsIURI> url;
// Charset will be deduced from mBaseURI, which is more or less correct.
nsresult rv = NS_NewURI(getter_AddRefs(url), aURLSpec, nullptr, mBaseURI);
if (NS_FAILED(rv)) {
if (rv == NS_ERROR_MALFORMED_URI) {
// import url is bad
REPORT_UNEXPECTED_P(PEImportBadURI, aURLSpec);
OUTPUT_ERROR();
}
return;
}
if (mChildLoader) {
mChildLoader->LoadChildSheet(mSheet, url, aMedia, rule, mReusableSheets);
}
}
// Parse the {} part of an @media or @-moz-document rule.
bool
CSSParserImpl::ParseGroupRule(css::GroupRule* aRule,
RuleAppendFunc aAppendFunc,
void* aData)
{
// XXXbz this could use better error reporting throughout the method
if (!ExpectSymbol('{', true)) {
return false;
}
// push rule on stack, loop over children
PushGroup(aRule);
nsCSSSection holdSection = mSection;
mSection = eCSSSection_General;
for (;;) {
// Get next non-whitespace token
if (! GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEGroupRuleEOF2);
break;
}
if (mToken.IsSymbol('}')) { // done!
UngetToken();
break;
}
if (eCSSToken_AtKeyword == mToken.mType) {
// Parse for nested rules
ParseAtRule(aAppendFunc, aData, true);
continue;
}
UngetToken();
ParseRuleSet(AppendRuleToSheet, this, true);
}
PopGroup();
if (!ExpectSymbol('}', true)) {
mSection = holdSection;
return false;
}
(*aAppendFunc)(aRule, aData);
return true;
}
// Parse a CSS2 media rule: "@media medium [, medium] { ... }"
bool
CSSParserImpl::ParseMediaRule(RuleAppendFunc aAppendFunc, void* aData)
{
RefPtr<nsMediaList> media = new nsMediaList();
uint32_t linenum, colnum;
if (GetNextTokenLocation(true, &linenum, &colnum) &&
GatherMedia(media, true)) {
// XXXbz this could use better error reporting throughout the method
RefPtr<css::MediaRule> rule = new css::MediaRule(linenum, colnum);
// Append first, so when we do SetMedia() the rule
// knows what its stylesheet is.
if (ParseGroupRule(rule, aAppendFunc, aData)) {
rule->SetMedia(media);
return true;
}
}
return false;
}
// Parse a @-moz-document rule. This is like an @media rule, but instead
// of a medium it has a nonempty list of items where each item is either
// url(), url-prefix(), or domain().
bool
CSSParserImpl::ParseMozDocumentRule(RuleAppendFunc aAppendFunc, void* aData)
{
css::DocumentRule::URL *urls = nullptr;
css::DocumentRule::URL **next = &urls;
uint32_t linenum, colnum;
if (!GetNextTokenLocation(true, &linenum, &colnum)) {
return false;
}
do {
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEMozDocRuleEOF);
delete urls;
return false;
}
if (!(eCSSToken_URL == mToken.mType ||
(eCSSToken_Function == mToken.mType &&
(mToken.mIdent.LowerCaseEqualsLiteral("url-prefix") ||
mToken.mIdent.LowerCaseEqualsLiteral("domain") ||
mToken.mIdent.LowerCaseEqualsLiteral("regexp"))))) {
REPORT_UNEXPECTED_TOKEN(PEMozDocRuleBadFunc2);
UngetToken();
delete urls;
return false;
}
css::DocumentRule::URL *cur = *next = new css::DocumentRule::URL;
next = &cur->next;
if (mToken.mType == eCSSToken_URL) {
cur->func = URLMatchingFunction::eURL;
CopyUTF16toUTF8(mToken.mIdent, cur->url);
} else if (mToken.mIdent.LowerCaseEqualsLiteral("regexp")) {
// regexp() is different from url-prefix() and domain() (but
// probably the way they *should* have been* in that it requires a
// string argument, and doesn't try to behave like url().
cur->func = URLMatchingFunction::eRegExp;
GetToken(true);
// copy before we know it's valid (but before ExpectSymbol changes
// mToken.mIdent)
CopyUTF16toUTF8(mToken.mIdent, cur->url);
if (eCSSToken_String != mToken.mType || !ExpectSymbol(')', true)) {
REPORT_UNEXPECTED_TOKEN(PEMozDocRuleNotString);
SkipUntil(')');
delete urls;
return false;
}
} else {
if (mToken.mIdent.LowerCaseEqualsLiteral("url-prefix")) {
cur->func = URLMatchingFunction::eURLPrefix;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("domain")) {
cur->func = URLMatchingFunction::eDomain;
}
NS_ASSERTION(!mHavePushBack, "mustn't have pushback at this point");
mScanner->NextURL(mToken);
if (mToken.mType != eCSSToken_URL) {
REPORT_UNEXPECTED_TOKEN(PEMozDocRuleNotURI);
SkipUntil(')');
delete urls;
return false;
}
// We could try to make the URL (as long as it's not domain())
// canonical and absolute with NS_NewURI and GetSpec, but I'm
// inclined to think we shouldn't.
CopyUTF16toUTF8(mToken.mIdent, cur->url);
}
} while (ExpectSymbol(',', true));
RefPtr<css::DocumentRule> rule = new css::DocumentRule(linenum, colnum);
rule->SetURLs(urls);
return ParseGroupRule(rule, aAppendFunc, aData);
}
// Parse a CSS3 namespace rule: "@namespace [prefix] STRING | URL;"
bool
CSSParserImpl::ParseNameSpaceRule(RuleAppendFunc aAppendFunc, void* aData)
{
uint32_t linenum, colnum;
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEAtNSPrefixEOF);
return false;
}
nsAutoString prefix;
nsAutoString url;
if (eCSSToken_Ident == mToken.mType) {
prefix = mToken.mIdent;
// user-specified identifiers are case-sensitive (bug 416106)
} else {
UngetToken();
}
if (!ParseURLOrString(url) || !ExpectSymbol(';', true)) {
if (mHavePushBack) {
REPORT_UNEXPECTED_TOKEN(PEAtNSUnexpected);
} else {
REPORT_UNEXPECTED_EOF(PEAtNSURIEOF);
}
return false;
}
ProcessNameSpace(prefix, url, aAppendFunc, aData, linenum, colnum);
return true;
}
void
CSSParserImpl::ProcessNameSpace(const nsString& aPrefix,
const nsString& aURLSpec,
RuleAppendFunc aAppendFunc,
void* aData,
uint32_t aLineNumber,
uint32_t aColumnNumber)
{
RefPtr<nsAtom> prefix;
if (!aPrefix.IsEmpty()) {
prefix = NS_Atomize(aPrefix);
}
RefPtr<css::NameSpaceRule> rule = new css::NameSpaceRule(prefix, aURLSpec,
aLineNumber,
aColumnNumber);
(*aAppendFunc)(rule, aData);
// If this was the first namespace rule encountered, it will trigger
// creation of a namespace map.
if (!mNameSpaceMap) {
mNameSpaceMap = mSheet->GetNameSpaceMap();
}
}
// font-face-rule: '@font-face' '{' font-description '}'
// font-description: font-descriptor+
bool
CSSParserImpl::ParseFontFaceRule(RuleAppendFunc aAppendFunc, void* aData)
{
uint32_t linenum, colnum;
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
!ExpectSymbol('{', true)) {
REPORT_UNEXPECTED_TOKEN(PEBadFontBlockStart);
return false;
}
RefPtr<nsCSSFontFaceRule> rule(new nsCSSFontFaceRule(linenum, colnum));
for (;;) {
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEFontFaceEOF);
break;
}
if (mToken.IsSymbol('}')) { // done!
UngetToken();
break;
}
// ignore extra semicolons
if (mToken.IsSymbol(';'))
continue;
if (!ParseFontDescriptor(rule)) {
REPORT_UNEXPECTED(PEDeclSkipped);
OUTPUT_ERROR();
if (!SkipDeclaration(true))
break;
}
}
if (!ExpectSymbol('}', true)) {
REPORT_UNEXPECTED_TOKEN(PEBadFontBlockEnd);
return false;
}
(*aAppendFunc)(rule, aData);
return true;
}
// font-descriptor: font-family-desc
// | font-style-desc
// | font-weight-desc
// | font-stretch-desc
// | font-src-desc
// | unicode-range-desc
//
// All font-*-desc productions follow the pattern
// IDENT ':' value ';'
//
// On entry to this function, mToken is the IDENT.
bool
CSSParserImpl::ParseFontDescriptor(nsCSSFontFaceRule* aRule)
{
if (eCSSToken_Ident != mToken.mType) {
REPORT_UNEXPECTED_TOKEN(PEFontDescExpected);
return false;
}
nsString descName = mToken.mIdent;
if (!ExpectSymbol(':', true)) {
REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
OUTPUT_ERROR();
return false;
}
nsCSSFontDesc descID = nsCSSProps::LookupFontDesc(descName);
nsCSSValue value;
if (descID == eCSSFontDesc_UNKNOWN ||
(descID == eCSSFontDesc_Display && !StylePrefs::sFontDisplayEnabled)) {
if (NonMozillaVendorIdentifier(descName)) {
// silently skip other vendors' extensions
Unused << SkipDeclaration(true);
return true;
} else {
REPORT_UNEXPECTED_P(PEUnknownFontDesc, descName);
return false;
}
}
if (!ParseFontDescriptorValue(descID, value)) {
REPORT_UNEXPECTED_P(PEValueParsingError, descName);
return false;
}
// Expect termination by ;, }, or EOF.
if (GetToken(true)) {
if (!mToken.IsSymbol(';') &&
!mToken.IsSymbol('}')) {
UngetToken();
REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
return false;
}
UngetToken();
}
aRule->SetDesc(descID, value);
return true;
}
// @font-feature-values <font-family># {
// @<feature-type> {
// <feature-ident> : <feature-index>+;
// <feature-ident> : <feature-index>+;
// ...
// }
// ...
// }
bool
CSSParserImpl::ParseFontFeatureValuesRule(RuleAppendFunc aAppendFunc,
void* aData)
{
uint32_t linenum, colnum;
if (!GetNextTokenLocation(true, &linenum, &colnum)) {
return false;
}
RefPtr<nsCSSFontFeatureValuesRule>
valuesRule(new nsCSSFontFeatureValuesRule(linenum, colnum));
// parse family list
nsCSSValue fontlistValue;
if (!ParseFamily(fontlistValue) ||
fontlistValue.GetUnit() != eCSSUnit_FontFamilyList)
{
REPORT_UNEXPECTED_TOKEN(PEFFVNoFamily);
return false;
}
// add family to rule
SharedFontList* fontlist = fontlistValue.GetFontFamilyListValue();
// family list has generic ==> parse error
if (fontlist->HasGeneric()) {
REPORT_UNEXPECTED_TOKEN(PEFFVGenericInFamilyList);
return false;
}
valuesRule->SetFamilyList(fontlist);
// open brace
if (!ExpectSymbol('{', true)) {
REPORT_UNEXPECTED_TOKEN(PEFFVBlockStart);
return false;
}
// list of sets of feature values, each set bound to a specific
// feature-type (e.g. swash, annotation)
for (;;) {
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF);
break;
}
if (mToken.IsSymbol('}')) { // done!
UngetToken();
break;
}
if (!ParseFontFeatureValueSet(valuesRule)) {
if (!SkipAtRule(false)) {
break;
}
}
}
if (!ExpectSymbol('}', true)) {
REPORT_UNEXPECTED_TOKEN(PEFFVUnexpectedBlockEnd);
SkipUntil('}');
return false;
}
(*aAppendFunc)(valuesRule, aData);
return true;
}
#define NUMVALUES_NO_LIMIT 0xFFFF
// parse a single value set containing name-value pairs for a single feature type
// @<feature-type> { [ <feature-ident> : <feature-index>+ ; ]* }
// Ex: @swash { flowing: 1; delicate: 2; }
bool
CSSParserImpl::ParseFontFeatureValueSet(nsCSSFontFeatureValuesRule
*aFeatureValuesRule)
{
// -- @keyword (e.g. swash, styleset)
if (eCSSToken_AtKeyword != mToken.mType) {
REPORT_UNEXPECTED_TOKEN(PEFontFeatureValuesNoAt);
OUTPUT_ERROR();
UngetToken();
return false;
}
// which font-specific variant of font-variant-alternates
int32_t whichVariant;
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
if (!nsCSSProps::FindKeyword(keyword,
nsCSSProps::kFontVariantAlternatesFuncsKTable,
whichVariant))
{
if (!NonMozillaVendorIdentifier(mToken.mIdent)) {
REPORT_UNEXPECTED_TOKEN(PEFFVUnknownFontVariantPropValue);
OUTPUT_ERROR();
}
UngetToken();
return false;
}
nsAutoString featureType(mToken.mIdent);
// open brace
if (!ExpectSymbol('{', true)) {
REPORT_UNEXPECTED_TOKEN(PEFFVValueSetStart);
return false;
}
// styleset and character-variant can be multi-valued, otherwise single value
int32_t limitNumValues;
switch (keyword) {
case eCSSKeyword_styleset:
limitNumValues = NUMVALUES_NO_LIMIT;
break;
case eCSSKeyword_character_variant:
limitNumValues = 2;
break;
default:
limitNumValues = 1;
break;
}
// -- ident integer+ [, ident integer+]
AutoTArray<gfxFontFeatureValueSet::ValueList, 5> values;
// list of font-feature-values-declaration's
for (;;) {
nsAutoString valueId;
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF);
break;
}
// ignore extra semicolons
if (mToken.IsSymbol(';')) {
continue;
}
// close brace ==> done
if (mToken.IsSymbol('}')) {
break;
}
// ident
if (eCSSToken_Ident != mToken.mType) {
REPORT_UNEXPECTED_TOKEN(PEFFVExpectedIdent);
if (!SkipDeclaration(true)) {
break;
}
continue;
}
valueId.Assign(mToken.mIdent);
// colon
if (!ExpectSymbol(':', true)) {
REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
OUTPUT_ERROR();
if (!SkipDeclaration(true)) {
break;
}
continue;
}
// value list
AutoTArray<uint32_t,4> featureSelectors;
nsCSSValue intValue;
while (ParseNonNegativeInteger(intValue)) {
featureSelectors.AppendElement(uint32_t(intValue.GetIntValue()));
}
int32_t numValues = featureSelectors.Length();
if (numValues == 0) {
REPORT_UNEXPECTED_TOKEN(PEFFVExpectedValue);
OUTPUT_ERROR();
if (!SkipDeclaration(true)) {
break;
}
continue;
}
if (numValues > limitNumValues) {
REPORT_UNEXPECTED_P(PEFFVTooManyValues, featureType);
OUTPUT_ERROR();
if (!SkipDeclaration(true)) {
break;
}
continue;
}
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF);
gfxFontFeatureValueSet::ValueList v(valueId, featureSelectors);
values.AppendElement(v);
break;
}
// ';' or '}' to end definition
if (!mToken.IsSymbol(';') && !mToken.IsSymbol('}')) {
REPORT_UNEXPECTED_TOKEN(PEFFVValueDefinitionTrailing);
OUTPUT_ERROR();
if (!SkipDeclaration(true)) {
break;
}
continue;
}
gfxFontFeatureValueSet::ValueList v(valueId, featureSelectors);
values.AppendElement(v);
if (mToken.IsSymbol('}')) {
break;
}
}
aFeatureValuesRule->AddValueList(whichVariant, values);
return true;
}
bool
CSSParserImpl::ParseKeyframesRule(RuleAppendFunc aAppendFunc, void* aData)
{
uint32_t linenum, colnum;
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEKeyframeNameEOF);
return false;
}
if (mToken.mType != eCSSToken_Ident && mToken.mType != eCSSToken_String) {
REPORT_UNEXPECTED_TOKEN(PEKeyframeBadName);
UngetToken();
return false;
}
if (mToken.mType == eCSSToken_Ident) {
// Check for keywords that are not allowed as custom-ident for the
// keyframes-name: standard CSS-wide keywords, plus 'none'.
static const nsCSSKeyword excludedKeywords[] = {
eCSSKeyword_none,
eCSSKeyword_UNKNOWN
};
nsCSSValue value;
if (!ParseCustomIdent(value, mToken.mIdent, excludedKeywords)) {
REPORT_UNEXPECTED_TOKEN(PEKeyframeBadName);
UngetToken();
return false;
}
}
nsString name(mToken.mIdent);
if (!ExpectSymbol('{', true)) {
REPORT_UNEXPECTED_TOKEN(PEKeyframeBrace);
return false;
}
RefPtr<nsCSSKeyframesRule> rule =
new nsCSSKeyframesRule(NS_Atomize(name), linenum, colnum);
while (!ExpectSymbol('}', true)) {
RefPtr<nsCSSKeyframeRule> kid = ParseKeyframeRule();
if (kid) {
rule->AppendStyleRule(kid);
} else {
OUTPUT_ERROR();
SkipRuleSet(true);
}
}
(*aAppendFunc)(rule, aData);
return true;
}
bool
CSSParserImpl::ParsePageRule(RuleAppendFunc aAppendFunc, void* aData)
{
uint32_t linenum, colnum;
if (!GetNextTokenLocation(true, &linenum, &colnum)) {
return false;
}
// TODO: There can be page selectors after @page such as ":first", ":left".
uint32_t parseFlags = eParseDeclaration_InBraces |
eParseDeclaration_AllowImportant;
// Forbid viewport units in @page rules. See bug 811391.
MOZ_ASSERT(mViewportUnitsEnabled,
"Viewport units should be enabled outside of @page rules.");
mViewportUnitsEnabled = false;
RefPtr<css::Declaration> declaration =
ParseDeclarationBlock(parseFlags, eCSSContext_Page);
mViewportUnitsEnabled = true;
if (!declaration) {
return false;
}
RefPtr<nsCSSPageRule> rule =
new nsCSSPageRule(declaration, linenum, colnum);
(*aAppendFunc)(rule, aData);
return true;
}
already_AddRefed<nsCSSKeyframeRule>
CSSParserImpl::ParseKeyframeRule()
{
InfallibleTArray<float> selectorList;
uint32_t linenum, colnum;
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
!ParseKeyframeSelectorList(selectorList)) {
REPORT_UNEXPECTED(PEBadSelectorKeyframeRuleIgnored);
return nullptr;
}
// Ignore !important in keyframe rules
uint32_t parseFlags = eParseDeclaration_InBraces;
RefPtr<css::Declaration> declaration(ParseDeclarationBlock(parseFlags));
if (!declaration) {
return nullptr;
}
// Takes ownership of declaration
RefPtr<nsCSSKeyframeRule> rule =
new nsCSSKeyframeRule(Move(selectorList), declaration.forget(),
linenum, colnum);
return rule.forget();
}
bool
CSSParserImpl::ParseKeyframeSelectorList(InfallibleTArray<float>& aSelectorList)
{
for (;;) {
if (!GetToken(true)) {
// The first time through the loop, this means we got an empty
// list. Otherwise, it means we have a trailing comma.
return false;
}
float value;
switch (mToken.mType) {
case eCSSToken_Percentage:
value = mToken.mNumber;
break;
case eCSSToken_Ident:
if (mToken.mIdent.LowerCaseEqualsLiteral("from")) {
value = 0.0f;
break;
}
if (mToken.mIdent.LowerCaseEqualsLiteral("to")) {
value = 1.0f;
break;
}
MOZ_FALLTHROUGH;
default:
UngetToken();
// The first time through the loop, this means we got an empty
// list. Otherwise, it means we have a trailing comma.
return false;
}
aSelectorList.AppendElement(value);
if (!ExpectSymbol(',', true)) {
return true;
}
}
}
// supports_rule
// : "@supports" supports_condition group_rule_body
// ;
bool
CSSParserImpl::ParseSupportsRule(RuleAppendFunc aAppendFunc, void* aProcessData)
{
bool conditionMet = false;
nsString condition;
mScanner->StartRecording();
uint32_t linenum, colnum;
if (!GetNextTokenLocation(true, &linenum, &colnum)) {
return false;
}
bool parsed = ParseSupportsCondition(conditionMet);
if (!parsed) {
mScanner->StopRecording();
return false;
}
if (!ExpectSymbol('{', true)) {
REPORT_UNEXPECTED_TOKEN(PESupportsGroupRuleStart);
mScanner->StopRecording();
return false;
}
UngetToken();
mScanner->StopRecording(condition);
// Remove the "{" that would follow the condition.
if (condition.Length() != 0) {
condition.Truncate(condition.Length() - 1);
}
// Remove spaces from the start and end of the recorded supports condition.
condition.Trim(" ", true, true, false);
// Record whether we are in a failing @supports, so that property parse
// errors don't get reported.
nsAutoFailingSupportsRule failing(this, conditionMet);
RefPtr<css::GroupRule> rule =
new mozilla::CSSSupportsRule(conditionMet, condition, linenum, colnum);
return ParseGroupRule(rule, aAppendFunc, aProcessData);
}
// supports_condition
// : supports_condition_in_parens supports_condition_terms
// | supports_condition_negation
// ;
bool
CSSParserImpl::ParseSupportsCondition(bool& aConditionMet)
{
nsAutoInSupportsCondition aisc(this);
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PESupportsConditionStartEOF2);
return false;
}
UngetToken();
mScanner->ClearSeenBadToken();
if (mToken.IsSymbol('(') ||
mToken.mType == eCSSToken_Function ||
mToken.mType == eCSSToken_URL ||
mToken.mType == eCSSToken_Bad_URL) {
bool result = ParseSupportsConditionInParens(aConditionMet) &&
ParseSupportsConditionTerms(aConditionMet) &&
!mScanner->SeenBadToken();
return result;
}
if (mToken.mType == eCSSToken_Ident &&
mToken.mIdent.LowerCaseEqualsLiteral("not")) {
bool result = ParseSupportsConditionNegation(aConditionMet) &&
!mScanner->SeenBadToken();
return result;
}
REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedStart);
return false;
}
// supports_condition_negation
// : 'not' supports_condition_in_parens
// ;
bool
CSSParserImpl::ParseSupportsConditionNegation(bool& aConditionMet)
{
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PESupportsConditionNotEOF);
return false;
}
if (mToken.mType != eCSSToken_Ident ||
!mToken.mIdent.LowerCaseEqualsLiteral("not")) {
REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedNot);
return false;
}
if (ParseSupportsConditionInParens(aConditionMet)) {
aConditionMet = !aConditionMet;
return true;
}
return false;
}
// supports_condition_in_parens
// : '(' S* supports_condition_in_parens_inside_parens ')' S*
// | supports_condition_pref
// | general_enclosed
// ;
bool
CSSParserImpl::ParseSupportsConditionInParens(bool& aConditionMet)
{
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PESupportsConditionInParensStartEOF);
return false;
}
if (mToken.mType == eCSSToken_URL) {
aConditionMet = false;
return true;
}
if (AgentRulesEnabled() &&
mToken.mType == eCSSToken_Function &&
mToken.mIdent.LowerCaseEqualsLiteral("-moz-bool-pref")) {
return ParseSupportsMozBoolPrefName(aConditionMet);
}
if (mToken.mType == eCSSToken_Function ||
mToken.mType == eCSSToken_Bad_URL) {
if (!SkipUntil(')')) {
REPORT_UNEXPECTED_EOF(PESupportsConditionInParensEOF);
return false;
}
aConditionMet = false;
return true;
}
if (!mToken.IsSymbol('(')) {
REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedOpenParenOrFunction);
UngetToken();
return false;
}
if (!ParseSupportsConditionInParensInsideParens(aConditionMet)) {
if (!SkipUntil(')')) {
REPORT_UNEXPECTED_EOF(PESupportsConditionInParensEOF);
return false;
}
aConditionMet = false;
return true;
}
if (!(ExpectSymbol(')', true))) {
SkipUntil(')');
aConditionMet = false;
return true;
}
return true;
}
// supports_condition_pref
// : '-moz-bool-pref(' bool_pref_name ')'
// ;
bool
CSSParserImpl::ParseSupportsMozBoolPrefName(bool& aConditionMet)
{
if (!GetToken(true)) {
return false;
}
if (mToken.mType != eCSSToken_String) {
SkipUntil(')');
return false;
}
aConditionMet = Preferences::GetBool(
NS_ConvertUTF16toUTF8(mToken.mIdent).get());
if (!ExpectSymbol(')', true)) {
SkipUntil(')');
return false;
}
return true;
}
// supports_condition_in_parens_inside_parens
// : core_declaration
// | supports_condition_negation
// | supports_condition_in_parens supports_condition_terms
// ;
bool
CSSParserImpl::ParseSupportsConditionInParensInsideParens(bool& aConditionMet)
{
if (!GetToken(true)) {
return false;
}
if (mToken.mType == eCSSToken_Ident) {
if (!mToken.mIdent.LowerCaseEqualsLiteral("not")) {
nsAutoString propertyName = mToken.mIdent;
if (!ExpectSymbol(':', true)) {
return false;
}
nsCSSPropertyID propID = LookupEnabledProperty(propertyName);
if (propID == eCSSProperty_UNKNOWN) {
if (ExpectSymbol(')', true)) {
UngetToken();
return false;
}
aConditionMet = false;
SkipUntil(')');
UngetToken();
} else if (propID == eCSSPropertyExtra_variable) {
if (ExpectSymbol(')', false)) {
UngetToken();
return false;
}
CSSVariableDeclarations::Type variableType;
nsString variableValue;
aConditionMet =
ParseVariableDeclaration(&variableType, variableValue) &&
ParsePriority() != ePriority_Error;
if (!aConditionMet) {
SkipUntil(')');
UngetToken();
}
} else {
if (ExpectSymbol(')', true)) {
UngetToken();
return false;
}
aConditionMet = ParseProperty(propID) &&
ParsePriority() != ePriority_Error;
if (!aConditionMet) {
SkipUntil(')');
UngetToken();
}
mTempData.ClearProperty(propID);
mTempData.AssertInitialState();
}
return true;
}
UngetToken();
return ParseSupportsConditionNegation(aConditionMet);
}
UngetToken();
return ParseSupportsConditionInParens(aConditionMet) &&
ParseSupportsConditionTerms(aConditionMet);
}
// supports_condition_terms
// : 'and' supports_condition_terms_after_operator('and')
// | 'or' supports_condition_terms_after_operator('or')
// |
// ;
bool
CSSParserImpl::ParseSupportsConditionTerms(bool& aConditionMet)
{
if (!GetToken(true)) {
return true;
}
if (mToken.mType != eCSSToken_Ident) {
UngetToken();
return true;
}
if (mToken.mIdent.LowerCaseEqualsLiteral("and")) {
return ParseSupportsConditionTermsAfterOperator(aConditionMet, eAnd);
}
if (mToken.mIdent.LowerCaseEqualsLiteral("or")) {
return ParseSupportsConditionTermsAfterOperator(aConditionMet, eOr);
}
UngetToken();
return true;
}
// supports_condition_terms_after_operator(operator)
// : supports_condition_in_parens ( <operator> supports_condition_in_parens )*
// ;
bool
CSSParserImpl::ParseSupportsConditionTermsAfterOperator(
bool& aConditionMet,
CSSParserImpl::SupportsConditionTermOperator aOperator)
{
const char* token = aOperator == eAnd ? "and" : "or";
for (;;) {
bool termConditionMet = false;
if (!ParseSupportsConditionInParens(termConditionMet)) {
return false;
}
aConditionMet = aOperator == eAnd ? aConditionMet && termConditionMet :
aConditionMet || termConditionMet;
if (!GetToken(true)) {
return true;
}
if (mToken.mType != eCSSToken_Ident ||
!mToken.mIdent.LowerCaseEqualsASCII(token)) {
UngetToken();
return true;
}
}
}
bool
CSSParserImpl::ParseCounterStyleRule(RuleAppendFunc aAppendFunc, void* aData)
{
RefPtr<nsAtom> name;
uint32_t linenum, colnum;
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
!(name = ParseCounterStyleName(true))) {
REPORT_UNEXPECTED_TOKEN(PECounterStyleNotIdent);
return false;
}
if (!ExpectSymbol('{', true)) {
REPORT_UNEXPECTED_TOKEN(PECounterStyleBadBlockStart);
return false;
}
RefPtr<nsCSSCounterStyleRule> rule = new nsCSSCounterStyleRule(name,
linenum,
colnum);
for (;;) {
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PECounterStyleEOF);
break;
}
if (mToken.IsSymbol('}')) {
break;
}
if (mToken.IsSymbol(';')) {
continue;
}
if (!ParseCounterDescriptor(rule)) {
REPORT_UNEXPECTED(PEDeclSkipped);
OUTPUT_ERROR();
if (!SkipDeclaration(true)) {
REPORT_UNEXPECTED_EOF(PECounterStyleEOF);
break;
}
}
}
int32_t system = rule->GetSystem();
bool isCorrect = false;
switch (system) {
case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC:
case NS_STYLE_COUNTER_SYSTEM_FIXED: {
// check whether symbols is set and the length is sufficient
const nsCSSValue& symbols = rule->GetDesc(eCSSCounterDesc_Symbols);
if (symbols.GetUnit() == eCSSUnit_List &&
nsCSSCounterStyleRule::CheckDescValue(
system, eCSSCounterDesc_Symbols, symbols)) {
isCorrect = true;
}
break;
}
case NS_STYLE_COUNTER_SYSTEM_ADDITIVE: {
// for additive system, additive-symbols must be set
const nsCSSValue& symbols =
rule->GetDesc(eCSSCounterDesc_AdditiveSymbols);
if (symbols.GetUnit() == eCSSUnit_PairList) {
isCorrect = true;
}
break;
}
case NS_STYLE_COUNTER_SYSTEM_EXTENDS: {
// for extends system, symbols & additive-symbols must not be set
const nsCSSValue& symbols = rule->GetDesc(eCSSCounterDesc_Symbols);
const nsCSSValue& additiveSymbols =
rule->GetDesc(eCSSCounterDesc_AdditiveSymbols);
if (symbols.GetUnit() == eCSSUnit_Null &&
additiveSymbols.GetUnit() == eCSSUnit_Null) {
isCorrect = true;
}
break;
}
default:
NS_NOTREACHED("unknown system");
}
if (isCorrect) {
(*aAppendFunc)(rule, aData);
}
return true;
}
already_AddRefed<nsAtom>
CSSParserImpl::ParseCounterStyleName(bool aForDefinition)
{
if (!GetToken(true)) {
return nullptr;
}
if (mToken.mType != eCSSToken_Ident) {
UngetToken();
return nullptr;
}
static const nsCSSKeyword kReservedNames[] = {
eCSSKeyword_none,
eCSSKeyword_decimal,
eCSSKeyword_disc,
eCSSKeyword_UNKNOWN
};
nsCSSValue value; // we don't actually care about the value
if (!ParseCustomIdent(value, mToken.mIdent,
aForDefinition ? kReservedNames : nullptr)) {
REPORT_UNEXPECTED_TOKEN(PECounterStyleBadName);
UngetToken();
return nullptr;
}
nsString name = mToken.mIdent;
if (nsCSSProps::IsPredefinedCounterStyle(name)) {
ToLowerCase(name);
}
return NS_Atomize(name);
}
bool
CSSParserImpl::ParseCounterStyleNameValue(nsCSSValue& aValue)
{
if (RefPtr<nsAtom> name = ParseCounterStyleName(false)) {
aValue.SetAtomIdentValue(name.forget());
return true;
}
return false;
}
bool
CSSParserImpl::ParseCounterDescriptor(nsCSSCounterStyleRule* aRule)
{
if (eCSSToken_Ident != mToken.mType) {
REPORT_UNEXPECTED_TOKEN(PECounterDescExpected);
return false;
}
nsString descName = mToken.mIdent;
if (!ExpectSymbol(':', true)) {
REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
OUTPUT_ERROR();
return false;
}
nsCSSCounterDesc descID = nsCSSProps::LookupCounterDesc(descName);
nsCSSValue value;
if (descID == eCSSCounterDesc_UNKNOWN) {
REPORT_UNEXPECTED_P(PEUnknownCounterDesc, descName);
return false;
}
if (!ParseCounterDescriptorValue(descID, value)) {
REPORT_UNEXPECTED_P(PEValueParsingError, descName);
return false;
}
if (!ExpectEndProperty()) {
return false;
}
aRule->SetDesc(descID, value);
return true;
}
bool
CSSParserImpl::ParseCounterDescriptorValue(nsCSSCounterDesc aDescID,
nsCSSValue& aValue)
{
// Should also include VARIANT_IMAGE, but it is not supported currently.
// See bug 1024179.
static const int32_t VARIANT_COUNTER_SYMBOL =
VARIANT_STRING | VARIANT_IDENTIFIER;
switch (aDescID) {
case eCSSCounterDesc_System: {
nsCSSValue system;
if (!ParseEnum(system, nsCSSProps::kCounterSystemKTable)) {
return false;
}
switch (system.GetIntValue()) {
case NS_STYLE_COUNTER_SYSTEM_FIXED: {
nsCSSValue start;
if (!ParseSingleTokenVariant(start, VARIANT_INTEGER, nullptr)) {
start.SetIntValue(1, eCSSUnit_Integer);
}
aValue.SetPairValue(system, start);
return true;
}
case NS_STYLE_COUNTER_SYSTEM_EXTENDS: {
nsCSSValue name;
if (!ParseCounterStyleNameValue(name)) {
REPORT_UNEXPECTED_TOKEN(PECounterExtendsNotIdent);
return false;
}
aValue.SetPairValue(system, name);
return true;
}
default:
aValue = system;
return true;
}
}
case eCSSCounterDesc_Negative: {
nsCSSValue first, second;
if (!ParseSingleTokenVariant(first, VARIANT_COUNTER_SYMBOL, nullptr)) {
return false;
}
if (!ParseSingleTokenVariant(second, VARIANT_COUNTER_SYMBOL, nullptr)) {
aValue = first;
} else {
aValue.SetPairValue(first, second);
}
return true;
}
case eCSSCounterDesc_Prefix:
case eCSSCounterDesc_Suffix:
return ParseSingleTokenVariant(aValue, VARIANT_COUNTER_SYMBOL, nullptr);
case eCSSCounterDesc_Range: {
if (ParseSingleTokenVariant(aValue, VARIANT_AUTO, nullptr)) {
return true;
}
nsCSSValuePairList* item = aValue.SetPairListValue();
for (;;) {
nsCSSValuePair pair;
if (!ParseCounterRange(pair)) {
return false;
}
item->mXValue = pair.mXValue;
item->mYValue = pair.mYValue;
if (!ExpectSymbol(',', true)) {
return true;
}
item->mNext = new nsCSSValuePairList;
item = item->mNext;
}
// should always return in the loop
}
case eCSSCounterDesc_Pad: {
nsCSSValue width, symbol;
bool hasWidth = ParseNonNegativeInteger(width);
if (!ParseSingleTokenVariant(symbol, VARIANT_COUNTER_SYMBOL, nullptr) ||
(!hasWidth && !ParseNonNegativeInteger(width))) {
return false;
}
aValue.SetPairValue(width, symbol);
return true;
}
case eCSSCounterDesc_Fallback:
return ParseCounterStyleNameValue(aValue);
case eCSSCounterDesc_Symbols: {
nsCSSValueList* item = nullptr;
for (;;) {
nsCSSValue value;
if (!ParseSingleTokenVariant(value, VARIANT_COUNTER_SYMBOL, nullptr)) {
return !!item;
}
if (!item) {
item = aValue.SetListValue();
} else {
item->mNext = new nsCSSValueList;
item = item->mNext;
}
item->mValue = value;
}
// should always return in the loop
}
case eCSSCounterDesc_AdditiveSymbols: {
nsCSSValuePairList* item = nullptr;
int32_t lastWeight = -1;
for (;;) {
nsCSSValue weight, symbol;
bool hasWeight = ParseNonNegativeInteger(weight);
if (!ParseSingleTokenVariant(symbol, VARIANT_COUNTER_SYMBOL, nullptr) ||
(!hasWeight && !ParseNonNegativeInteger(weight))) {
return false;
}
if (lastWeight != -1 && weight.GetIntValue() >= lastWeight) {
REPORT_UNEXPECTED(PECounterASWeight);
return false;
}
lastWeight = weight.GetIntValue();
if (!item) {
item = aValue.SetPairListValue();
} else {
item->mNext = new nsCSSValuePairList;
item = item->mNext;
}
item->mXValue = weight;
item->mYValue = symbol;
if (!ExpectSymbol(',', true)) {
return true;
}
}
// should always return in the loop
}
case eCSSCounterDesc_SpeakAs:
if (ParseSingleTokenVariant(aValue, VARIANT_AUTO | VARIANT_KEYWORD,
nsCSSProps::kCounterSpeakAsKTable)) {
if (aValue.GetUnit() == eCSSUnit_Enumerated &&
aValue.GetIntValue() == NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT) {
// Currently spell-out is not supported, so it is explicitly
// rejected here rather than parsed as a custom identifier.
// See bug 1024178.
return false;
}
return true;
}
return ParseCounterStyleNameValue(aValue);
default:
NS_NOTREACHED("unknown descriptor");
return false;
}
}
bool
CSSParserImpl::ParseCounterRange(nsCSSValuePair& aPair)
{
static const int32_t VARIANT_BOUND = VARIANT_INTEGER | VARIANT_KEYWORD;
nsCSSValue lower, upper;
if (!ParseSingleTokenVariant(lower, VARIANT_BOUND,
nsCSSProps::kCounterRangeKTable) ||
!ParseSingleTokenVariant(upper, VARIANT_BOUND,
nsCSSProps::kCounterRangeKTable)) {
return false;
}
if (lower.GetUnit() != eCSSUnit_Enumerated &&
upper.GetUnit() != eCSSUnit_Enumerated &&
lower.GetIntValue() > upper.GetIntValue()) {
return false;
}
aPair = nsCSSValuePair(lower, upper);
return true;
}
bool
CSSParserImpl::SkipUntil(char16_t aStopSymbol)
{
nsCSSToken* tk = &mToken;
AutoTArray<char16_t, 16> stack;
stack.AppendElement(aStopSymbol);
for (;;) {
if (!GetToken(true)) {
return false;
}
if (eCSSToken_Symbol == tk->mType) {
char16_t symbol = tk->mSymbol;
uint32_t stackTopIndex = stack.Length() - 1;
if (symbol == stack.ElementAt(stackTopIndex)) {
stack.RemoveElementAt(stackTopIndex);
if (stackTopIndex == 0) {
return true;
}
// Just handle out-of-memory by parsing incorrectly. It's
// highly unlikely we're dealing with a legitimate style sheet
// anyway.
} else if ('{' == symbol) {
stack.AppendElement('}');
} else if ('[' == symbol) {
stack.AppendElement(']');
} else if ('(' == symbol) {
stack.AppendElement(')');
}
} else if (eCSSToken_Function == tk->mType ||
eCSSToken_Bad_URL == tk->mType) {
stack.AppendElement(')');
}
}
}
bool
CSSParserImpl::SkipBalancedContentUntil(char16_t aStopSymbol)
{
nsCSSToken* tk = &mToken;
AutoTArray<char16_t, 16> stack;
stack.AppendElement(aStopSymbol);
for (;;) {
if (!GetToken(true)) {
return true;
}
if (eCSSToken_Symbol == tk->mType) {
char16_t symbol = tk->mSymbol;
uint32_t stackTopIndex = stack.Length() - 1;
if (symbol == stack.ElementAt(stackTopIndex)) {
stack.RemoveElementAt(stackTopIndex);
if (stackTopIndex == 0) {
return true;
}
// Just handle out-of-memory by parsing incorrectly. It's
// highly unlikely we're dealing with a legitimate style sheet
// anyway.
} else if ('{' == symbol) {
stack.AppendElement('}');
} else if ('[' == symbol) {
stack.AppendElement(']');
} else if ('(' == symbol) {
stack.AppendElement(')');
} else if (')' == symbol ||
']' == symbol ||
'}' == symbol) {
UngetToken();
return false;
}
} else if (eCSSToken_Function == tk->mType ||
eCSSToken_Bad_URL == tk->mType) {
stack.AppendElement(')');
}
}
}
void
CSSParserImpl::SkipUntilOneOf(const char16_t* aStopSymbolChars)
{
nsCSSToken* tk = &mToken;
nsDependentString stopSymbolChars(aStopSymbolChars);
for (;;) {
if (!GetToken(true)) {
break;
}
if (eCSSToken_Symbol == tk->mType) {
char16_t symbol = tk->mSymbol;
if (stopSymbolChars.FindChar(symbol) != -1) {
break;
} else if ('{' == symbol) {
SkipUntil('}');
} else if ('[' == symbol) {
SkipUntil(']');
} else if ('(' == symbol) {
SkipUntil(')');
}
} else if (eCSSToken_Function == tk->mType ||
eCSSToken_Bad_URL == tk->mType) {
SkipUntil(')');
}
}
}
void
CSSParserImpl::SkipUntilAllOf(const StopSymbolCharStack& aStopSymbolChars)
{
uint32_t i = aStopSymbolChars.Length();
while (i--) {
SkipUntil(aStopSymbolChars[i]);
}
}
bool
CSSParserImpl::SkipDeclaration(bool aCheckForBraces)
{
nsCSSToken* tk = &mToken;
for (;;) {
if (!GetToken(true)) {
if (aCheckForBraces) {
REPORT_UNEXPECTED_EOF(PESkipDeclBraceEOF);
}
return false;
}
if (eCSSToken_Symbol == tk->mType) {
char16_t symbol = tk->mSymbol;
if (';' == symbol) {
break;
}
if (aCheckForBraces) {
if ('}' == symbol) {
UngetToken();
break;
}
}
if ('{' == symbol) {
SkipUntil('}');
} else if ('(' == symbol) {
SkipUntil(')');
} else if ('[' == symbol) {
SkipUntil(']');
}
} else if (eCSSToken_Function == tk->mType ||
eCSSToken_Bad_URL == tk->mType) {
SkipUntil(')');
}
}
return true;
}
void
CSSParserImpl::SkipRuleSet(bool aInsideBraces)
{
nsCSSToken* tk = &mToken;
for (;;) {
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PESkipRSBraceEOF);
break;
}
if (eCSSToken_Symbol == tk->mType) {
char16_t symbol = tk->mSymbol;
if ('}' == symbol && aInsideBraces) {
// leave block closer for higher-level grammar to consume
UngetToken();
break;
} else if ('{' == symbol) {
SkipUntil('}');
break;
} else if ('(' == symbol) {
SkipUntil(')');
} else if ('[' == symbol) {
SkipUntil(']');
}
} else if (eCSSToken_Function == tk->mType ||
eCSSToken_Bad_URL == tk->mType) {
SkipUntil(')');
}
}
}
void
CSSParserImpl::PushGroup(css::GroupRule* aRule)
{
mGroupStack.AppendElement(aRule);
}
void
CSSParserImpl::PopGroup()
{
uint32_t count = mGroupStack.Length();
if (0 < count) {
mGroupStack.RemoveElementAt(count - 1);
}
}
void
CSSParserImpl::AppendRule(css::Rule* aRule)
{
uint32_t count = mGroupStack.Length();
if (0 < count) {
mGroupStack[count - 1]->AppendStyleRule(aRule);
}
else {
mSheet->AppendStyleRule(aRule);
}
}
bool
CSSParserImpl::ParseRuleSet(RuleAppendFunc aAppendFunc, void* aData,
bool aInsideBraces)
{
// First get the list of selectors for the rule
nsCSSSelectorList* slist = nullptr;
uint32_t linenum, colnum;
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
!ParseSelectorList(slist, char16_t('{'))) {
REPORT_UNEXPECTED(PEBadSelectorRSIgnored);
OUTPUT_ERROR();
SkipRuleSet(aInsideBraces);
return false;
}
NS_ASSERTION(nullptr != slist, "null selector list");
CLEAR_ERROR();
// Next parse the declaration block
uint32_t parseFlags = eParseDeclaration_InBraces |
eParseDeclaration_AllowImportant;
RefPtr<css::Declaration> declaration = ParseDeclarationBlock(parseFlags);
if (nullptr == declaration) {
delete slist;
return false;
}
#if 0
slist->Dump();
fputs("{\n", stdout);
declaration->List();
fputs("}\n", stdout);
#endif
// Translate the selector list and declaration block into style data
RefPtr<css::StyleRule> rule = new css::StyleRule(slist, declaration,
linenum, colnum);
(*aAppendFunc)(rule, aData);
return true;
}
bool
CSSParserImpl::ParseSelectorList(nsCSSSelectorList*& aListHead,
char16_t aStopChar)
{
nsCSSSelectorList* list = nullptr;
if (! ParseSelectorGroup(list)) {
// must have at least one selector group
aListHead = nullptr;
return false;
}
NS_ASSERTION(nullptr != list, "no selector list");
aListHead = list;
// After that there must either be a "," or a "{" (the latter if
// StopChar is nonzero)
nsCSSToken* tk = &mToken;
for (;;) {
if (! GetToken(true)) {
if (aStopChar == char16_t(0)) {
return true;
}
REPORT_UNEXPECTED_EOF(PESelectorListExtraEOF);
break;
}
if (eCSSToken_Symbol == tk->mType) {
if (',' == tk->mSymbol) {
nsCSSSelectorList* newList = nullptr;
// Another selector group must follow
if (! ParseSelectorGroup(newList)) {
break;
}
// add new list to the end of the selector list
list->mNext = newList;
list = newList;
continue;
} else if (aStopChar == tk->mSymbol && aStopChar != char16_t(0)) {
UngetToken();
return true;
}
}
REPORT_UNEXPECTED_TOKEN(PESelectorListExtra);
UngetToken();
break;
}
delete aListHead;
aListHead = nullptr;
return false;
}
static bool IsUniversalSelector(const nsCSSSelector& aSelector)
{
return bool((aSelector.mNameSpace == kNameSpaceID_Unknown) &&
(aSelector.mLowercaseTag == nullptr) &&
(aSelector.mIDList == nullptr) &&
(aSelector.mClassList == nullptr) &&
(aSelector.mAttrList == nullptr) &&
(aSelector.mNegations == nullptr) &&
(aSelector.mPseudoClassList == nullptr));
}
bool
CSSParserImpl::ParseSelectorGroup(nsCSSSelectorList*& aList)
{
char16_t combinator = 0;
nsAutoPtr<nsCSSSelectorList> list(new nsCSSSelectorList());
for (;;) {
if (!ParseSelector(list, combinator)) {
return false;
}
// Look for a combinator.
if (!GetToken(false)) {
break; // EOF ok here
}
combinator = char16_t(0);
if (mToken.mType == eCSSToken_Whitespace) {
if (!GetToken(true)) {
break; // EOF ok here
}
combinator = char16_t(' ');
}
if (mToken.mType != eCSSToken_Symbol) {
UngetToken(); // not a combinator
} else {
char16_t symbol = mToken.mSymbol;
if (symbol == '+' || symbol == '>' || symbol == '~') {
combinator = mToken.mSymbol;
} else {
UngetToken(); // not a combinator
if (symbol == ',' || symbol == '{' || symbol == ')') {
break; // end of selector group
}
}
}
if (!combinator) {
REPORT_UNEXPECTED_TOKEN(PESelectorListExtra);
return false;
}
}
aList = list.forget();
return true;
}
#define SEL_MASK_NSPACE 0x01
#define SEL_MASK_ELEM 0x02
#define SEL_MASK_ID 0x04
#define SEL_MASK_CLASS 0x08
#define SEL_MASK_ATTRIB 0x10
#define SEL_MASK_PCLASS 0x20
#define SEL_MASK_PELEM 0x40
//
// Parses an ID selector #name
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParseIDSelector(int32_t& aDataMask,
nsCSSSelector& aSelector)
{
NS_ASSERTION(!mToken.mIdent.IsEmpty(),
"Empty mIdent in eCSSToken_ID token?");
aDataMask |= SEL_MASK_ID;
aSelector.AddID(mToken.mIdent);
return eSelectorParsingStatus_Continue;
}
//
// Parses a class selector .name
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParseClassSelector(int32_t& aDataMask,
nsCSSSelector& aSelector)
{
if (! GetToken(false)) { // get ident
REPORT_UNEXPECTED_EOF(PEClassSelEOF);
return eSelectorParsingStatus_Error;
}
if (eCSSToken_Ident != mToken.mType) { // malformed selector
REPORT_UNEXPECTED_TOKEN(PEClassSelNotIdent);
UngetToken();
return eSelectorParsingStatus_Error;
}
aDataMask |= SEL_MASK_CLASS;
aSelector.AddClass(mToken.mIdent);
return eSelectorParsingStatus_Continue;
}
//
// Parse a type element selector or a universal selector
// namespace|type or namespace|* or *|* or *
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParseTypeOrUniversalSelector(int32_t& aDataMask,
nsCSSSelector& aSelector,
bool aIsNegated)
{
nsAutoString buffer;
if (mToken.IsSymbol('*')) { // universal element selector, or universal namespace
if (ExpectSymbol('|', false)) { // was namespace
aDataMask |= SEL_MASK_NSPACE;
aSelector.SetNameSpace(kNameSpaceID_Unknown); // namespace wildcard
if (! GetToken(false)) {
REPORT_UNEXPECTED_EOF(PETypeSelEOF);
return eSelectorParsingStatus_Error;
}
if (eCSSToken_Ident == mToken.mType) { // element name
aDataMask |= SEL_MASK_ELEM;
aSelector.SetTag(mToken.mIdent);
}
else if (mToken.IsSymbol('*')) { // universal selector
aDataMask |= SEL_MASK_ELEM;
// don't set tag
}
else {
REPORT_UNEXPECTED_TOKEN(PETypeSelNotType);
UngetToken();
return eSelectorParsingStatus_Error;
}
}
else { // was universal element selector
SetDefaultNamespaceOnSelector(aSelector);
aDataMask |= SEL_MASK_ELEM;
// don't set any tag in the selector
}
if (! GetToken(false)) { // premature eof is ok (here!)
return eSelectorParsingStatus_Done;
}
}
else if (eCSSToken_Ident == mToken.mType) { // element name or namespace name
buffer = mToken.mIdent; // hang on to ident
if (ExpectSymbol('|', false)) { // was namespace
aDataMask |= SEL_MASK_NSPACE;
int32_t nameSpaceID = GetNamespaceIdForPrefix(buffer);
if (nameSpaceID == kNameSpaceID_Unknown) {
return eSelectorParsingStatus_Error;
}
aSelector.SetNameSpace(nameSpaceID);
if (! GetToken(false)) {
REPORT_UNEXPECTED_EOF(PETypeSelEOF);
return eSelectorParsingStatus_Error;
}
if (eCSSToken_Ident == mToken.mType) { // element name
aDataMask |= SEL_MASK_ELEM;
aSelector.SetTag(mToken.mIdent);
}
else if (mToken.IsSymbol('*')) { // universal selector
aDataMask |= SEL_MASK_ELEM;
// don't set tag
}
else {
REPORT_UNEXPECTED_TOKEN(PETypeSelNotType);
UngetToken();
return eSelectorParsingStatus_Error;
}
}
else { // was element name
SetDefaultNamespaceOnSelector(aSelector);
aSelector.SetTag(buffer);
aDataMask |= SEL_MASK_ELEM;
}
if (! GetToken(false)) { // premature eof is ok (here!)
return eSelectorParsingStatus_Done;
}
}
else if (mToken.IsSymbol('|')) { // No namespace
aDataMask |= SEL_MASK_NSPACE;
aSelector.SetNameSpace(kNameSpaceID_None); // explicit NO namespace
// get mandatory tag
if (! GetToken(false)) {
REPORT_UNEXPECTED_EOF(PETypeSelEOF);
return eSelectorParsingStatus_Error;
}
if (eCSSToken_Ident == mToken.mType) { // element name
aDataMask |= SEL_MASK_ELEM;
aSelector.SetTag(mToken.mIdent);
}
else if (mToken.IsSymbol('*')) { // universal selector
aDataMask |= SEL_MASK_ELEM;
// don't set tag
}
else {
REPORT_UNEXPECTED_TOKEN(PETypeSelNotType);
UngetToken();
return eSelectorParsingStatus_Error;
}
if (! GetToken(false)) { // premature eof is ok (here!)
return eSelectorParsingStatus_Done;
}
}
else {
SetDefaultNamespaceOnSelector(aSelector);
}
if (aIsNegated) {
// restore last token read in case of a negated type selector
UngetToken();
}
return eSelectorParsingStatus_Continue;
}
//
// Parse attribute selectors [attr], [attr=value], [attr|=value],
// [attr~=value], [attr^=value], [attr$=value] and [attr*=value]
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParseAttributeSelector(int32_t& aDataMask,
nsCSSSelector& aSelector)
{
if (! GetToken(true)) { // premature EOF
REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
return eSelectorParsingStatus_Error;
}
int32_t nameSpaceID = kNameSpaceID_None;
nsAutoString attr;
if (mToken.IsSymbol('*')) { // wildcard namespace
nameSpaceID = kNameSpaceID_Unknown;
if (ExpectSymbol('|', false)) {
if (! GetToken(false)) { // premature EOF
REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
return eSelectorParsingStatus_Error;
}
if (eCSSToken_Ident == mToken.mType) { // attr name
attr = mToken.mIdent;
}
else {
REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
UngetToken();
return eSelectorParsingStatus_Error;
}
}
else {
REPORT_UNEXPECTED_TOKEN(PEAttSelNoBar);
return eSelectorParsingStatus_Error;
}
}
else if (mToken.IsSymbol('|')) { // NO namespace
if (! GetToken(false)) { // premature EOF
REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
return eSelectorParsingStatus_Error;
}
if (eCSSToken_Ident == mToken.mType) { // attr name
attr = mToken.mIdent;
}
else {
REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
UngetToken();
return eSelectorParsingStatus_Error;
}
}
else if (eCSSToken_Ident == mToken.mType) { // attr name or namespace
attr = mToken.mIdent; // hang on to it
if (ExpectSymbol('|', false)) { // was a namespace
nameSpaceID = GetNamespaceIdForPrefix(attr);
if (nameSpaceID == kNameSpaceID_Unknown) {
return eSelectorParsingStatus_Error;
}
if (! GetToken(false)) { // premature EOF
REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
return eSelectorParsingStatus_Error;
}
if (eCSSToken_Ident == mToken.mType) { // attr name
attr = mToken.mIdent;
}
else {
REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
UngetToken();
return eSelectorParsingStatus_Error;
}
}
}
else { // malformed
REPORT_UNEXPECTED_TOKEN(PEAttributeNameOrNamespaceExpected);
UngetToken();
return eSelectorParsingStatus_Error;
}
bool gotEOF = false;
if (! GetToken(true)) { // premature EOF
// Treat this just like we saw a ']', but do still output the
// warning, similar to what ExpectSymbol does.
REPORT_UNEXPECTED_EOF(PEAttSelInnerEOF);
gotEOF = true;
}
if (gotEOF ||
(eCSSToken_Symbol == mToken.mType) ||
(eCSSToken_Includes == mToken.mType) ||
(eCSSToken_Dashmatch == mToken.mType) ||
(eCSSToken_Beginsmatch == mToken.mType) ||
(eCSSToken_Endsmatch == mToken.mType) ||
(eCSSToken_Containsmatch == mToken.mType)) {
uint8_t func;
// Important: Check the EOF/']' case first, since if gotEOF we
// don't want to be examining mToken.
if (gotEOF || ']' == mToken.mSymbol) {
aDataMask |= SEL_MASK_ATTRIB;
aSelector.AddAttribute(nameSpaceID, attr);
func = NS_ATTR_FUNC_SET;
}
else if (eCSSToken_Includes == mToken.mType) {
func = NS_ATTR_FUNC_INCLUDES;
}
else if (eCSSToken_Dashmatch == mToken.mType) {
func = NS_ATTR_FUNC_DASHMATCH;
}
else if (eCSSToken_Beginsmatch == mToken.mType) {
func = NS_ATTR_FUNC_BEGINSMATCH;
}
else if (eCSSToken_Endsmatch == mToken.mType) {
func = NS_ATTR_FUNC_ENDSMATCH;
}
else if (eCSSToken_Containsmatch == mToken.mType) {
func = NS_ATTR_FUNC_CONTAINSMATCH;
}
else if ('=' == mToken.mSymbol) {
func = NS_ATTR_FUNC_EQUALS;
}
else {
REPORT_UNEXPECTED_TOKEN(PEAttSelUnexpected);
UngetToken(); // bad function
return eSelectorParsingStatus_Error;
}
if (NS_ATTR_FUNC_SET != func) { // get value
if (! GetToken(true)) { // premature EOF
REPORT_UNEXPECTED_EOF(PEAttSelValueEOF);
return eSelectorParsingStatus_Error;
}
if ((eCSSToken_Ident == mToken.mType) || (eCSSToken_String == mToken.mType)) {
nsAutoString value(mToken.mIdent);
// Avoid duplicating the eof handling by just not checking for
// the 'i' annotation if we got eof.
typedef nsAttrSelector::ValueCaseSensitivity ValueCaseSensitivity;
ValueCaseSensitivity valueCaseSensitivity =
ValueCaseSensitivity::CaseSensitive;
bool eof = !GetToken(true);
if (!eof) {
if (eCSSToken_Ident == mToken.mType &&
mToken.mIdent.LowerCaseEqualsLiteral("i")) {
valueCaseSensitivity = ValueCaseSensitivity::CaseInsensitive;
eof = !GetToken(true);
}
}
bool gotClosingBracket;
if (eof) { // premature EOF
// Report a warning, but then treat it as a closing bracket.
REPORT_UNEXPECTED_EOF(PEAttSelCloseEOF);
gotClosingBracket = true;
} else {
gotClosingBracket = mToken.IsSymbol(']');
}
if (gotClosingBracket) {
// For cases when this style sheet is applied to an HTML
// element in an HTML document, and the attribute selector is
// for a non-namespaced attribute, then check to see if it's
// one of the known attributes whose VALUE is
// case-insensitive.
if (valueCaseSensitivity != ValueCaseSensitivity::CaseInsensitive &&
nameSpaceID == kNameSpaceID_None) {
static const char* caseInsensitiveHTMLAttribute[] = {
// list based on http://www.w3.org/TR/html4/
"lang",
"dir",
"http-equiv",
"text",
"link",
"vlink",
"alink",
"compact",
"align",
"frame",
"rules",
"valign",
"scope",
"axis",
"nowrap",
"hreflang",
"rel",
"rev",
"charset",
"codetype",
"declare",
"valuetype",
"shape",
"nohref",
"media",
"bgcolor",
"clear",
"color",
"face",
"noshade",
"noresize",
"scrolling",
"target",
"method",
"enctype",
"accept-charset",
"accept",
"checked",
"multiple",
"selected",
"disabled",
"readonly",
"language",
"defer",
"type",
// additional attributes not in HTML4
"direction", // marquee
nullptr
};
short i = 0;
const char* htmlAttr;
while ((htmlAttr = caseInsensitiveHTMLAttribute[i++])) {
if (attr.LowerCaseEqualsASCII(htmlAttr)) {
valueCaseSensitivity = ValueCaseSensitivity::CaseInsensitiveInHTML;
break;
}
}
}
aDataMask |= SEL_MASK_ATTRIB;
aSelector.AddAttribute(nameSpaceID, attr, func, value,
valueCaseSensitivity);
}
else {
REPORT_UNEXPECTED_TOKEN(PEAttSelNoClose);
UngetToken();
return eSelectorParsingStatus_Error;
}
}
else {
REPORT_UNEXPECTED_TOKEN(PEAttSelBadValue);
UngetToken();
return eSelectorParsingStatus_Error;
}
}
}
else {
REPORT_UNEXPECTED_TOKEN(PEAttSelUnexpected);
UngetToken(); // bad dog, no biscut!
return eSelectorParsingStatus_Error;
}
return eSelectorParsingStatus_Continue;
}
//
// Parse pseudo-classes and pseudo-elements
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
nsCSSSelector& aSelector,
bool aIsNegated,
nsAtom** aPseudoElement,
nsAtomList** aPseudoElementArgs,
CSSPseudoElementType* aPseudoElementType)
{
NS_ASSERTION(aIsNegated || (aPseudoElement && aPseudoElementArgs),
"expected location to store pseudo element");
NS_ASSERTION(!aIsNegated || (!aPseudoElement && !aPseudoElementArgs),
"negated selectors shouldn't have a place to store "
"pseudo elements");
if (! GetToken(false)) { // premature eof
REPORT_UNEXPECTED_EOF(PEPseudoSelEOF);
return eSelectorParsingStatus_Error;
}
// First, find out whether we are parsing a CSS3 pseudo-element
bool parsingPseudoElement = false;
if (mToken.IsSymbol(':')) {
parsingPseudoElement = true;
if (! GetToken(false)) { // premature eof
REPORT_UNEXPECTED_EOF(PEPseudoSelEOF);
return eSelectorParsingStatus_Error;
}
}
// Do some sanity-checking on the token
if (eCSSToken_Ident != mToken.mType && eCSSToken_Function != mToken.mType) {
// malformed selector
REPORT_UNEXPECTED_TOKEN(PEPseudoSelBadName);
UngetToken();
return eSelectorParsingStatus_Error;
}
// OK, now we know we have an mIdent. Atomize it. All the atoms, for
// pseudo-classes as well as pseudo-elements, start with a single ':'.
nsAutoString buffer;
buffer.Append(char16_t(':'));
buffer.Append(mToken.mIdent);
nsContentUtils::ASCIIToLower(buffer);
RefPtr<nsAtom> pseudo = NS_Atomize(buffer);
// stash away some info about this pseudo so we only have to get it once.
bool isTreePseudo = false;
CSSEnabledState enabledState = EnabledState();
CSSPseudoElementType pseudoElementType =
nsCSSPseudoElements::GetPseudoType(pseudo, enabledState);
CSSPseudoClassType pseudoClassType =
nsCSSPseudoClasses::GetPseudoType(pseudo, enabledState);
bool pseudoClassIsUserAction =
nsCSSPseudoClasses::IsUserActionPseudoClass(pseudoClassType);
if (nsCSSAnonBoxes::IsNonElement(pseudo)) {
// Non-element anonymous boxes should not match any rule.
REPORT_UNEXPECTED_TOKEN(PEPseudoSelUnknown);
UngetToken();
return eSelectorParsingStatus_Error;
}
// We currently allow :-moz-placeholder and ::-moz-placeholder and
// ::placeholder. We have to be a bit stricter regarding the
// pseudo-element parsing rules.
if (pseudoElementType == CSSPseudoElementType::placeholder &&
pseudoClassType == CSSPseudoClassType::mozPlaceholder) {
if (parsingPseudoElement) {
pseudoClassType = CSSPseudoClassType::NotPseudo;
} else {
pseudoElementType = CSSPseudoElementType::NotPseudo;
}
}
#ifdef MOZ_XUL
isTreePseudo = (pseudoElementType == CSSPseudoElementType::XULTree);
// If a tree pseudo-element is using the function syntax, it will
// get isTree set here and will pass the check below that only
// allows functions if they are in our list of things allowed to be
// functions. If it is _not_ using the function syntax, isTree will
// be false, and it will still pass that check. So the tree
// pseudo-elements are allowed to be either functions or not, as
// desired.
bool isTree = (eCSSToken_Function == mToken.mType) && isTreePseudo;
#endif
bool isPseudoElement = (pseudoElementType < CSSPseudoElementType::Count);
// anonymous boxes are only allowed if they're the tree boxes or we have
// enabled agent rules
bool isAnonBox = isTreePseudo ||
((pseudoElementType == CSSPseudoElementType::InheritingAnonBox ||
pseudoElementType == CSSPseudoElementType::NonInheritingAnonBox) &&
AgentRulesEnabled());
bool isPseudoClass =
(pseudoClassType != CSSPseudoClassType::NotPseudo);
NS_ASSERTION(!isPseudoClass ||
pseudoElementType == CSSPseudoElementType::NotPseudo,
"Why is this atom both a pseudo-class and a pseudo-element?");
NS_ASSERTION(isPseudoClass + isPseudoElement + isAnonBox <= 1,
"Shouldn't be more than one of these");
if (!isPseudoClass && !isPseudoElement && !isAnonBox) {
// Not a pseudo-class, not a pseudo-element.... forget it
REPORT_UNEXPECTED_TOKEN(PEPseudoSelUnknown);
UngetToken();
return eSelectorParsingStatus_Error;
}
// If it's a function token, it better be on our "ok" list, and if the name
// is that of a function pseudo it better be a function token
if ((eCSSToken_Function == mToken.mType) !=
(
#ifdef MOZ_XUL
isTree ||
#endif
CSSPseudoClassType::negation == pseudoClassType ||
nsCSSPseudoClasses::HasStringArg(pseudoClassType) ||
nsCSSPseudoClasses::HasNthPairArg(pseudoClassType) ||
nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType))) {
// There are no other function pseudos
REPORT_UNEXPECTED_TOKEN(PEPseudoSelNonFunc);
UngetToken();
return eSelectorParsingStatus_Error;
}
// If it starts with "::", it better be a pseudo-element
if (parsingPseudoElement &&
!isPseudoElement &&
!isAnonBox) {
REPORT_UNEXPECTED_TOKEN(PEPseudoSelNotPE);
UngetToken();
return eSelectorParsingStatus_Error;
}
if (aSelector.IsPseudoElement()) {
CSSPseudoElementType type = aSelector.PseudoType();
if (type >= CSSPseudoElementType::Count ||
!nsCSSPseudoElements::PseudoElementSupportsUserActionState(type)) {
// We only allow user action pseudo-classes on certain pseudo-elements.
REPORT_UNEXPECTED_TOKEN(PEPseudoSelNoUserActionPC);
UngetToken();
return eSelectorParsingStatus_Error;
}
if (!isPseudoClass || !pseudoClassIsUserAction) {
// CSS 4 Selectors says that pseudo-elements can only be followed by
// a user action pseudo-class.
REPORT_UNEXPECTED_TOKEN(PEPseudoClassNotUserAction);
UngetToken();
return eSelectorParsingStatus_Error;
}
}
if (!parsingPseudoElement &&
CSSPseudoClassType::negation == pseudoClassType) {
if (aIsNegated) { // :not() can't be itself negated
REPORT_UNEXPECTED_TOKEN(PEPseudoSelDoubleNot);
UngetToken();
return eSelectorParsingStatus_Error;
}
// CSS 3 Negation pseudo-class takes one simple selector as argument
nsSelectorParsingStatus parsingStatus =
ParseNegatedSimpleSelector(aDataMask, aSelector);
if (eSelectorParsingStatus_Continue != parsingStatus) {
return parsingStatus;
}
}
else if (!parsingPseudoElement && isPseudoClass) {
aDataMask |= SEL_MASK_PCLASS;
if (eCSSToken_Function == mToken.mType) {
nsSelectorParsingStatus parsingStatus;
if (nsCSSPseudoClasses::HasStringArg(pseudoClassType)) {
parsingStatus =
ParsePseudoClassWithIdentArg(aSelector, pseudoClassType);
}
else if (nsCSSPseudoClasses::HasNthPairArg(pseudoClassType)) {
parsingStatus =
ParsePseudoClassWithNthPairArg(aSelector, pseudoClassType);
}
else {
MOZ_ASSERT(nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType),
"unexpected pseudo with function token");
parsingStatus = ParsePseudoClassWithSelectorListArg(aSelector,
pseudoClassType);
}
if (eSelectorParsingStatus_Continue != parsingStatus) {
if (eSelectorParsingStatus_Error == parsingStatus) {
SkipUntil(')');
}
return parsingStatus;
}
}
else {
aSelector.AddPseudoClass(pseudoClassType);
}
}
else if (isPseudoElement || isAnonBox) {
// Pseudo-element. Make some more sanity checks.
if (aIsNegated) { // pseudo-elements can't be negated
REPORT_UNEXPECTED_TOKEN(PEPseudoSelPEInNot);
UngetToken();
return eSelectorParsingStatus_Error;
}
// CSS2 pseudo-elements and -moz-tree-* pseudo-elements are allowed
// to have a single ':' on them. Others (CSS3+ pseudo-elements and
// various -moz-* pseudo-elements) must have |parsingPseudoElement|
// set.
if (!parsingPseudoElement &&
!nsCSSPseudoElements::IsCSS2PseudoElement(pseudo)
#ifdef MOZ_XUL
&& !isTreePseudo
#endif
) {
REPORT_UNEXPECTED_TOKEN(PEPseudoSelNewStyleOnly);
UngetToken();
return eSelectorParsingStatus_Error;
}
if (0 == (aDataMask & SEL_MASK_PELEM)) {
aDataMask |= SEL_MASK_PELEM;
NS_ADDREF(*aPseudoElement = pseudo);
*aPseudoElementType = pseudoElementType;
#ifdef MOZ_XUL
if (isTree) {
// We have encountered a pseudoelement of the form
// -moz-tree-xxxx(a,b,c). We parse (a,b,c) and add each
// item in the list to the pseudoclass list. They will be pulled
// from the list later along with the pseudo-element.
if (!ParseTreePseudoElement(aPseudoElementArgs)) {
return eSelectorParsingStatus_Error;
}
}
#endif
// Pseudo-elements can only be followed by user action pseudo-classes
// or be the end of the selector. So the next non-whitespace token must
// be ':', '{' or ',' or EOF.
if (!GetToken(true)) { // premature eof is ok (here!)
return eSelectorParsingStatus_Done;
}
if (parsingPseudoElement && mToken.IsSymbol(':')) {
UngetToken();
return eSelectorParsingStatus_Continue;
}
if ((mToken.IsSymbol('{') || mToken.IsSymbol(','))) {
UngetToken();
return eSelectorParsingStatus_Done;
}
REPORT_UNEXPECTED_TOKEN(PEPseudoSelEndOrUserActionPC);
UngetToken();
return eSelectorParsingStatus_Error;
}
else { // multiple pseudo elements, not legal
REPORT_UNEXPECTED_TOKEN(PEPseudoSelMultiplePE);
UngetToken();
return eSelectorParsingStatus_Error;
}
}
#ifdef DEBUG
else {
// We should never end up here. Indeed, if we ended up here, we know (from
// the current if/else cascade) that !isPseudoElement and !isAnonBox. But
// then due to our earlier check we know that isPseudoClass. Since we
// didn't fall into the isPseudoClass case in this cascade, we must have
// parsingPseudoElement. But we've already checked the
// parsingPseudoElement && !isPseudoClass && !isAnonBox case and bailed if
// it's happened.
NS_NOTREACHED("How did this happen?");
}
#endif
return eSelectorParsingStatus_Continue;
}
//
// Parse the argument of a negation pseudo-class :not()
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParseNegatedSimpleSelector(int32_t& aDataMask,
nsCSSSelector& aSelector)
{
if (! GetToken(true)) { // premature eof
REPORT_UNEXPECTED_EOF(PENegationEOF);
return eSelectorParsingStatus_Error;
}
if (mToken.IsSymbol(')')) {
REPORT_UNEXPECTED_TOKEN(PENegationBadArg);
return eSelectorParsingStatus_Error;
}
// Create a new nsCSSSelector and add it to the end of
// aSelector.mNegations.
// Given the current parsing rules, every selector in mNegations
// contains only one simple selector (css3 definition) within it.
// This could easily change in future versions of CSS, and the only
// thing we need to change to support that is this parsing code and the
// serialization code for nsCSSSelector.
nsCSSSelector *newSel = new nsCSSSelector();
nsCSSSelector* negations = &aSelector;
while (negations->mNegations) {
negations = negations->mNegations;
}
negations->mNegations = newSel;
nsSelectorParsingStatus parsingStatus;
if (eCSSToken_ID == mToken.mType) { // #id
parsingStatus = ParseIDSelector(aDataMask, *newSel);
}
else if (mToken.IsSymbol('.')) { // .class
parsingStatus = ParseClassSelector(aDataMask, *newSel);
}
else if (mToken.IsSymbol(':')) { // :pseudo
parsingStatus = ParsePseudoSelector(aDataMask, *newSel, true,
nullptr, nullptr, nullptr);
}
else if (mToken.IsSymbol('[')) { // [attribute
parsingStatus = ParseAttributeSelector(aDataMask, *newSel);
if (eSelectorParsingStatus_Error == parsingStatus) {
// Skip forward to the matching ']'
SkipUntil(']');
}
}
else {
// then it should be a type element or universal selector
parsingStatus = ParseTypeOrUniversalSelector(aDataMask, *newSel, true);
}
if (eSelectorParsingStatus_Error == parsingStatus) {
REPORT_UNEXPECTED_TOKEN(PENegationBadInner);
SkipUntil(')');
return parsingStatus;
}
// close the parenthesis
if (!ExpectSymbol(')', true)) {
REPORT_UNEXPECTED_TOKEN(PENegationNoClose);
SkipUntil(')');
return eSelectorParsingStatus_Error;
}
NS_ASSERTION(newSel->mNameSpace == kNameSpaceID_Unknown ||
(!newSel->mIDList && !newSel->mClassList &&
!newSel->mPseudoClassList && !newSel->mAttrList),
"Need to fix the serialization code to deal with this");
return eSelectorParsingStatus_Continue;
}
//
// Parse the argument of a pseudo-class that has an ident arg
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector,
CSSPseudoClassType aType)
{
if (! GetToken(true)) { // premature eof
REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
return eSelectorParsingStatus_Error;
}
// We expect an identifier with a language abbreviation
if (eCSSToken_Ident != mToken.mType) {
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotIdent);
UngetToken();
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
}
// -moz-locale-dir and dir take an identifier argument. While
// only 'ltr' and 'rtl' (case-insensitively) will match anything, any
// other identifier is still valid.
if (aType == CSSPseudoClassType::mozLocaleDir ||
aType == CSSPseudoClassType::dir) {
nsContentUtils::ASCIIToLower(mToken.mIdent); // case insensitive
}
// Add the pseudo with the language parameter
aSelector.AddPseudoClass(aType, mToken.mIdent.get());
// close the parenthesis
if (!ExpectSymbol(')', true)) {
REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose);
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
}
return eSelectorParsingStatus_Continue;
}
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
CSSPseudoClassType aType)
{
int32_t numbers[2] = { 0, 0 };
bool lookForB = true;
bool onlyN = false;
bool hasSign = false;
int sign = 1;
// Follow the whitespace rules as proposed in
// http://lists.w3.org/Archives/Public/www-style/2008Mar/0121.html
if (! GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
return eSelectorParsingStatus_Error;
}
// A helper function that checks if the token starts with literal string
// |aStr| using a case-insensitive match.
auto TokenBeginsWith = [this] (const nsLiteralString& aStr) {
return StringBeginsWith(mToken.mIdent, aStr,
nsASCIICaseInsensitiveStringComparator());
};
if (mToken.IsSymbol('+')) {
// This can only be +n, since +an, -an, +a, -a will all
// parse a number as the first token, and -n is an ident token.
numbers[0] = 1;
onlyN = true;
// consume the `n`
// We do not allow whitespace here
// https://drafts.csswg.org/css-syntax-3/#the-anb-type
if (! GetToken(false)) {
REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
}
}
if (eCSSToken_Ident == mToken.mType || eCSSToken_Dimension == mToken.mType) {
// The CSS tokenization doesn't handle :nth-child() containing - well:
// 2n-1 is a dimension
// n-1 is an identifier
// The easiest way to deal with that is to push everything from the
// minus on back onto the scanner's pushback buffer.
uint32_t truncAt = 0;
if (TokenBeginsWith(NS_LITERAL_STRING("n-"))) {
truncAt = 1;
} else if (TokenBeginsWith(NS_LITERAL_STRING("-n-"))) {
truncAt = 2;
}
if (truncAt != 0) {
mScanner->Backup(mToken.mIdent.Length() - truncAt);
mToken.mIdent.Truncate(truncAt);
}
}
if (onlyN) {
// If we parsed a + or -, check that the truncated
// token is an "n"
if (eCSSToken_Ident != mToken.mType || !mToken.mIdent.LowerCaseEqualsLiteral("n")) {
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
return eSelectorParsingStatus_Error;
}
} else {
if (eCSSToken_Ident == mToken.mType) {
if (mToken.mIdent.LowerCaseEqualsLiteral("odd")) {
numbers[0] = 2;
numbers[1] = 1;
lookForB = false;
}
else if (mToken.mIdent.LowerCaseEqualsLiteral("even")) {
numbers[0] = 2;
numbers[1] = 0;
lookForB = false;
}
else if (mToken.mIdent.LowerCaseEqualsLiteral("n")) {
numbers[0] = 1;
}
else if (mToken.mIdent.LowerCaseEqualsLiteral("-n")) {
numbers[0] = -1;
}
else {
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
}
}
else if (eCSSToken_Number == mToken.mType) {
if (!mToken.mIntegerValid) {
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
}
numbers[1] = mToken.mInteger;
lookForB = false;
}
else if (eCSSToken_Dimension == mToken.mType) {
if (!mToken.mIntegerValid || !mToken.mIdent.LowerCaseEqualsLiteral("n")) {
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
}
numbers[0] = mToken.mInteger;
}
// XXX If it's a ')', is that valid? (as 0n+0)
else {
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
UngetToken();
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
}
}
if (! GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
return eSelectorParsingStatus_Error;
}
if (lookForB && !mToken.IsSymbol(')')) {
// The '+' or '-' sign can optionally be separated by whitespace.
// If it is separated by whitespace from what follows it, it appears
// as a separate token rather than part of the number token.
if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) {
hasSign = true;
if (mToken.IsSymbol('-')) {
sign = -1;
}
if (! GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
return eSelectorParsingStatus_Error;
}
}
if (eCSSToken_Number != mToken.mType ||
!mToken.mIntegerValid || mToken.mHasSign == hasSign) {
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
UngetToken();
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
}
numbers[1] = mToken.mInteger * sign;
if (! GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
return eSelectorParsingStatus_Error;
}
}
if (!mToken.IsSymbol(')')) {
REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose);
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
}
aSelector.AddPseudoClass(aType, numbers);
return eSelectorParsingStatus_Continue;
}
//
// Parse the argument of a pseudo-class that has a selector list argument.
// Such selector lists cannot contain combinators, but can contain
// anything that goes between a pair of combinators.
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
CSSPseudoClassType aType)
{
nsAutoPtr<nsCSSSelectorList> slist;
if (! ParseSelectorList(*getter_Transfers(slist), char16_t(')'))) {
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
}
// Check that none of the selectors in the list have combinators or
// pseudo-elements.
for (nsCSSSelectorList *l = slist; l; l = l->mNext) {
nsCSSSelector *s = l->mSelectors;
if (s->mNext || s->IsPseudoElement()) {
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
}
}
// Add the pseudo with the selector list parameter
aSelector.AddPseudoClass(aType, slist.forget());
// close the parenthesis
if (!ExpectSymbol(')', true)) {
REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose);
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
}
return eSelectorParsingStatus_Continue;
}
/**
* This is the format for selectors:
* operator? [[namespace |]? element_name]? [ ID | class | attrib | pseudo ]*
*/
bool
CSSParserImpl::ParseSelector(nsCSSSelectorList* aList,
char16_t aPrevCombinator)
{
if (! GetToken(true)) {
REPORT_UNEXPECTED_EOF(PESelectorEOF);
return false;
}
nsCSSSelector* selector = aList->AddSelector(aPrevCombinator);
RefPtr<nsAtom> pseudoElement;
nsAutoPtr<nsAtomList> pseudoElementArgs;
CSSPseudoElementType pseudoElementType = CSSPseudoElementType::NotPseudo;
int32_t dataMask = 0;
nsSelectorParsingStatus parsingStatus =
ParseTypeOrUniversalSelector(dataMask, *selector, false);
while (parsingStatus == eSelectorParsingStatus_Continue) {
if (mToken.IsSymbol(':')) { // :pseudo
parsingStatus = ParsePseudoSelector(dataMask, *selector, false,
getter_AddRefs(pseudoElement),
getter_Transfers(pseudoElementArgs),
&pseudoElementType);
if (pseudoElement &&
pseudoElementType != CSSPseudoElementType::InheritingAnonBox &&
pseudoElementType != CSSPseudoElementType::NonInheritingAnonBox) {
// Pseudo-elements other than anonymous boxes are represented with
// a special ':' combinator.
aList->mWeight += selector->CalcWeight();
selector = aList->AddSelector(':');
selector->mLowercaseTag.swap(pseudoElement);
selector->mClassList = pseudoElementArgs.forget();
selector->SetPseudoType(pseudoElementType);
}
} else if (selector->IsPseudoElement()) {
// Once we parsed a pseudo-element, we can only parse
// pseudo-classes (and only a limited set, which
// ParsePseudoSelector knows how to handle).
parsingStatus = eSelectorParsingStatus_Done;
UngetToken();
break;
} else if (eCSSToken_ID == mToken.mType) { // #id
parsingStatus = ParseIDSelector(dataMask, *selector);
} else if (mToken.IsSymbol('.')) { // .class
parsingStatus = ParseClassSelector(dataMask, *selector);
}
else if (mToken.IsSymbol('[')) { // [attribute
parsingStatus = ParseAttributeSelector(dataMask, *selector);
if (eSelectorParsingStatus_Error == parsingStatus) {
SkipUntil(']');
}
}
else { // not a selector token, we're done
parsingStatus = eSelectorParsingStatus_Done;
UngetToken();
break;
}
if (parsingStatus != eSelectorParsingStatus_Continue) {
break;
}
if (! GetToken(false)) { // premature eof is ok (here!)
parsingStatus = eSelectorParsingStatus_Done;
break;
}
}
if (parsingStatus == eSelectorParsingStatus_Error) {
return false;
}
if (!dataMask) {
if (selector->mNext) {
REPORT_UNEXPECTED(PESelectorGroupExtraCombinator);
} else {
REPORT_UNEXPECTED(PESelectorGroupNoSelector);
}
return false;
}
if (pseudoElementType == CSSPseudoElementType::InheritingAnonBox ||
pseudoElementType == CSSPseudoElementType::NonInheritingAnonBox) {
// We got an anonymous box pseudo-element; it must be the only
// thing in this selector group.
if (selector->mNext || !IsUniversalSelector(*selector)) {
REPORT_UNEXPECTED(PEAnonBoxNotAlone);
return false;
}
// Rewrite the current selector as this pseudo-element.
// It does not contribute to selector weight.
selector->mLowercaseTag.swap(pseudoElement);
selector->mClassList = pseudoElementArgs.forget();
selector->SetPseudoType(pseudoElementType);
return true;
}
aList->mWeight += selector->CalcWeight();
return true;
}
already_AddRefed<css::Declaration>
CSSParserImpl::ParseDeclarationBlock(uint32_t aFlags, nsCSSContextType aContext)
{
bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0;
MOZ_ASSERT(mWebkitBoxUnprefixState == eNotParsingDecls,
"Someone forgot to clear mWebkitBoxUnprefixState!");
AutoRestore<WebkitBoxUnprefixState> autoRestore(mWebkitBoxUnprefixState);
mWebkitBoxUnprefixState = eHaveNotUnprefixed;
if (checkForBraces) {
if (!ExpectSymbol('{', true)) {
REPORT_UNEXPECTED_TOKEN(PEBadDeclBlockStart);
OUTPUT_ERROR();
return nullptr;
}
}
RefPtr<css::Declaration> declaration = new css::Declaration();
mData.AssertInitialState();
for (;;) {
bool changed = false;
if (!ParseDeclaration(declaration, aFlags, true, &changed, aContext)) {
if (!SkipDeclaration(checkForBraces)) {
break;
}
if (checkForBraces) {
if (ExpectSymbol('}', true)) {
break;
}
}
// Since the skipped declaration didn't end the block we parse
// the next declaration.
}
}
declaration->CompressFrom(&mData);
return declaration.forget();
}
static Maybe<int32_t>
GetEnumColorValue(nsCSSKeyword aKeyword, bool aIsChrome)
{
int32_t value;
if (!nsCSSProps::FindKeyword(aKeyword, nsCSSProps::kColorKTable, value)) {
// Unknown color keyword.
return Nothing();
}
if (value < 0) {
// Known special color keyword handled by style system,
// e.g. NS_COLOR_CURRENTCOLOR. See nsStyleConsts.h.
return Some(value);
}
nscolor color;
auto colorID = static_cast<LookAndFeel::ColorID>(value);
if (NS_FAILED(LookAndFeel::GetColor(colorID, !aIsChrome, &color))) {
// Known LookAndFeel::ColorID, but this platform's LookAndFeel impl
// doesn't map it to a color. (This might be a platform-specific
// ColorID, which only makes sense on another platform.)
return Nothing();
}
// Known color provided by LookAndFeel.
return Some(value);
}
/// Returns the number of digits in a positive number
/// assuming it has <= 6 digits
static uint32_t
CountNumbersForHashlessColor(uint32_t number) {
/// Just use a simple match instead of calculating a log
/// or dividing in a loop to be more efficient.
if (number < 10) {
return 1;
} else if (number < 100) {
return 2;
} else if (number < 1000) {
return 3;
} else if (number < 10000) {
return 4;
} else if (number < 100000) {
return 5;
} else if (number < 1000000) {
return 6;
} else {
// we don't care about numbers with more than 6 digits other
// than the fact that they have more than 6 digits, so just return something
// larger than 6 here. This is incorrect in the general case.
return 100;
}
}
CSSParseResult
CSSParserImpl::ParseColor(nsCSSValue& aValue)
{
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEColorEOF);
return CSSParseResult::NotFound;
}
nsCSSToken* tk = &mToken;
nscolor rgba;
switch (tk->mType) {
case eCSSToken_ID:
case eCSSToken_Hash:
// #rgb, #rrggbb, #rgba, #rrggbbaa
if (NS_HexToRGBA(tk->mIdent, nsHexColorType::AllowAlpha, &rgba)) {
nsCSSUnit unit;
switch (tk->mIdent.Length()) {
case 3:
unit = eCSSUnit_ShortHexColor;
break;
case 4:
unit = eCSSUnit_ShortHexColorAlpha;
break;
case 6:
unit = eCSSUnit_HexColor;
break;
default:
MOZ_FALLTHROUGH_ASSERT("unexpected hex color length");
case 8:
unit = eCSSUnit_HexColorAlpha;
break;
}
aValue.SetIntegerColorValue(rgba, unit);
return CSSParseResult::Ok;
}
break;
case eCSSToken_Ident: {
if (NS_ColorNameToRGB(tk->mIdent, &rgba)) {
// Lowercase color name, since keyword values should be
// serialized in lowercase.
nsContentUtils::ASCIIToLower(tk->mIdent);
aValue.SetStringValue(tk->mIdent, eCSSUnit_Ident);
return CSSParseResult::Ok;
}
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(tk->mIdent);
if (Maybe<int32_t> value = GetEnumColorValue(keyword, mIsChrome)) {
aValue.SetIntValue(value.value(), eCSSUnit_EnumColor);
return CSSParseResult::Ok;
}
break;
}
case eCSSToken_Function: {
bool isRGB;
bool isHSL;
if ((isRGB = mToken.mIdent.LowerCaseEqualsLiteral("rgb")) ||
mToken.mIdent.LowerCaseEqualsLiteral("rgba")) {
// rgb() = rgb( <percentage>{3} [ / <alpha-value> ]? ) |
// rgb( <number>{3} [ / <alpha-value> ]? ) |
// rgb( <percentage>#{3} , <alpha-value>? ) |
// rgb( <number>#{3} , <alpha-value>? )
// <alpha-value> = <number> | <percentage>
// rgba is an alias of rgb.
if (GetToken(true)) {
UngetToken();
}
if (mToken.mType == eCSSToken_Number) { // <number>
uint8_t r, g, b, a;
if (ParseRGBColor(r, g, b, a)) {
aValue.SetIntegerColorValue(NS_RGBA(r, g, b, a),
isRGB ? eCSSUnit_RGBColor : eCSSUnit_RGBAColor);
return CSSParseResult::Ok;
}
} else { // <percentage>
float r, g, b, a;
if (ParseRGBColor(r, g, b, a)) {
aValue.SetFloatColorValue(r, g, b, a,
isRGB ? eCSSUnit_PercentageRGBColor : eCSSUnit_PercentageRGBAColor);
return CSSParseResult::Ok;
}
}
SkipUntil(')');
return CSSParseResult::Error;
}
else if ((isHSL = mToken.mIdent.LowerCaseEqualsLiteral("hsl")) ||
mToken.mIdent.LowerCaseEqualsLiteral("hsla")) {
// hsl() = hsl( <hue> <percentage> <percentage> [ / <alpha-value> ]? ) ||
// hsl( <hue>, <percentage>, <percentage>, <alpha-value>? )
// <hue> = <number> | <angle>
// hsla is an alias of hsl.
float h, s, l, a;
if (ParseHSLColor(h, s, l, a)) {
aValue.SetFloatColorValue(h, s, l, a,
isHSL ? eCSSUnit_HSLColor : eCSSUnit_HSLAColor);
return CSSParseResult::Ok;
}
SkipUntil(')');
return CSSParseResult::Error;
}
break;
}
default:
break;
}
// try 'xxyyzz' without '#' prefix for compatibility with IE and Nav4x (bug 23236 and 45804)
if (mHashlessColorQuirk) {
// https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk
//
// - If the string starts with 'a-f', the nsCSSScanner builds the
// token as a eCSSToken_Ident and we can parse the string as a
// 'xxyyzz' RGB color.
// - If it only contains up to six '0-9' digits, the token is a
// eCSSToken_Number and it must be converted back to a 6
// characters string to be parsed as a RGB color. The number cannot
// be specified as more than six digits.
// - If it starts with '0-9' and contains any 'a-f', the token is a
// eCSSToken_Dimension, the mNumber part must be converted back to
// a string and the mIdent part must be appended to that string so
// that the resulting string has 6 characters. The combined
// dimension cannot be longer than 6 characters.
// Note: This is a hack for Nav compatibility. Do not attempt to
// simplify it by hacking into the ncCSSScanner. This would be very
// bad.
nsAutoString str;
char buffer[20];
switch (tk->mType) {
case eCSSToken_Ident:
str.Assign(tk->mIdent);
break;
case eCSSToken_Number:
if (tk->mIntegerValid && tk->mInteger < 1000000 && tk->mInteger >= 0) {
SprintfLiteral(buffer, "%06d", tk->mInteger);
CopyASCIItoUTF16(buffer, str);
}
break;
case eCSSToken_Dimension:
if (tk->mIntegerValid &&
tk->mIdent.Length() + CountNumbersForHashlessColor(tk->mInteger) <= 6 &&
tk->mInteger >= 0) {
SprintfLiteral(buffer, "%06d", tk->mInteger);
nsAutoString temp;
CopyASCIItoUTF16(buffer, temp);
temp.Right(str, 6 - tk->mIdent.Length());
str.Append(tk->mIdent);
}
break;
default:
// There is a whole bunch of cases that are
// not handled by this switch. Ignore them.
break;
}
// The hashless color quirk does not support 4 & 8 digit colors with alpha.
if (NS_HexToRGBA(str, nsHexColorType::NoAlpha, &rgba)) {
aValue.SetIntegerColorValue(rgba, eCSSUnit_HexColor);
return CSSParseResult::Ok;
}
}
// It's not a color
REPORT_UNEXPECTED_TOKEN(PEColorNotColor);
UngetToken();
return CSSParseResult::NotFound;
}
bool
CSSParserImpl::ParseColorComponent(uint8_t& aComponent, const Maybe<char>& aSeparator)
{
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEColorComponentEOF);
return false;
}
if (mToken.mType != eCSSToken_Number) {
REPORT_UNEXPECTED_TOKEN(PEExpectedNumber);
UngetToken();
return false;
}
float value = mToken.mNumber;
if (aSeparator && !ExpectSymbol(*aSeparator, true)) {
REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, *aSeparator);
return false;
}
if (value < 0.0f) value = 0.0f;
if (value > 255.0f) value = 255.0f;
aComponent = NSToIntRound(value);
return true;
}
bool
CSSParserImpl::ParseColorComponent(float& aComponent, const Maybe<char>& aSeparator)
{
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEColorComponentEOF);
return false;
}
if (mToken.mType != eCSSToken_Percentage) {
REPORT_UNEXPECTED_TOKEN(PEExpectedPercent);
UngetToken();
return false;
}
float value = mToken.mNumber;
if (aSeparator && !ExpectSymbol(*aSeparator, true)) {
REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, *aSeparator);
return false;
}
if (value < 0.0f) value = 0.0f;
if (value > 1.0f) value = 1.0f;
aComponent = value;
return true;
}
bool
CSSParserImpl::ParseHue(float& aAngle)
{
nsCSSValue value;
// <hue> = <number> | <angle>
// https://drafts.csswg.org/css-color/#typedef-hue
if (!ParseSingleTokenVariant(value,
VARIANT_NUMBER | VARIANT_ANGLE,
nullptr)) {
REPORT_UNEXPECTED_TOKEN(PEExpectedNumberOrAngle);
return false;
}
float unclampedResult;
if (value.GetUnit() == eCSSUnit_Number) {
unclampedResult = value.GetFloatValue();
} else {
// Convert double value of GetAngleValueInDegrees() to float.
unclampedResult = value.GetAngleValueInDegrees();
}
// Clamp it as finite values in float.
aAngle = mozilla::clamped(unclampedResult,
-std::numeric_limits<float>::max(),
std::numeric_limits<float>::max());
return true;
}
bool
CSSParserImpl::ParseHSLColor(float& aHue, float& aSaturation, float& aLightness,
float& aOpacity)
{
// comma-less expression:
// hsl() = hsl( <hue> <saturation> <lightness> [ / <alpha-value> ]? )
// the expression with comma:
// hsl() = hsl( <hue>, <saturation>, <lightness>, <alpha-value>? )
const char commaSeparator = ',';
// Parse hue.
// <hue> = <number> | <angle>
float degreeAngle;
if (!ParseHue(degreeAngle)) {
return false;
}
aHue = degreeAngle / 360.0f;
// hue values are wraparound
aHue = aHue - floor(aHue);
// Look for a comma separator after "hue" component to determine if the
// expression is comma-less or not.
bool hasComma = ExpectSymbol(commaSeparator, true);
// Parse saturation, lightness and opacity.
// The saturation and lightness are <percentage>, so reuse the float version
// of ParseColorComponent function for them. No need to check the separator
// after 'lightness'. It will be checked in opacity value parsing.
const char separatorBeforeAlpha = hasComma ? commaSeparator : '/';
if (ParseColorComponent(aSaturation, hasComma ? Some(commaSeparator) : Nothing()) &&
ParseColorComponent(aLightness, Nothing()) &&
ParseColorOpacityAndCloseParen(aOpacity, separatorBeforeAlpha)) {
return true;
}
return false;
}
bool
CSSParserImpl::ParseColorOpacityAndCloseParen(uint8_t& aOpacity,
char aSeparator)
{
float floatOpacity;
if (!ParseColorOpacityAndCloseParen(floatOpacity, aSeparator)) {
return false;
}
uint8_t value = nsStyleUtil::FloatToColorComponent(floatOpacity);
// Need to compare to something slightly larger
// than 0.5 due to floating point inaccuracies.
NS_ASSERTION(fabs(255.0f * floatOpacity - value) <= 0.51f,
"FloatToColorComponent did something weird");
aOpacity = value;
return true;
}
bool
CSSParserImpl::ParseColorOpacityAndCloseParen(float& aOpacity,
char aSeparator)
{
if (ExpectSymbol(')', true)) {
// The optional [separator <alpha-value>] was omitted, so set the opacity
// to a fully-opaque value '1.0f' and return success.
aOpacity = 1.0f;
return true;
}
if (!ExpectSymbol(aSeparator, true)) {
REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, aSeparator);
return false;
}
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEColorOpacityEOF);
return false;
}
// eCSSToken_Number or eCSSToken_Percentage.
if (mToken.mType != eCSSToken_Number && mToken.mType != eCSSToken_Percentage) {
REPORT_UNEXPECTED_TOKEN(PEExpectedNumberOrPercent);
UngetToken();
return false;
}
if (!ExpectSymbol(')', true)) {
REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
return false;
}
if (mToken.mNumber < 0.0f) {
mToken.mNumber = 0.0f;
} else if (mToken.mNumber > 1.0f) {
mToken.mNumber = 1.0f;
}
aOpacity = mToken.mNumber;
return true;
}
template<typename ComponentType>
bool
CSSParserImpl::ParseRGBColor(ComponentType& aR,
ComponentType& aG,
ComponentType& aB,
ComponentType& aA)
{
// comma-less expression:
// rgb() = rgb( component{3} [ / <alpha-value> ]? )
// the expression with comma:
// rgb() = rgb( component#{3} , <alpha-value>? )
const char commaSeparator = ',';
// Parse R.
if (!ParseColorComponent(aR, Nothing())) {
return false;
}
// Look for a comma separator after "r" component to determine if the
// expression is comma-less or not.
bool hasComma = ExpectSymbol(commaSeparator, true);
// Parse G, B and A.
// No need to check the separator after 'B'. It will be checked in 'A' value
// parsing.
const char separatorBeforeAlpha = hasComma ? commaSeparator : '/';
if (ParseColorComponent(aG, hasComma ? Some(commaSeparator) : Nothing()) &&
ParseColorComponent(aB, Nothing()) &&
ParseColorOpacityAndCloseParen(aA, separatorBeforeAlpha)) {
return true;
}
return false;
}
#ifdef MOZ_XUL
bool
CSSParserImpl::ParseTreePseudoElement(nsAtomList **aPseudoElementArgs)
{
// The argument to a tree pseudo-element is a sequence of identifiers
// that are either space- or comma-separated. (Was the intent to
// allow only comma-separated? That's not what was done.)
nsCSSSelector fakeSelector; // so we can reuse AddPseudoClass
while (!ExpectSymbol(')', true)) {
if (!GetToken(true)) {
return false;
}
if (eCSSToken_Ident == mToken.mType) {
fakeSelector.AddClass(mToken.mIdent);
}
else if (!mToken.IsSymbol(',')) {
UngetToken();
SkipUntil(')');
return false;
}
}
*aPseudoElementArgs = fakeSelector.mClassList;
fakeSelector.mClassList = nullptr;
return true;
}
#endif
nsCSSKeyword
CSSParserImpl::LookupKeywordPrefixAware(nsAString& aKeywordStr,
const KTableEntry aKeywordTable[])
{
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aKeywordStr);
if (!StylePrefs::sWebkitPrefixedAliasesEnabled) {
// Not accepting webkit-prefixed keywords -> don't do anything special.
return keyword;
}
if (aKeywordTable == nsCSSProps::kDisplayKTable) {
if ((keyword == eCSSKeyword__webkit_box ||
keyword == eCSSKeyword__webkit_inline_box)) {
// Make a note that we're accepting some "-webkit-{inline-}box" styling,
// so we can give special treatment to subsequent "-moz-{inline}-box".
// (See special treatment below.)
if (mWebkitBoxUnprefixState == eHaveNotUnprefixed) {
mWebkitBoxUnprefixState = eHaveUnprefixed;
}
} else if (mWebkitBoxUnprefixState == eHaveUnprefixed &&
(keyword == eCSSKeyword__moz_box ||
keyword == eCSSKeyword__moz_inline_box)) {
// If we've seen "display: -webkit-box" (or "-webkit-inline-box") in an
// earlier declaration and we honored it, then we have to watch out for
// later "display: -moz-box" (and "-moz-inline-box") declarations; they're
// likely just a halfhearted attempt at compatibility, and they actually
// end up stomping on our emulation of the earlier -webkit-box
// display-value, via the CSS cascade. To prevent this problem, we treat
// "display: -moz-box" & "-moz-inline-box" as if they were simply a
// repetition of the webkit equivalent that we already parsed.
MOZ_ASSERT(StylePrefs::sWebkitPrefixedAliasesEnabled,
"The only way mWebkitBoxUnprefixState can be eHaveUnprefixed "
"is if we're supporting webkit-prefixed aliases");
return (keyword == eCSSKeyword__moz_box) ?
eCSSKeyword__webkit_box : eCSSKeyword__webkit_inline_box;
}
}
return keyword;
}
//----------------------------------------------------------------------
bool
CSSParserImpl::ParseDeclaration(css::Declaration* aDeclaration,
uint32_t aFlags,
bool aMustCallValueAppended,
bool* aChanged,
nsCSSContextType aContext)
{
NS_PRECONDITION(aContext == eCSSContext_General ||
aContext == eCSSContext_Page,
"Must be page or general context");
bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0;
mTempData.AssertInitialState();
// Get property name
nsCSSToken* tk = &mToken;
nsAutoString propertyName;
for (;;) {
if (!GetToken(true)) {
if (checkForBraces) {
REPORT_UNEXPECTED_EOF(PEDeclEndEOF);
}
return false;
}
if (eCSSToken_Ident == tk->mType) {
propertyName = tk->mIdent;
// grab the ident before the ExpectSymbol trashes the token
if (!ExpectSymbol(':', true)) {
REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
REPORT_UNEXPECTED(PEDeclDropped);
OUTPUT_ERROR();
return false;
}
break;
}
if (tk->IsSymbol(';')) {
// dangling semicolons are skipped
continue;
}
if (!tk->IsSymbol('}')) {
REPORT_UNEXPECTED_TOKEN(PEParseDeclarationDeclExpected);
REPORT_UNEXPECTED(PEDeclSkipped);
OUTPUT_ERROR();
if (eCSSToken_AtKeyword == tk->mType) {
SkipAtRule(checkForBraces);
return true; // Not a declaration, but don't skip until ';'
}
}
// Not a declaration...
UngetToken();
return false;
}
// Don't report property parse errors if we're inside a failing @supports
// rule.
nsAutoSuppressErrors suppressErrors(this, mInFailingSupportsRule);
// Information about a parsed non-custom property.
nsCSSPropertyID propID;
// Information about a parsed custom property.
CSSVariableDeclarations::Type variableType;
nsString variableValue;
// Check if the property name is a custom property.
bool customProperty = nsCSSProps::IsCustomPropertyName(propertyName) &&
aContext == eCSSContext_General;
if (customProperty) {
if (!ParseVariableDeclaration(&variableType, variableValue)) {
REPORT_UNEXPECTED_P(PEValueParsingError, propertyName);
REPORT_UNEXPECTED(PEDeclDropped);
OUTPUT_ERROR();
return false;
}
} else {
// Map property name to its ID.
propID = LookupEnabledProperty(propertyName);
if (eCSSProperty_UNKNOWN == propID ||
eCSSPropertyExtra_variable == propID ||
(aContext == eCSSContext_Page &&
!nsCSSProps::PropHasFlags(propID,
CSS_PROPERTY_APPLIES_TO_PAGE_RULE))) { // unknown property
if (!NonMozillaVendorIdentifier(propertyName)) {
REPORT_UNEXPECTED_P(PEUnknownProperty, propertyName);
REPORT_UNEXPECTED(PEDeclDropped);
OUTPUT_ERROR();
}
return false;
}
// Then parse the property.
if (!ParseProperty(propID)) {
// XXX Much better to put stuff in the value parsers instead...
REPORT_UNEXPECTED_P(PEValueParsingError, propertyName);
REPORT_UNEXPECTED(PEDeclDropped);
OUTPUT_ERROR();
mTempData.ClearProperty(propID);
mTempData.AssertInitialState();
return false;
}
}
CLEAR_ERROR();
// Look for "!important".
PriorityParsingStatus status;
if ((aFlags & eParseDeclaration_AllowImportant) != 0) {
status = ParsePriority();
} else {
status = ePriority_None;
}
// Look for a semicolon or close brace.
if (status != ePriority_Error) {
if (!GetToken(true)) {
// EOF is always ok
} else if (mToken.IsSymbol(';')) {
// semicolon is always ok
} else if (mToken.IsSymbol('}')) {
// brace is ok if checkForBraces, but don't eat it
UngetToken();
if (!checkForBraces) {
status = ePriority_Error;
}
} else {
UngetToken();
status = ePriority_Error;
}
}
if (status == ePriority_Error) {
if (checkForBraces) {
REPORT_UNEXPECTED_TOKEN(PEBadDeclOrRuleEnd2);
} else {
REPORT_UNEXPECTED_TOKEN(PEBadDeclEnd);
}
REPORT_UNEXPECTED(PEDeclDropped);
OUTPUT_ERROR();
if (!customProperty) {
mTempData.ClearProperty(propID);
}
mTempData.AssertInitialState();
return false;
}
if (customProperty) {
MOZ_ASSERT(Substring(propertyName, 0,
CSS_CUSTOM_NAME_PREFIX_LENGTH).EqualsLiteral("--"));
// remove '--'
nsDependentString varName(propertyName, CSS_CUSTOM_NAME_PREFIX_LENGTH);
aDeclaration->AddVariable(varName, variableType, variableValue,
status == ePriority_Important, false);
} else {
*aChanged |= mData.TransferFromBlock(mTempData, propID, EnabledState(),
status == ePriority_Important,
false, aMustCallValueAppended,
aDeclaration, GetDocument());
}
return true;
}
static const nsCSSPropertyID kBorderTopIDs[] = {
eCSSProperty_border_top_width,
eCSSProperty_border_top_style,
eCSSProperty_border_top_color
};
static const nsCSSPropertyID kBorderRightIDs[] = {
eCSSProperty_border_right_width,
eCSSProperty_border_right_style,
eCSSProperty_border_right_color
};
static const nsCSSPropertyID kBorderBottomIDs[] = {
eCSSProperty_border_bottom_width,
eCSSProperty_border_bottom_style,
eCSSProperty_border_bottom_color
};
static const nsCSSPropertyID kBorderLeftIDs[] = {
eCSSProperty_border_left_width,
eCSSProperty_border_left_style,
eCSSProperty_border_left_color
};
static const nsCSSPropertyID kBorderInlineStartIDs[] = {
eCSSProperty_border_inline_start_width,
eCSSProperty_border_inline_start_style,
eCSSProperty_border_inline_start_color
};
static const nsCSSPropertyID kBorderInlineEndIDs[] = {
eCSSProperty_border_inline_end_width,
eCSSProperty_border_inline_end_style,
eCSSProperty_border_inline_end_color
};
static const nsCSSPropertyID kBorderBlockStartIDs[] = {
eCSSProperty_border_block_start_width,
eCSSProperty_border_block_start_style,
eCSSProperty_border_block_start_color
};
static const nsCSSPropertyID kBorderBlockEndIDs[] = {
eCSSProperty_border_block_end_width,
eCSSProperty_border_block_end_style,
eCSSProperty_border_block_end_color
};
static const nsCSSPropertyID kColumnRuleIDs[] = {
eCSSProperty_column_rule_width,
eCSSProperty_column_rule_style,
eCSSProperty_column_rule_color
};
bool
CSSParserImpl::ParseEnum(nsCSSValue& aValue,
const KTableEntry aKeywordTable[])
{
nsAString* ident = NextIdent();
if (nullptr == ident) {
return false;
}
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(*ident);
int32_t value;
if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) {
aValue.SetIntValue(value, eCSSUnit_Enumerated);
return true;
}
// Put the unknown identifier back and return
UngetToken();
return false;
}
bool
CSSParserImpl::ParseAlignEnum(nsCSSValue& aValue,
const KTableEntry aKeywordTable[])
{
MOZ_ASSERT(nsCSSProps::ValueToKeywordEnum(NS_STYLE_ALIGN_BASELINE,
aKeywordTable) !=
eCSSKeyword_UNKNOWN,
"Please use ParseEnum instead");
nsAString* ident = NextIdent();
if (!ident) {
return false;
}
nsCSSKeyword baselinePrefix = eCSSKeyword_first;
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(*ident);
if (keyword == eCSSKeyword_first || keyword == eCSSKeyword_last) {
baselinePrefix = keyword;
ident = NextIdent();
if (!ident) {
return false;
}
keyword = nsCSSKeywords::LookupKeyword(*ident);
}
int32_t value;
if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) {
if (baselinePrefix == eCSSKeyword_last &&
keyword == eCSSKeyword_baseline) {
value = NS_STYLE_ALIGN_LAST_BASELINE;
}
aValue.SetIntValue(value, eCSSUnit_Enumerated);
return true;
}
// Put the unknown identifier back and return
UngetToken();
return false;
}
struct UnitInfo {
char name[6]; // needs to be long enough for the longest unit, with
// terminating null.
uint32_t length;
nsCSSUnit unit;
int32_t type;
};
#define STR_WITH_LEN(_str) \
_str, sizeof(_str) - 1
const UnitInfo UnitData[] = {
{ STR_WITH_LEN("px"), eCSSUnit_Pixel, VARIANT_LENGTH },
{ STR_WITH_LEN("em"), eCSSUnit_EM, VARIANT_LENGTH },
{ STR_WITH_LEN("ex"), eCSSUnit_XHeight, VARIANT_LENGTH },
{ STR_WITH_LEN("pt"), eCSSUnit_Point, VARIANT_LENGTH },
{ STR_WITH_LEN("in"), eCSSUnit_Inch, VARIANT_LENGTH },
{ STR_WITH_LEN("cm"), eCSSUnit_Centimeter, VARIANT_LENGTH },
{ STR_WITH_LEN("ch"), eCSSUnit_Char, VARIANT_LENGTH },
{ STR_WITH_LEN("rem"), eCSSUnit_RootEM, VARIANT_LENGTH },
{ STR_WITH_LEN("mm"), eCSSUnit_Millimeter, VARIANT_LENGTH },
{ STR_WITH_LEN("vw"), eCSSUnit_ViewportWidth, VARIANT_LENGTH },
{ STR_WITH_LEN("vh"), eCSSUnit_ViewportHeight, VARIANT_LENGTH },
{ STR_WITH_LEN("vmin"), eCSSUnit_ViewportMin, VARIANT_LENGTH },
{ STR_WITH_LEN("vmax"), eCSSUnit_ViewportMax, VARIANT_LENGTH },
{ STR_WITH_LEN("pc"), eCSSUnit_Pica, VARIANT_LENGTH },
{ STR_WITH_LEN("q"), eCSSUnit_Quarter, VARIANT_LENGTH },
{ STR_WITH_LEN("deg"), eCSSUnit_Degree, VARIANT_ANGLE },
{ STR_WITH_LEN("grad"), eCSSUnit_Grad, VARIANT_ANGLE },
{ STR_WITH_LEN("rad"), eCSSUnit_Radian, VARIANT_ANGLE },
{ STR_WITH_LEN("turn"), eCSSUnit_Turn, VARIANT_ANGLE },
{ STR_WITH_LEN("hz"), eCSSUnit_Hertz, VARIANT_FREQUENCY },
{ STR_WITH_LEN("khz"), eCSSUnit_Kilohertz, VARIANT_FREQUENCY },
{ STR_WITH_LEN("s"), eCSSUnit_Seconds, VARIANT_TIME },
{ STR_WITH_LEN("ms"), eCSSUnit_Milliseconds, VARIANT_TIME }
};
#undef STR_WITH_LEN
bool
CSSParserImpl::TranslateDimension(nsCSSValue& aValue,
uint32_t aVariantMask,
float aNumber,
const nsString& aUnit)
{
nsCSSUnit units;
int32_t type = 0;
if (!aUnit.IsEmpty()) {
uint32_t i;
for (i = 0; i < ArrayLength(UnitData); ++i) {
if (aUnit.LowerCaseEqualsASCII(UnitData[i].name,
UnitData[i].length)) {
units = UnitData[i].unit;
type = UnitData[i].type;
break;
}
}
if (i == ArrayLength(UnitData)) {
// Unknown unit
return false;
}
if (!mViewportUnitsEnabled &&
(eCSSUnit_ViewportWidth == units ||
eCSSUnit_ViewportHeight == units ||
eCSSUnit_ViewportMin == units ||
eCSSUnit_ViewportMax == units)) {
// Viewport units aren't allowed right now, probably because we're
// inside an @page declaration. Fail.
return false;
}
if ((VARIANT_ABSOLUTE_DIMENSION & aVariantMask) != 0 &&
!nsCSSValue::IsPixelLengthUnit(units)) {
return false;
}
} else {
// Must be a zero number...
NS_ASSERTION(0 == aNumber, "numbers without units must be 0");
if ((VARIANT_LENGTH & aVariantMask) != 0) {
units = eCSSUnit_Pixel;
type = VARIANT_LENGTH;
}
else if ((VARIANT_ANGLE & aVariantMask) != 0) {
NS_ASSERTION(aVariantMask & VARIANT_ZERO_ANGLE,
"must have allowed zero angle");
units = eCSSUnit_Degree;
type = VARIANT_ANGLE;
}
else {
NS_ERROR("Variant mask does not include dimension; why were we called?");
return false;
}
}
if ((type & aVariantMask) != 0) {
aValue.SetFloatValue(aNumber, units);
return true;
}
return false;
}
// Note that this does include VARIANT_CALC, which is numeric. This is
// because calc() parsing, as proposed, drops range restrictions inside
// the calc() expression and clamps the result of the calculation to the
// range.
#define VARIANT_ALL_NONNUMERIC \
VARIANT_KEYWORD | \
VARIANT_COLOR | \
VARIANT_URL | \
VARIANT_STRING | \
VARIANT_COUNTER | \
VARIANT_ATTR | \
VARIANT_IDENTIFIER | \
VARIANT_IDENTIFIER_NO_INHERIT | \
VARIANT_AUTO | \
VARIANT_INHERIT | \
VARIANT_NONE | \
VARIANT_NORMAL | \
VARIANT_SYSFONT | \
VARIANT_GRADIENT | \
VARIANT_TIMING_FUNCTION | \
VARIANT_ALL | \
VARIANT_CALC | \
VARIANT_OPENTYPE_SVG_KEYWORD
CSSParseResult
CSSParserImpl::ParseVariantWithRestrictions(nsCSSValue& aValue,
int32_t aVariantMask,
const KTableEntry aKeywordTable[],
uint32_t aRestrictions)
{
switch (aRestrictions) {
default:
MOZ_FALLTHROUGH_ASSERT("should not be reached");
case 0:
return ParseVariant(aValue, aVariantMask, aKeywordTable);
case CSS_PROPERTY_VALUE_NONNEGATIVE:
return ParseNonNegativeVariant(aValue, aVariantMask, aKeywordTable);
case CSS_PROPERTY_VALUE_AT_LEAST_ONE:
return ParseOneOrLargerVariant(aValue, aVariantMask, aKeywordTable);
}
}
// Note that callers passing VARIANT_CALC in aVariantMask will get
// full-range parsing inside the calc() expression, and the code that
// computes the calc will be required to clamp the resulting value to an
// appropriate range.
CSSParseResult
CSSParserImpl::ParseNonNegativeVariant(nsCSSValue& aValue,
int32_t aVariantMask,
const KTableEntry aKeywordTable[])
{
// The variant mask must only contain non-numeric variants or the ones
// that we specifically handle.
MOZ_ASSERT((aVariantMask & ~(VARIANT_ALL_NONNUMERIC |
VARIANT_NUMBER |
VARIANT_LENGTH |
VARIANT_PERCENT |
VARIANT_INTEGER)) == 0,
"need to update code below to handle additional variants");
CSSParseResult result = ParseVariant(aValue, aVariantMask, aKeywordTable);
if (result == CSSParseResult::Ok) {
if (eCSSUnit_Number == aValue.GetUnit() ||
aValue.IsLengthUnit()){
if (aValue.GetFloatValue() < 0) {
UngetToken();
return CSSParseResult::NotFound;
}
}
else if (aValue.GetUnit() == eCSSUnit_Percent) {
if (aValue.GetPercentValue() < 0) {
UngetToken();
return CSSParseResult::NotFound;
}
} else if (aValue.GetUnit() == eCSSUnit_Integer) {
if (aValue.GetIntValue() < 0) {
UngetToken();
return CSSParseResult::NotFound;
}
}
}
return result;
}
// Note that callers passing VARIANT_CALC in aVariantMask will get
// full-range parsing inside the calc() expression, and the code that
// computes the calc will be required to clamp the resulting value to an
// appropriate range.
CSSParseResult
CSSParserImpl::ParseOneOrLargerVariant(nsCSSValue& aValue,
int32_t aVariantMask,
const KTableEntry aKeywordTable[])
{
// The variant mask must only contain non-numeric variants or the ones
// that we specifically handle.
MOZ_ASSERT((aVariantMask & ~(VARIANT_ALL_NONNUMERIC |
VARIANT_NUMBER |
VARIANT_INTEGER)) == 0,
"need to update code below to handle additional variants");
CSSParseResult result = ParseVariant(aValue, aVariantMask, aKeywordTable);
if (result == CSSParseResult::Ok) {
if (aValue.GetUnit() == eCSSUnit_Integer) {
if (aValue.GetIntValue() < 1) {
UngetToken();
return CSSParseResult::NotFound;
}
} else if (eCSSUnit_Number == aValue.GetUnit()) {
if (aValue.GetFloatValue() < 1.0f) {
UngetToken();
return CSSParseResult::NotFound;
}
}
}
return result;
}
static bool
IsCSSTokenCalcFunction(const nsCSSToken& aToken)
{
return aToken.mType == eCSSToken_Function &&
aToken.mIdent.LowerCaseEqualsLiteral("calc");
}
// Assigns to aValue iff it returns CSSParseResult::Ok.
CSSParseResult
CSSParserImpl::ParseVariant(nsCSSValue& aValue,
uint32_t aVariantMask,
const KTableEntry aKeywordTable[])
{
NS_ASSERTION(!(mHashlessColorQuirk && (aVariantMask & VARIANT_COLOR)) ||
!(aVariantMask & VARIANT_NUMBER),
"can't distinguish colors from numbers");
NS_ASSERTION(!(mHashlessColorQuirk && (aVariantMask & VARIANT_COLOR)) ||
!(mUnitlessLengthQuirk && (aVariantMask & VARIANT_LENGTH)),
"can't distinguish colors from lengths");
NS_ASSERTION(!(mUnitlessLengthQuirk && (aVariantMask & VARIANT_LENGTH)) ||
!(aVariantMask & VARIANT_NUMBER),
"can't distinguish lengths from numbers");
MOZ_ASSERT(!(aVariantMask & VARIANT_IDENTIFIER) ||
!(aVariantMask & VARIANT_IDENTIFIER_NO_INHERIT),
"must not set both VARIANT_IDENTIFIER and "
"VARIANT_IDENTIFIER_NO_INHERIT");
uint32_t lineBefore, colBefore;
if (!GetNextTokenLocation(true, &lineBefore, &colBefore) ||
!GetToken(true)) {
// Must be at EOF.
return CSSParseResult::NotFound;
}
nsCSSToken* tk = &mToken;
if (((aVariantMask & (VARIANT_AHK | VARIANT_NORMAL | VARIANT_NONE | VARIANT_ALL)) != 0) &&
(eCSSToken_Ident == tk->mType)) {
nsCSSKeyword keyword = LookupKeywordPrefixAware(tk->mIdent,
aKeywordTable);
if (eCSSKeyword_UNKNOWN < keyword) { // known keyword
if ((aVariantMask & VARIANT_AUTO) != 0) {
if (eCSSKeyword_auto == keyword) {
aValue.SetAutoValue();
return CSSParseResult::Ok;
}
}
if ((aVariantMask & VARIANT_INHERIT) != 0) {
// XXX Should we check IsParsingCompoundProperty, or do all
// callers handle it? (Not all callers set it, though, since
// they want the quirks that are disabled by setting it.)
// IMPORTANT: If new keywords are added here,
// they probably need to be added in ParseCustomIdent as well.
if (eCSSKeyword_inherit == keyword) {
aValue.SetInheritValue();
return CSSParseResult::Ok;
}
else if (eCSSKeyword_initial == keyword) {
aValue.SetInitialValue();
return CSSParseResult::Ok;
}
else if (eCSSKeyword_unset == keyword &&
nsLayoutUtils::UnsetValueEnabled()) {
aValue.SetUnsetValue();
return CSSParseResult::Ok;
}
}
if ((aVariantMask & VARIANT_NONE) != 0) {
if (eCSSKeyword_none == keyword) {
aValue.SetNoneValue();
return CSSParseResult::Ok;
}
}
if ((aVariantMask & VARIANT_ALL) != 0) {
if (eCSSKeyword_all == keyword) {
aValue.SetAllValue();
return CSSParseResult::Ok;
}
}
if ((aVariantMask & VARIANT_NORMAL) != 0) {
if (eCSSKeyword_normal == keyword) {
aValue.SetNormalValue();
return CSSParseResult::Ok;
}
}
if ((aVariantMask & VARIANT_SYSFONT) != 0) {
if (eCSSKeyword__moz_use_system_font == keyword &&
!IsParsingCompoundProperty()) {
aValue.SetSystemFontValue();
return CSSParseResult::Ok;
}
}
if ((aVariantMask & VARIANT_OPENTYPE_SVG_KEYWORD) != 0) {
if (StylePrefs::sOpentypeSVGEnabled) {
aVariantMask |= VARIANT_KEYWORD;
}
}
if ((aVariantMask & VARIANT_KEYWORD) != 0) {
int32_t value;
if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) {
aValue.SetIntValue(value, eCSSUnit_Enumerated);
return CSSParseResult::Ok;
}
}
}
}
// Check VARIANT_NUMBER and VARIANT_INTEGER before VARIANT_LENGTH or
// VARIANT_ZERO_ANGLE.
if (((aVariantMask & VARIANT_NUMBER) != 0) &&
(eCSSToken_Number == tk->mType)) {
aValue.SetFloatValue(tk->mNumber, eCSSUnit_Number);
return CSSParseResult::Ok;
}
if (((aVariantMask & VARIANT_INTEGER) != 0) &&
(eCSSToken_Number == tk->mType) && tk->mIntegerValid) {
aValue.SetIntValue(tk->mInteger, eCSSUnit_Integer);
return CSSParseResult::Ok;
}
if (((aVariantMask & (VARIANT_LENGTH | VARIANT_ANGLE |
VARIANT_FREQUENCY | VARIANT_TIME)) != 0 &&
eCSSToken_Dimension == tk->mType) ||
((aVariantMask & (VARIANT_LENGTH | VARIANT_ZERO_ANGLE)) != 0 &&
eCSSToken_Number == tk->mType &&
tk->mNumber == 0.0f)) {
if ((aVariantMask & VARIANT_NONNEGATIVE_DIMENSION) != 0 &&
tk->mNumber < 0.0) {
UngetToken();
AssertNextTokenAt(lineBefore, colBefore);
return CSSParseResult::NotFound;
}
if (TranslateDimension(aValue, aVariantMask, tk->mNumber, tk->mIdent)) {
return CSSParseResult::Ok;
}
// Put the token back; we didn't parse it, so we shouldn't consume it
UngetToken();
AssertNextTokenAt(lineBefore, colBefore);
return CSSParseResult::NotFound;
}
if (((aVariantMask & VARIANT_PERCENT) != 0) &&
(eCSSToken_Percentage == tk->mType)) {
aValue.SetPercentValue(tk->mNumber);
return CSSParseResult::Ok;
}
if (mUnitlessLengthQuirk) { // NONSTANDARD: Nav interprets unitless numbers as px
if (((aVariantMask & VARIANT_LENGTH) != 0) &&
(eCSSToken_Number == tk->mType)) {
aValue.SetFloatValue(tk->mNumber, eCSSUnit_Pixel);
return CSSParseResult::Ok;
}
}
if (mIsSVGMode && !IsParsingCompoundProperty()) {
// STANDARD: SVG Spec states that lengths and coordinates can be unitless
// in which case they default to user-units (1 px = 1 user unit)
if (((aVariantMask & VARIANT_LENGTH) != 0) &&
(eCSSToken_Number == tk->mType)) {
aValue.SetFloatValue(tk->mNumber, eCSSUnit_Pixel);
return CSSParseResult::Ok;
}
}
if (((aVariantMask & VARIANT_URL) != 0) &&
eCSSToken_URL == tk->mType) {
SetValueToURL(aValue, tk->mIdent);
return CSSParseResult::Ok;
}
if ((aVariantMask & VARIANT_GRADIENT) != 0 &&
eCSSToken_Function == tk->mType) {
// a generated gradient
nsDependentString tmp(tk->mIdent, 0);
uint8_t gradientFlags = 0;
if (StylePrefs::sMozGradientsEnabled &&
StringBeginsWith(tmp, NS_LITERAL_STRING("-moz-"))) {
tmp.Rebind(tmp, 5);
gradientFlags |= eGradient_MozLegacy;
} else if (StylePrefs::sWebkitPrefixedAliasesEnabled &&
StringBeginsWith(tmp, NS_LITERAL_STRING("-webkit-"))) {
tmp.Rebind(tmp, 8);
gradientFlags |= eGradient_WebkitLegacy;
}
if (StringBeginsWith(tmp, NS_LITERAL_STRING("repeating-"))) {
tmp.Rebind(tmp, 10);
gradientFlags |= eGradient_Repeating;
}
if (tmp.LowerCaseEqualsLiteral("linear-gradient")) {
if (!ParseLinearGradient(aValue, gradientFlags)) {
return CSSParseResult::Error;
}
return CSSParseResult::Ok;
}
if (tmp.LowerCaseEqualsLiteral("radial-gradient")) {
if (!ParseRadialGradient(aValue, gradientFlags)) {
return CSSParseResult::Error;
}
return CSSParseResult::Ok;
}
if ((gradientFlags == eGradient_WebkitLegacy) &&
tmp.LowerCaseEqualsLiteral("gradient")) {
// Note: we check gradientFlags using '==' to select *exactly*
// eGradient_WebkitLegacy -- and exclude eGradient_Repeating -- because
// we don't want to accept -webkit-repeating-gradient() expressions.
// (This is not a recognized syntax.)
if (!ParseWebkitGradient(aValue)) {
return CSSParseResult::Error;
}
return CSSParseResult::Ok;
}
}
if ((aVariantMask & VARIANT_IMAGE_RECT) != 0 &&
eCSSToken_Function == tk->mType &&
tk->mIdent.LowerCaseEqualsLiteral("-moz-image-rect")) {
if (!ParseImageRect(aValue)) {
return CSSParseResult::Error;
}
return CSSParseResult::Ok;
}
if ((aVariantMask & VARIANT_ELEMENT) != 0 &&
eCSSToken_Function == tk->mType &&
tk->mIdent.LowerCaseEqualsLiteral("-moz-element")) {
if (!ParseElement(aValue)) {
return CSSParseResult::Error;
}
return CSSParseResult::Ok;
}
if ((aVariantMask & VARIANT_COLOR) != 0) {
if (mHashlessColorQuirk || // NONSTANDARD: Nav interprets 'xxyyzz' values even without '#' prefix
(eCSSToken_ID == tk->mType) ||
(eCSSToken_Hash == tk->mType) ||
(eCSSToken_Ident == tk->mType) ||
((eCSSToken_Function == tk->mType) &&
(tk->mIdent.LowerCaseEqualsLiteral("rgb") ||
tk->mIdent.LowerCaseEqualsLiteral("hsl") ||
tk->mIdent.LowerCaseEqualsLiteral("rgba") ||
tk->mIdent.LowerCaseEqualsLiteral("hsla"))))
{
// Put token back so that parse color can get it
UngetToken();
return ParseColor(aValue);
}
}
if (((aVariantMask & VARIANT_STRING) != 0) &&
(eCSSToken_String == tk->mType)) {
nsAutoString buffer;
buffer.Append(tk->mIdent);
aValue.SetStringValue(buffer, eCSSUnit_String);
return CSSParseResult::Ok;
}
if (((aVariantMask &
(VARIANT_IDENTIFIER | VARIANT_IDENTIFIER_NO_INHERIT)) != 0) &&
(eCSSToken_Ident == tk->mType) &&
((aVariantMask & VARIANT_IDENTIFIER) != 0 ||
!(tk->mIdent.LowerCaseEqualsLiteral("inherit") ||
tk->mIdent.LowerCaseEqualsLiteral("initial") ||
(tk->mIdent.LowerCaseEqualsLiteral("unset") &&
nsLayoutUtils::UnsetValueEnabled())))) {
aValue.SetStringValue(tk->mIdent, eCSSUnit_Ident);
return CSSParseResult::Ok;
}
if (((aVariantMask & VARIANT_COUNTER) != 0) &&
(eCSSToken_Function == tk->mType) &&
(tk->mIdent.LowerCaseEqualsLiteral("counter") ||
tk->mIdent.LowerCaseEqualsLiteral("counters"))) {
if (!ParseCounter(aValue)) {
return CSSParseResult::Error;
}
return CSSParseResult::Ok;
}
if (((aVariantMask & VARIANT_ATTR) != 0) &&
(eCSSToken_Function == tk->mType) &&
tk->mIdent.LowerCaseEqualsLiteral("attr")) {
if (!ParseAttr(aValue)) {
SkipUntil(')');
return CSSParseResult::Error;
}
return CSSParseResult::Ok;
}
if (((aVariantMask & VARIANT_TIMING_FUNCTION) != 0) &&
(eCSSToken_Function == tk->mType)) {
if (tk->mIdent.LowerCaseEqualsLiteral("cubic-bezier")) {
if (!ParseTransitionTimingFunctionValues(aValue)) {
SkipUntil(')');
return CSSParseResult::Error;
}
return CSSParseResult::Ok;
}
if (tk->mIdent.LowerCaseEqualsLiteral("steps")) {
if (!ParseTransitionStepTimingFunctionValues(aValue)) {
SkipUntil(')');
return CSSParseResult::Error;
}
return CSSParseResult::Ok;
}
if (StylePrefs::sFramesTimingFunctionEnabled &&
tk->mIdent.LowerCaseEqualsLiteral("frames")) {
if (!ParseTransitionFramesTimingFunctionValues(aValue)) {
SkipUntil(')');
return CSSParseResult::Error;
}
return CSSParseResult::Ok;
}
}
if ((aVariantMask & VARIANT_CALC) &&
IsCSSTokenCalcFunction(*tk)) {
// calc() currently allows only lengths, percents, numbers, and integers.
//
// Note that VARIANT_NUMBER can be mixed with VARIANT_LENGTH and
// VARIANT_PERCENTAGE in the list of allowed types (numbers can be used as
// coefficients).
// However, the the resulting type is not a mixed type with number.
// VARIANT_INTEGER can't be mixed with anything else.
if (!ParseCalc(aValue, aVariantMask & (VARIANT_LPN | VARIANT_INTEGER))) {
return CSSParseResult::Error;
}
return CSSParseResult::Ok;
}
UngetToken();
AssertNextTokenAt(lineBefore, colBefore);
return CSSParseResult::NotFound;
}
bool
CSSParserImpl::ParseCustomIdent(nsCSSValue& aValue,
const nsAutoString& aIdentValue,
const nsCSSKeyword aExcludedKeywords[],
const nsCSSProps::KTableEntry aPropertyKTable[])
{
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aIdentValue);
if (keyword == eCSSKeyword_UNKNOWN) {
// Fast path for identifiers that are not known CSS keywords:
aValue.SetStringValue(mToken.mIdent, eCSSUnit_Ident);
return true;
}
if (keyword == eCSSKeyword_inherit ||
keyword == eCSSKeyword_initial ||
keyword == eCSSKeyword_unset ||
keyword == eCSSKeyword_default ||
(aPropertyKTable &&
nsCSSProps::FindIndexOfKeyword(keyword, aPropertyKTable) >= 0)) {
return false;
}
if (aExcludedKeywords) {
for (uint32_t i = 0;; i++) {
nsCSSKeyword excludedKeyword = aExcludedKeywords[i];
if (excludedKeyword == eCSSKeyword_UNKNOWN) {
break;
}
if (excludedKeyword == keyword) {
return false;
}
}
}
aValue.SetStringValue(mToken.mIdent, eCSSUnit_Ident);
return true;
}
bool
CSSParserImpl::ParseCounter(nsCSSValue& aValue)
{
nsCSSUnit unit = (mToken.mIdent.LowerCaseEqualsLiteral("counter") ?
eCSSUnit_Counter : eCSSUnit_Counters);
// A non-iterative for loop to break out when an error occurs.
for (;;) {
if (!GetToken(true)) {
break;
}
if (eCSSToken_Ident != mToken.mType) {
UngetToken();
break;
}
RefPtr<nsCSSValue::Array> val =
nsCSSValue::Array::Create(unit == eCSSUnit_Counter ? 2 : 3);
val->Item(0).SetStringValue(mToken.mIdent, eCSSUnit_Ident);
if (eCSSUnit_Counters == unit) {
// must have a comma and then a separator string
if (!ExpectSymbol(',', true) || !GetToken(true)) {
break;
}
if (eCSSToken_String != mToken.mType) {
UngetToken();
break;
}
val->Item(1).SetStringValue(mToken.mIdent, eCSSUnit_String);
}
// get optional type
int32_t typeItem = eCSSUnit_Counters == unit ? 2 : 1;
nsCSSValue& type = val->Item(typeItem);
if (ExpectSymbol(',', true)) {
if (!ParseCounterStyleNameValue(type) && !ParseSymbols(type)) {
break;
}
} else {
type.SetAtomIdentValue(
do_AddRef(static_cast<nsAtom*>(nsGkAtoms::decimal)));
}
if (!ExpectSymbol(')', true)) {
break;
}
aValue.SetArrayValue(val, unit);
return true;
}
SkipUntil(')');
return false;
}
bool
CSSParserImpl::ParseContextProperties()
{
nsCSSValue listValue;
nsCSSValueList* currentListValue = listValue.SetListValue();
bool first = true;
for (;;) {
const uint32_t variantMask = VARIANT_IDENTIFIER |
VARIANT_INHERIT |
VARIANT_NONE;
nsCSSValue value;
if (!ParseSingleTokenVariant(value, variantMask, nullptr)) {
return false;
}
if (value.GetUnit() != eCSSUnit_Ident) {
if (first) {
AppendValue(eCSSProperty__moz_context_properties, value);
return true;
} else {
return false;
}
}
value.AtomizeIdentValue();
nsAtom* atom = value.GetAtomValue();
if (atom == nsGkAtoms::_default) {
return false;
}
currentListValue->mValue = Move(value);
if (!ExpectSymbol(',', true)) {
break;
}
currentListValue->mNext = new nsCSSValueList;
currentListValue = currentListValue->mNext;
first = false;
}
AppendValue(eCSSProperty__moz_context_properties, listValue);
return true;
}
bool
CSSParserImpl::ParseAttr(nsCSSValue& aValue)
{
if (!GetToken(true)) {
return false;
}
nsAutoString attr;
if (eCSSToken_Ident == mToken.mType) { // attr name or namespace
nsAutoString holdIdent(mToken.mIdent);
if (ExpectSymbol('|', false)) { // namespace
int32_t nameSpaceID = GetNamespaceIdForPrefix(holdIdent);
if (nameSpaceID == kNameSpaceID_Unknown) {
return false;
}
attr.AppendInt(nameSpaceID, 10);
attr.Append(char16_t('|'));
if (! GetToken(false)) {
REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
return false;
}
if (eCSSToken_Ident == mToken.mType) {
attr.Append(mToken.mIdent);
}
else {
REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
UngetToken();
return false;
}
}
else { // no namespace
attr = holdIdent;
}
}
else if (mToken.IsSymbol('*')) { // namespace wildcard
// Wildcard namespace makes no sense here and is not allowed
REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
UngetToken();
return false;
}
else if (mToken.IsSymbol('|')) { // explicit NO namespace
if (! GetToken(false)) {
REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
return false;
}
if (eCSSToken_Ident == mToken.mType) {
attr.Append(mToken.mIdent);
}
else {
REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
UngetToken();
return false;
}
}
else {
REPORT_UNEXPECTED_TOKEN(PEAttributeNameOrNamespaceExpected);
UngetToken();
return false;
}
if (!ExpectSymbol(')', true)) {
return false;
}
aValue.SetStringValue(attr, eCSSUnit_Attr);
return true;
}
bool
CSSParserImpl::ParseSymbols(nsCSSValue& aValue)
{
if (!GetToken(true)) {
return false;
}
if (mToken.mType != eCSSToken_Function &&
!mToken.mIdent.LowerCaseEqualsLiteral("symbols")) {
UngetToken();
return false;
}
RefPtr<nsCSSValue::Array> params = nsCSSValue::Array::Create(2);
nsCSSValue& type = params->Item(0);
nsCSSValue& symbols = params->Item(1);
if (!ParseEnum(type, nsCSSProps::kCounterSymbolsSystemKTable)) {
type.SetIntValue(NS_STYLE_COUNTER_SYSTEM_SYMBOLIC, eCSSUnit_Enumerated);
}
bool first = true;
nsCSSValueList* item = symbols.SetListValue();
for (;;) {
// FIXME Should also include VARIANT_IMAGE. See bug 1071436.
if (!ParseSingleTokenVariant(item->mValue, VARIANT_STRING, nullptr)) {
break;
}
if (ExpectSymbol(')', true)) {
if (first) {
switch (type.GetIntValue()) {
case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
// require at least two symbols
return false;
}
}
aValue.SetArrayValue(params, eCSSUnit_Symbols);
return true;
}
item->mNext = new nsCSSValueList;
item = item->mNext;
first = false;
}
SkipUntil(')');
return false;
}
bool
CSSParserImpl::SetValueToURL(nsCSSValue& aValue, const nsString& aURL)
{
if (!mSheetPrincipal) {
if (!mSheetPrincipalRequired) {
/* Pretend to succeed. */
return true;
}
NS_NOTREACHED("Codepaths that expect to parse URLs MUST pass in an "
"origin principal");
return false;
}
mozilla::css::URLValue *urlVal =
new mozilla::css::URLValue(aURL, mBaseURI, mSheetURI, mSheetPrincipal);
aValue.SetURLValue(urlVal);
return true;
}
/**
* Parse the image-orientation property, which has the grammar:
* <angle> flip? | flip | from-image
*/
bool
CSSParserImpl::ParseImageOrientation(nsCSSValue& aValue)
{
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
// 'inherit', 'initial' and 'unset' must be alone
return true;
}
// Check for an angle with optional 'flip'.
nsCSSValue angle;
if (ParseSingleTokenVariant(angle, VARIANT_ANGLE, nullptr)) {
nsCSSValue flip;
if (ParseSingleTokenVariant(flip, VARIANT_KEYWORD,
nsCSSProps::kImageOrientationFlipKTable)) {
RefPtr<nsCSSValue::Array> array = nsCSSValue::Array::Create(2);
array->Item(0) = angle;
array->Item(1) = flip;
aValue.SetArrayValue(array, eCSSUnit_Array);
} else {
aValue = angle;
}
return true;
}
// The remaining possibilities (bare 'flip' and 'from-image') are both
// keywords, so we can handle them at the same time.
nsCSSValue keyword;
if (ParseSingleTokenVariant(keyword, VARIANT_KEYWORD,
nsCSSProps::kImageOrientationKTable)) {
aValue = keyword;
return true;
}
// All possibilities failed.
return false;
}
/**
* Parse the arguments of -moz-image-rect() function.
* -moz-image-rect(<uri>, <top>, <right>, <bottom>, <left>)
*/
bool
CSSParserImpl::ParseImageRect(nsCSSValue& aImage)
{
// A non-iterative for loop to break out when an error occurs.
for (;;) {
nsCSSValue newFunction;
static const uint32_t kNumArgs = 5;
nsCSSValue::Array* func =
newFunction.InitFunction(eCSSKeyword__moz_image_rect, kNumArgs);
// func->Item(0) is reserved for the function name.
nsCSSValue& url = func->Item(1);
nsCSSValue& top = func->Item(2);
nsCSSValue& right = func->Item(3);
nsCSSValue& bottom = func->Item(4);
nsCSSValue& left = func->Item(5);
nsAutoString urlString;
if (!ParseURLOrString(urlString) ||
!SetValueToURL(url, urlString) ||
!ExpectSymbol(',', true)) {
break;
}
static const int32_t VARIANT_SIDE = VARIANT_NUMBER | VARIANT_PERCENT;
if (!ParseSingleTokenNonNegativeVariant(top, VARIANT_SIDE, nullptr) ||
!ExpectSymbol(',', true) ||
!ParseSingleTokenNonNegativeVariant(right, VARIANT_SIDE, nullptr) ||
!ExpectSymbol(',', true) ||
!ParseSingleTokenNonNegativeVariant(bottom, VARIANT_SIDE, nullptr) ||
!ExpectSymbol(',', true) ||
!ParseSingleTokenNonNegativeVariant(left, VARIANT_SIDE, nullptr) ||
!ExpectSymbol(')', true))
break;
aImage = newFunction;
return true;
}
SkipUntil(')');
return false;
}
// <element>: -moz-element(# <element_id> )
bool
CSSParserImpl::ParseElement(nsCSSValue& aValue)
{
// A non-iterative for loop to break out when an error occurs.
for (;;) {
if (!GetToken(true))
break;
if (mToken.mType == eCSSToken_ID) {
aValue.SetStringValue(mToken.mIdent, eCSSUnit_Element);
} else {
UngetToken();
break;
}
if (!ExpectSymbol(')', true))
break;
return true;
}
// If we detect a syntax error, we must match the opening parenthesis of the
// function with the closing parenthesis and skip all the tokens in between.
SkipUntil(')');
return false;
}
// flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
bool
CSSParserImpl::ParseFlex()
{
// First check for inherit / initial / unset
nsCSSValue tmpVal;
if (ParseSingleTokenVariant(tmpVal, VARIANT_INHERIT, nullptr)) {
AppendValue(eCSSProperty_flex_grow, tmpVal);
AppendValue(eCSSProperty_flex_shrink, tmpVal);
AppendValue(eCSSProperty_flex_basis, tmpVal);
return true;
}
// Next, check for 'none' == '0 0 auto'
if (ParseSingleTokenVariant(tmpVal, VARIANT_NONE, nullptr)) {
AppendValue(eCSSProperty_flex_grow, nsCSSValue(0.0f, eCSSUnit_Number));
AppendValue(eCSSProperty_flex_shrink, nsCSSValue(0.0f, eCSSUnit_Number));
AppendValue(eCSSProperty_flex_basis, nsCSSValue(eCSSUnit_Auto));
return true;
}
// OK, try parsing our value as individual per-subproperty components:
// [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
// Each subproperty has a default value that it takes when it's omitted in a
// "flex" shorthand value. These default values are *only* for the shorthand
// syntax -- they're distinct from the subproperties' own initial values. We
// start with each subproperty at its default, as if we had "flex: 1 1 0%".
nsCSSValue flexGrow(1.0f, eCSSUnit_Number);
nsCSSValue flexShrink(1.0f, eCSSUnit_Number);
nsCSSValue flexBasis(0.0f, eCSSUnit_Percent);
// OVERVIEW OF PARSING STRATEGY:
// =============================
// a) Parse the first component as either flex-basis or flex-grow.
// b) If it wasn't flex-grow, parse the _next_ component as flex-grow.
// c) Now we've just parsed flex-grow -- so try parsing the next thing as
// flex-shrink.
// d) Finally: If we didn't get flex-basis at the beginning, try to parse
// it now, at the end.
//
// More details in each section below.
uint32_t flexBasisVariantMask =
(nsCSSProps::ParserVariant(eCSSProperty_flex_basis) & ~(VARIANT_INHERIT));
// (a) Parse first component. It can be either be a 'flex-basis' value or a
// 'flex-grow' value, so we use the flex-basis-specific variant mask, along
// with VARIANT_NUMBER to accept 'flex-grow' values.
//
// NOTE: if we encounter unitless 0 here, we *must* interpret it as a
// 'flex-grow' value (a number), *not* as a 'flex-basis' value (a length).
// Conveniently, that's the behavior this combined variant-mask gives us --
// it'll treat unitless 0 as a number. The flexbox spec requires this:
// "a unitless zero that is not already preceded by two flex factors must be
// interpreted as a flex factor.
if (ParseNonNegativeVariant(tmpVal, flexBasisVariantMask | VARIANT_NUMBER,
nsCSSProps::kWidthKTable) != CSSParseResult::Ok) {
// First component was not a valid flex-basis or flex-grow value. Fail.
return false;
}
// Record what we just parsed as either flex-basis or flex-grow:
bool wasFirstComponentFlexBasis = (tmpVal.GetUnit() != eCSSUnit_Number);
(wasFirstComponentFlexBasis ? flexBasis : flexGrow) = tmpVal;
// (b) If we didn't get flex-grow yet, parse _next_ component as flex-grow.
bool doneParsing = false;
if (wasFirstComponentFlexBasis) {
if (ParseNonNegativeNumber(tmpVal)) {
flexGrow = tmpVal;
} else {
// Failed to parse anything after our flex-basis -- that's fine. We can
// skip the remaining parsing.
doneParsing = true;
}
}
if (!doneParsing) {
// (c) OK -- the last thing we parsed was flex-grow, so look for a
// flex-shrink in the next position.
if (ParseNonNegativeNumber(tmpVal)) {
flexShrink = tmpVal;
}
// d) Finally: If we didn't get flex-basis at the beginning, try to parse
// it now, at the end.
//
// NOTE: If we encounter unitless 0 in this final position, we'll parse it
// as a 'flex-basis' value. That's OK, because we know it must have
// been "preceded by 2 flex factors" (justification below), which gets us
// out of the spec's requirement of otherwise having to treat unitless 0
// as a flex factor.
//
// JUSTIFICATION: How do we know that a unitless 0 here must have been
// preceded by 2 flex factors? Well, suppose we had a unitless 0 that
// was preceded by only 1 flex factor. Then, we would have already
// accepted this unitless 0 as the 'flex-shrink' value, up above (since
// it's a valid flex-shrink value), and we'd have moved on to the next
// token (if any). And of course, if we instead had a unitless 0 preceded
// by *no* flex factors (if it were the first token), we would've already
// parsed it in our very first call to ParseNonNegativeVariant(). So, any
// unitless 0 encountered here *must* have been preceded by 2 flex factors.
if (!wasFirstComponentFlexBasis) {
CSSParseResult result =
ParseNonNegativeVariant(tmpVal, flexBasisVariantMask,
nsCSSProps::kWidthKTable);
if (result == CSSParseResult::Error) {
return false;
} else if (result == CSSParseResult::Ok) {
flexBasis = tmpVal;
}
}
}
AppendValue(eCSSProperty_flex_grow, flexGrow);
AppendValue(eCSSProperty_flex_shrink, flexShrink);
AppendValue(eCSSProperty_flex_basis, flexBasis);
return true;
}
// flex-flow: <flex-direction> || <flex-wrap>
bool
CSSParserImpl::ParseFlexFlow()
{
static const nsCSSPropertyID kFlexFlowSubprops[] = {
eCSSProperty_flex_direction,
eCSSProperty_flex_wrap
};
const size_t numProps = MOZ_ARRAY_LENGTH(kFlexFlowSubprops);
nsCSSValue values[numProps];
int32_t found = ParseChoice(values, kFlexFlowSubprops, numProps);
// Bail if we didn't successfully parse anything
if (found < 1) {
return false;
}
// If either property didn't get an explicit value, use its initial value.
if ((found & 1) == 0) {
values[0].SetIntValue(NS_STYLE_FLEX_DIRECTION_ROW, eCSSUnit_Enumerated);
}
if ((found & 2) == 0) {
values[1].SetIntValue(NS_STYLE_FLEX_WRAP_NOWRAP, eCSSUnit_Enumerated);
}
// Store these values and declare success!
for (size_t i = 0; i < numProps; i++) {
AppendValue(kFlexFlowSubprops[i], values[i]);
}
return true;
}
bool
CSSParserImpl::ParseGridAutoFlow()
{
nsCSSValue value;
if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
AppendValue(eCSSProperty_grid_auto_flow, value);
return true;
}
static const int32_t mask[] = {
NS_STYLE_GRID_AUTO_FLOW_ROW | NS_STYLE_GRID_AUTO_FLOW_COLUMN,
MASK_END_VALUE
};
if (!ParseBitmaskValues(value, nsCSSProps::kGridAutoFlowKTable, mask)) {
return false;
}
int32_t bitField = value.GetIntValue();
// If neither row nor column is provided, row is assumed.
if (!(bitField & (NS_STYLE_GRID_AUTO_FLOW_ROW |
NS_STYLE_GRID_AUTO_FLOW_COLUMN))) {
value.SetIntValue(bitField | NS_STYLE_GRID_AUTO_FLOW_ROW,
eCSSUnit_Enumerated);
}
AppendValue(eCSSProperty_grid_auto_flow, value);
return true;
}
static const nsCSSKeyword kGridLineKeywords[] = {
eCSSKeyword_span,
eCSSKeyword_UNKNOWN // End-of-array marker
};
CSSParseResult
CSSParserImpl::ParseGridLineNames(nsCSSValue& aValue)
{
if (!ExpectSymbol('[', true)) {
return CSSParseResult::NotFound;
}
if (!GetToken(true) || mToken.IsSymbol(']')) {
return CSSParseResult::Ok;
}
// 'return' so far leaves aValue untouched, to represent an empty list.
nsCSSValueList* item;
if (aValue.GetUnit() == eCSSUnit_List) {
// Find the end of an existing list.
// The grid-template shorthand uses this, at most once for a given list.
// NOTE: we could avoid this traversal by somehow keeping around
// a pointer to the last item from the previous call.
// It's not yet clear if this is worth the additional code complexity.
item = aValue.GetListValue();
while (item->mNext) {
item = item->mNext;
}
item->mNext = new nsCSSValueList;
item = item->mNext;
} else {
MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Null, "Unexpected unit");
item = aValue.SetListValue();
}
for (;;) {
if (!(eCSSToken_Ident == mToken.mType &&
ParseCustomIdent(item->mValue, mToken.mIdent, kGridLineKeywords))) {
UngetToken();
SkipUntil(']');
return CSSParseResult::Error;
}
if (!GetToken(true) || mToken.IsSymbol(']')) {
return CSSParseResult::Ok;
}
item->mNext = new nsCSSValueList;
item = item->mNext;
}
}
// Assuming the 'repeat(' function token has already been consumed,
// parse the rest of repeat(<positive-integer> | auto-fill, <line-names>+)
// Append to the linked list whose end is given by |aTailPtr|,
// and update |aTailPtr| to point to the new end of the list.
bool
CSSParserImpl::ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr)
{
int32_t repetitions;
Maybe<int32_t> repeatAutoEnum;
if (!ParseGridTrackRepeatIntro(true, &repetitions, &repeatAutoEnum)) {
return false;
}
if (repeatAutoEnum.isSome()) {
// Parse exactly one <line-names>.
nsCSSValue listValue;
nsCSSValueList* list = listValue.SetListValue();
if (ParseGridLineNames(list->mValue) != CSSParseResult::Ok) {
return false;
}
if (!ExpectSymbol(')', true)) {
return false;
}
// Instead of hooking up this list into the flat name list as usual,
// we create a pair(Int, List) where the first value is the auto-fill
// keyword and the second is the name list to repeat.
nsCSSValue kwd;
kwd.SetIntValue(repeatAutoEnum.value(), eCSSUnit_Enumerated);
*aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList;
(*aTailPtr)->mValue.SetPairValue(kwd, listValue);
return true;
}
// Parse at least one <line-names>
nsCSSValueList* tail = *aTailPtr;
do {
tail->mNext = new nsCSSValueList;
tail = tail->mNext;
if (ParseGridLineNames(tail->mValue) != CSSParseResult::Ok) {
return false;
}
} while (!ExpectSymbol(')', true));
nsCSSValueList* firstRepeatedItem = (*aTailPtr)->mNext;
nsCSSValueList* lastRepeatedItem = tail;
// Our repeated items are already in the target list once,
// so they need to be repeated |repetitions - 1| more times.
MOZ_ASSERT(repetitions > 0, "Expected positive repetitions");
while (--repetitions) {
nsCSSValueList* repeatedItem = firstRepeatedItem;
for (;;) {
tail->mNext = new nsCSSValueList;
tail = tail->mNext;
tail->mValue = repeatedItem->mValue;
if (repeatedItem == lastRepeatedItem) {
break;
}
repeatedItem = repeatedItem->mNext;
}
}
*aTailPtr = tail;
return true;
}
// Assuming a 'subgrid' keyword was already consumed, parse <line-name-list>?
bool
CSSParserImpl::ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue)
{
nsCSSValueList* item = aValue.SetListValue();
// This marker distinguishes the value from a <track-list>.
item->mValue.SetIntValue(NS_STYLE_GRID_TEMPLATE_SUBGRID,
eCSSUnit_Enumerated);
bool haveRepeatAuto = false;
for (;;) {
// First try to parse <name-repeat>, i.e.
// repeat(<positive-integer> | auto-fill, <line-names>+)
if (!GetToken(true)) {
return true;
}
if (mToken.mType == eCSSToken_Function &&
mToken.mIdent.LowerCaseEqualsLiteral("repeat")) {
nsCSSValueList* startOfRepeat = item;
if (!ParseGridLineNameListRepeat(&item)) {
SkipUntil(')');
return false;
}
if (startOfRepeat->mNext->mValue.GetUnit() == eCSSUnit_Pair) {
if (haveRepeatAuto) {
REPORT_UNEXPECTED(PEMoreThanOneGridRepeatAutoFillInNameList);
return false;
}
haveRepeatAuto = true;
}
} else {
UngetToken();
// This was not a repeat() function. Try to parse <line-names>.
nsCSSValue lineNames;
CSSParseResult result = ParseGridLineNames(lineNames);
if (result == CSSParseResult::NotFound) {
return true;
}
if (result == CSSParseResult::Error) {
return false;
}
item->mNext = new nsCSSValueList;
item = item->mNext;
item->mValue = lineNames;
}
}
}
CSSParseResult
CSSParserImpl::ParseGridTrackBreadth(nsCSSValue& aValue)
{
CSSParseResult result = ParseNonNegativeVariant(aValue,
VARIANT_AUTO | VARIANT_LPCALC | VARIANT_KEYWORD,
nsCSSProps::kGridTrackBreadthKTable);
if (result == CSSParseResult::Ok ||
result == CSSParseResult::Error) {
return result;
}
// Attempt to parse <flex> (a dimension with the "fr" unit).
if (!GetToken(true)) {
return CSSParseResult::NotFound;
}
if (!(eCSSToken_Dimension == mToken.mType &&
mToken.mIdent.LowerCaseEqualsLiteral("fr") &&
mToken.mNumber >= 0)) {
UngetToken();
return CSSParseResult::NotFound;
}
aValue.SetFloatValue(mToken.mNumber, eCSSUnit_FlexFraction);
return CSSParseResult::Ok;
}
// Parse a <track-size>, or <fixed-size> when aFlags has eFixedTrackSize.
CSSParseResult
CSSParserImpl::ParseGridTrackSize(nsCSSValue& aValue,
GridTrackSizeFlags aFlags)
{
const bool requireFixedSize =
!!(aFlags & GridTrackSizeFlags::eFixedTrackSize);
// Attempt to parse a single <track-breadth>.
CSSParseResult result = ParseGridTrackBreadth(aValue);
if (requireFixedSize && result == CSSParseResult::Ok &&
!aValue.IsLengthPercentCalcUnit()) {
result = CSSParseResult::Error;
}
if (result == CSSParseResult::Error) {
return result;
}
if (result == CSSParseResult::Ok) {
if (aValue.GetUnit() == eCSSUnit_FlexFraction) {
// Single value <flex> is represented internally as minmax(auto, <flex>).
nsCSSValue minmax;
nsCSSValue::Array* func = minmax.InitFunction(eCSSKeyword_minmax, 2);
func->Item(1).SetAutoValue();
func->Item(2) = aValue;
aValue = minmax;
}
return result;
}
// Attempt to parse a minmax() or fit-content() function.
if (!GetToken(true)) {
return CSSParseResult::NotFound;
}
if (eCSSToken_Function != mToken.mType) {
UngetToken();
return CSSParseResult::NotFound;
}
if (mToken.mIdent.LowerCaseEqualsLiteral("fit-content")) {
if (requireFixedSize) {
UngetToken();
return CSSParseResult::Error;
}
nsCSSValue::Array* func = aValue.InitFunction(eCSSKeyword_fit_content, 1);
if (ParseGridTrackBreadth(func->Item(1)) == CSSParseResult::Ok &&
func->Item(1).IsLengthPercentCalcUnit() &&
ExpectSymbol(')', true)) {
return CSSParseResult::Ok;
}
SkipUntil(')');
return CSSParseResult::Error;
}
if (!mToken.mIdent.LowerCaseEqualsLiteral("minmax")) {
UngetToken();
return CSSParseResult::NotFound;
}
nsCSSValue::Array* func = aValue.InitFunction(eCSSKeyword_minmax, 2);
if (ParseGridTrackBreadth(func->Item(1)) == CSSParseResult::Ok &&
ExpectSymbol(',', true) &&
ParseGridTrackBreadth(func->Item(2)) == CSSParseResult::Ok &&
ExpectSymbol(')', true)) {
if (requireFixedSize &&
!func->Item(1).IsLengthPercentCalcUnit() &&
!func->Item(2).IsLengthPercentCalcUnit()) {
return CSSParseResult::Error;
}
// Reject <flex> min-sizing.
if (func->Item(1).GetUnit() == eCSSUnit_FlexFraction) {
return CSSParseResult::Error;
}
return CSSParseResult::Ok;
}
SkipUntil(')');
return CSSParseResult::Error;
}
bool
CSSParserImpl::ParseGridAutoColumnsRows(nsCSSPropertyID aPropID)
{
nsCSSValue value;
if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr) ||
ParseGridTrackSize(value) == CSSParseResult::Ok) {
AppendValue(aPropID, value);
return true;
}
return false;
}
bool
CSSParserImpl::ParseGridTrackListWithFirstLineNames(nsCSSValue& aValue,
const nsCSSValue& aFirstLineNames,
GridTrackListFlags aFlags)
{
nsCSSValueList* firstLineNamesItem = aValue.SetListValue();
firstLineNamesItem->mValue = aFirstLineNames;
// This function is trying to parse <track-list>, which is
// [ <line-names>? [ <track-size> | <repeat()> ] ]+ <line-names>?
// and we're already past the first "<line-names>?".
// If aFlags contains eExplicitTrackList then <repeat()> is disallowed.
//
// Each iteration of the following loop attempts to parse either a
// repeat() or a <track-size> expression, and then an (optional)
// <line-names> expression.
//
// The only successful exit point from this loop is the ::NotFound
// case after ParseGridTrackSize(); i.e. we'll greedily parse
// repeat()/<track-size> until we can't find one.
nsCSSValueList* item = firstLineNamesItem;
bool haveRepeatAuto = false;
for (;;) {
// First try to parse repeat()
if (!GetToken(true)) {
break;
}
if (!(aFlags & GridTrackListFlags::eExplicitTrackList) &&
mToken.mType == eCSSToken_Function &&
mToken.mIdent.LowerCaseEqualsLiteral("repeat")) {
nsCSSValueList* startOfRepeat = item;
if (!ParseGridTrackListRepeat(&item)) {
SkipUntil(')');
return false;
}
auto firstRepeat = startOfRepeat->mNext;
if (firstRepeat->mValue.GetUnit() == eCSSUnit_Pair) {
if (haveRepeatAuto) {
REPORT_UNEXPECTED(PEMoreThanOneGridRepeatAutoFillFitInTrackList);
return false;
}
haveRepeatAuto = true;
// We're parsing an <auto-track-list>, which requires that all tracks
// are <fixed-size>, so we need to check the ones we've parsed already.
for (nsCSSValueList* list = firstLineNamesItem->mNext;
list != firstRepeat; list = list->mNext) {
if (list->mValue.GetUnit() == eCSSUnit_Function) {
nsCSSValue::Array* func = list->mValue.GetArrayValue();
auto funcName = func->Item(0).GetKeywordValue();
if (funcName == eCSSKeyword_minmax) {
if (!func->Item(1).IsLengthPercentCalcUnit() &&
!func->Item(2).IsLengthPercentCalcUnit()) {
return false;
}
} else {
MOZ_ASSERT(funcName == eCSSKeyword_fit_content,
"Expected minmax() or fit-content() function");
return false; // fit-content() is not a <fixed-size>
}
} else if (!list->mValue.IsLengthPercentCalcUnit()) {
return false;
}
list = list->mNext; // skip line names
}
}
} else {
UngetToken();
// Not a repeat() function; try to parse <track-size> | <fixed-size>.
nsCSSValue trackSize;
GridTrackSizeFlags flags = haveRepeatAuto
? GridTrackSizeFlags::eFixedTrackSize
: GridTrackSizeFlags::eDefaultTrackSize;
CSSParseResult result = ParseGridTrackSize(trackSize, flags);
if (result == CSSParseResult::Error) {
return false;
}
if (result == CSSParseResult::NotFound) {
// What we've parsed so far is a valid <track-list>
// (modulo the "at least one <track-size>" check below.)
// Stop here.
break;
}
item->mNext = new nsCSSValueList;
item = item->mNext;
item->mValue = trackSize;
item->mNext = new nsCSSValueList;
item = item->mNext;
}
if (ParseGridLineNames(item->mValue) == CSSParseResult::Error) {
return false;
}
}
// Require at least one <track-size>.
if (item == firstLineNamesItem) {
return false;
}
MOZ_ASSERT(aValue.GetListValue() &&
aValue.GetListValue()->mNext &&
aValue.GetListValue()->mNext->mNext,
"<track-list> should have a minimum length of 3");
return true;
}
// Takes ownership of |aSecond|
static void
ConcatLineNames(nsCSSValue& aFirst, nsCSSValue& aSecond)
{
if (aSecond.GetUnit() == eCSSUnit_Null) {
// Nothing to do.
return;
}
if (aFirst.GetUnit() == eCSSUnit_Null) {
// Empty or omitted <line-names>. Replace it.
aFirst = aSecond;
return;
}
// Join the two <line-names> lists.
nsCSSValueList* source = aSecond.GetListValue();
nsCSSValueList* target = aFirst.GetListValue();
// Find the end:
while (target->mNext) {
target = target->mNext;
}
// Copy the first name. We can't take ownership of it
// as it'll be destroyed when |aSecond| goes out of scope.
target->mNext = new nsCSSValueList;
target = target->mNext;
target->mValue = source->mValue;
// Move the rest of the linked list.
target->mNext = source->mNext;
source->mNext = nullptr;
}
// Assuming the 'repeat(' function token has already been consumed,
// parse "repeat( <positive-integer> | auto-fill | auto-fit ,"
// (or "repeat( <positive-integer> | auto-fill ," when aForSubgrid is true)
// and stop after the comma. Return true when parsing succeeds,
// with aRepetitions set to the number of repetitions and aRepeatAutoEnum set
// to an enum value for auto-fill | auto-fit (it's not set at all when
// <positive-integer> was parsed).
bool
CSSParserImpl::ParseGridTrackRepeatIntro(bool aForSubgrid,
int32_t* aRepetitions,
Maybe<int32_t>* aRepeatAutoEnum)
{
if (!GetToken(true)) {
return false;
}
if (mToken.mType == eCSSToken_Ident) {
if (mToken.mIdent.LowerCaseEqualsLiteral("auto-fill")) {
aRepeatAutoEnum->emplace(NS_STYLE_GRID_REPEAT_AUTO_FILL);
} else if (!aForSubgrid &&
mToken.mIdent.LowerCaseEqualsLiteral("auto-fit")) {
aRepeatAutoEnum->emplace(NS_STYLE_GRID_REPEAT_AUTO_FIT);
} else {
return false;
}
*aRepetitions = 1;
} else if (mToken.mType == eCSSToken_Number) {
if (!(mToken.mIntegerValid &&
mToken.mInteger > 0)) {
return false;
}
*aRepetitions = std::min(mToken.mInteger, GRID_TEMPLATE_MAX_REPETITIONS);
} else {
return false;
}
if (!ExpectSymbol(',', true)) {
return false;
}
return true;
}
// Assuming the 'repeat(' function token has already been consumed,
// parse the rest of
// repeat( <positive-integer> | auto-fill | auto-fit ,
// [ <line-names>? <track-size> ]+ <line-names>? )
// Append to the linked list whose end is given by |aTailPtr|,
// and update |aTailPtr| to point to the new end of the list.
// Note: only one <track-size> is allowed for auto-fill/fit
bool
CSSParserImpl::ParseGridTrackListRepeat(nsCSSValueList** aTailPtr)
{
int32_t repetitions;
Maybe<int32_t> repeatAutoEnum;
if (!ParseGridTrackRepeatIntro(false, &repetitions, &repeatAutoEnum)) {
return false;
}
// Parse [ <line-names>? <track-size> ]+ <line-names>?
// but keep the first and last <line-names> separate
// because they'll need to be joined.
// http://dev.w3.org/csswg/css-grid/#repeat-notation
nsCSSValue firstLineNames;
nsCSSValue trackSize;
nsCSSValue lastLineNames;
// Optional
if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error) {
return false;
}
// Required
GridTrackSizeFlags flags = repeatAutoEnum.isSome()
? GridTrackSizeFlags::eFixedTrackSize
: GridTrackSizeFlags::eDefaultTrackSize;
if (ParseGridTrackSize(trackSize, flags) != CSSParseResult::Ok) {
return false;
}
// Use nsAutoPtr to free the list in case of early return.
nsAutoPtr<nsCSSValueList> firstTrackSizeItemAuto(new nsCSSValueList);
firstTrackSizeItemAuto->mValue = trackSize;
nsCSSValueList* item = firstTrackSizeItemAuto;
for (;;) {
// Optional
if (ParseGridLineNames(lastLineNames) == CSSParseResult::Error) {
return false;
}
if (ExpectSymbol(')', true)) {
break;
}
// <auto-repeat> only accepts a single track size:
// <line-names>? <fixed-size> <line-names>?
if (repeatAutoEnum.isSome()) {
REPORT_UNEXPECTED(PEMoreThanOneGridRepeatTrackSize);
return false;
}
// Required
if (ParseGridTrackSize(trackSize) != CSSParseResult::Ok) {
return false;
}
item->mNext = new nsCSSValueList;
item = item->mNext;
item->mValue = lastLineNames;
// Do not append to this list at the next iteration.
lastLineNames.Reset();
item->mNext = new nsCSSValueList;
item = item->mNext;
item->mValue = trackSize;
}
nsCSSValueList* lastTrackSizeItem = item;
// [ <line-names>? <track-size> ]+ <line-names>? is now parsed into:
// * firstLineNames: the first <line-names>
// * a linked list of odd length >= 1, from firstTrackSizeItem
// (the first <track-size>) to lastTrackSizeItem (the last),
// with the <line-names> sublists in between
// * lastLineNames: the last <line-names>
if (repeatAutoEnum.isSome()) {
// Instead of hooking up this list into the flat track/name list as usual,
// we create a pair(Int, List) where the first value is the auto-fill/fit
// keyword and the second is the list to repeat. There are three items
// in this list, the first is the list of line names before the track size,
// the second item is the track size, and the last item is the list of line
// names after the track size. Note that the line names are NOT merged
// with any line names before/after the repeat() itself.
nsCSSValue listValue;
nsCSSValueList* list = listValue.SetListValue();
list->mValue = firstLineNames;
list = list->mNext = new nsCSSValueList;
list->mValue = trackSize;
list = list->mNext = new nsCSSValueList;
list->mValue = lastLineNames;
nsCSSValue kwd;
kwd.SetIntValue(repeatAutoEnum.value(), eCSSUnit_Enumerated);
*aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList;
(*aTailPtr)->mValue.SetPairValue(kwd, listValue);
// Append an empty list since the caller expects that to represent the names
// that follows the repeat() function.
*aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList;
return true;
}
// Join the last and first <line-names> (in that order.)
// For example, repeat(3, (a) 100px (b) 200px (c)) results in
// (a) 100px (b) 200px (c a) 100px (b) 200px (c a) 100px (b) 200px (c)
// This is (c a).
// Make deep copies: the originals will be moved.
nsCSSValue joinerLineNames;
{
nsCSSValueList* target = nullptr;
if (lastLineNames.GetUnit() != eCSSUnit_Null) {
target = joinerLineNames.SetListValue();
nsCSSValueList* source = lastLineNames.GetListValue();
for (;;) {
target->mValue = source->mValue;
source = source->mNext;
if (!source) {
break;
}
target->mNext = new nsCSSValueList;
target = target->mNext;
}
}
if (firstLineNames.GetUnit() != eCSSUnit_Null) {
if (target) {
target->mNext = new nsCSSValueList;
target = target->mNext;
} else {
target = joinerLineNames.SetListValue();
}
nsCSSValueList* source = firstLineNames.GetListValue();
for (;;) {
target->mValue = source->mValue;
source = source->mNext;
if (!source) {
break;
}
target->mNext = new nsCSSValueList;
target = target->mNext;
}
}
}
// Join our first <line-names> with the one before repeat().
// (a) repeat(1, (b) 20px) expands to (a b) 20px
nsCSSValueList* previousItemBeforeRepeat = *aTailPtr;
ConcatLineNames(previousItemBeforeRepeat->mValue, firstLineNames);
// Move our linked list
// (first to last <track-size>, with the <line-names> sublists in between).
// This is the first repetition.
NS_ASSERTION(previousItemBeforeRepeat->mNext == nullptr,
"Expected the end of a linked list");
previousItemBeforeRepeat->mNext = firstTrackSizeItemAuto.forget();
nsCSSValueList* firstTrackSizeItem = previousItemBeforeRepeat->mNext;
nsCSSValueList* tail = lastTrackSizeItem;
// Repeat |repetitions - 1| more times:
// * the joiner <line-names>
// * the linked list
// (first to last <track-size>, with the <line-names> sublists in between)
MOZ_ASSERT(repetitions > 0, "Expected positive repetitions");
while (--repetitions) {
tail->mNext = new nsCSSValueList;
tail = tail->mNext;
tail->mValue = joinerLineNames;
nsCSSValueList* repeatedItem = firstTrackSizeItem;
for (;;) {
tail->mNext = new nsCSSValueList;
tail = tail->mNext;
tail->mValue = repeatedItem->mValue;
if (repeatedItem == lastTrackSizeItem) {
break;
}
repeatedItem = repeatedItem->mNext;
}
}
// Finally, move our last <line-names>.
// Any <line-names> immediately after repeat() will append to it.
tail->mNext = new nsCSSValueList;
tail = tail->mNext;
tail->mValue = lastLineNames;
*aTailPtr = tail;
return true;
}
bool
CSSParserImpl::ParseGridTrackList(nsCSSPropertyID aPropID,
GridTrackListFlags aFlags)
{
nsCSSValue value;
nsCSSValue firstLineNames;
if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error ||
!ParseGridTrackListWithFirstLineNames(value, firstLineNames, aFlags)) {
return false;
}
AppendValue(aPropID, value);
return true;
}
bool
CSSParserImpl::ParseGridTemplateColumnsRows(nsCSSPropertyID aPropID)
{
nsCSSValue value;
if (ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) {
AppendValue(aPropID, value);
return true;
}
nsAString* ident = NextIdent();
if (ident) {
if (ident->LowerCaseEqualsLiteral("subgrid")) {
if (!nsLayoutUtils::IsGridTemplateSubgridValueEnabled()) {
REPORT_UNEXPECTED(PESubgridNotSupported);
return false;
}
if (!ParseOptionalLineNameListAfterSubgrid(value)) {
return false;
}
AppendValue(aPropID, value);
return true;
}
UngetToken();
}
return ParseGridTrackList(aPropID);
}
bool
CSSParserImpl::ParseGridTemplateAreasLine(const nsAutoString& aInput,
css::GridTemplateAreasValue* aAreas,
nsDataHashtable<nsStringHashKey, uint32_t>& aAreaIndices)
{
aAreas->mTemplates.AppendElement(mToken.mIdent);
nsCSSGridTemplateAreaScanner scanner(aInput);
nsCSSGridTemplateAreaToken token;
css::GridNamedArea* currentArea = nullptr;
uint32_t row = aAreas->NRows();
// Column numbers starts at 1, but we might not have any, eg
// grid-template-areas:""; which will result in mNColumns == 0.
uint32_t column = 0;
while (scanner.Next(token)) {
++column;
if (token.isTrash) {
return false;
}
if (currentArea) {
if (token.mName == currentArea->mName) {
if (currentArea->mRowStart == row) {
// Next column in the first row of this named area.
currentArea->mColumnEnd++;
}
continue;
}
// We're exiting |currentArea|, so currentArea is ending at |column|.
// Make sure that this is consistent with currentArea on previous rows:
if (currentArea->mColumnEnd != column) {
NS_ASSERTION(currentArea->mRowStart != row,
"Inconsistent column end for the first row of a named area.");
// Not a rectangle
return false;
}
currentArea = nullptr;
}
if (!token.mName.IsEmpty()) {
// Named cell that doesn't have a cell with the same name on its left.
// Check if this is the continuation of an existing named area:
uint32_t index;
if (aAreaIndices.Get(token.mName, &index)) {
MOZ_ASSERT(index < aAreas->mNamedAreas.Length(),
"Invalid aAreaIndices hash table");
currentArea = &aAreas->mNamedAreas[index];
if (currentArea->mColumnStart != column ||
currentArea->mRowEnd != row) {
// Existing named area, but not forming a rectangle
return false;
}
// Next row of an existing named area
currentArea->mRowEnd++;
} else {
// New named area
aAreaIndices.Put(token.mName, aAreas->mNamedAreas.Length());
currentArea = aAreas->mNamedAreas.AppendElement();
currentArea->mName = token.mName;
// For column or row N (starting at 1),
// the start line is N, the end line is N + 1
currentArea->mColumnStart = column;
currentArea->mColumnEnd = column + 1;
currentArea->mRowStart = row;
currentArea->mRowEnd = row + 1;
}
}
}
if (currentArea && currentArea->mColumnEnd != column + 1) {
NS_ASSERTION(currentArea->mRowStart != row,
"Inconsistent column end for the first row of a named area.");
// Not a rectangle
return false;
}
// On the first row, set the number of columns
// that grid-template-areas contributes to the explicit grid.
// On other rows, check that the number of columns is consistent
// between rows.
if (row == 1) {
aAreas->mNColumns = column;
} else if (aAreas->mNColumns != column) {
return false;
}
return true;
}
bool
CSSParserImpl::ParseGridTemplateAreas()
{
nsCSSValue value;
if (ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) {
AppendValue(eCSSProperty_grid_template_areas, value);
return true;
}
RefPtr<css::GridTemplateAreasValue> areas =
new css::GridTemplateAreasValue();
nsDataHashtable<nsStringHashKey, uint32_t> areaIndices;
for (;;) {
if (!GetToken(true)) {
break;
}
if (eCSSToken_String != mToken.mType) {
UngetToken();
break;
}
if (!ParseGridTemplateAreasLine(mToken.mIdent, areas, areaIndices)) {
return false;
}
}
if (areas->NRows() == 0) {
return false;
}
AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(areas));
return true;
}
// [ auto-flow && dense? ] <'grid-auto-columns'>? |
// <'grid-template-columns'>
bool
CSSParserImpl::ParseGridTemplateColumnsOrAutoFlow(bool aForGridShorthand)
{
if (aForGridShorthand) {
auto res = ParseGridShorthandAutoProps(NS_STYLE_GRID_AUTO_FLOW_COLUMN);
if (res == CSSParseResult::Error) {
return false;
}
if (res == CSSParseResult::Ok) {
nsCSSValue value(eCSSUnit_None);
AppendValue(eCSSProperty_grid_template_columns, value);
return true;
}
}
return ParseGridTemplateColumnsRows(eCSSProperty_grid_template_columns);
}
bool
CSSParserImpl::ParseGridTemplate(bool aForGridShorthand)
{
// none |
// subgrid |
// <'grid-template-rows'> / <'grid-template-columns'> |
// [ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list>]?
// or additionally when aForGridShorthand is true:
// <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>?
nsCSSValue value;
if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
AppendValue(eCSSProperty_grid_template_areas, value);
AppendValue(eCSSProperty_grid_template_rows, value);
AppendValue(eCSSProperty_grid_template_columns, value);
return true;
}
// 'none' can appear either by itself,
// or as the beginning of <'grid-template-rows'> / <'grid-template-columns'>
if (ParseSingleTokenVariant(value, VARIANT_NONE, nullptr)) {
AppendValue(eCSSProperty_grid_template_rows, value);
AppendValue(eCSSProperty_grid_template_areas, value);
if (ExpectSymbol('/', true)) {
return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand);
}
AppendValue(eCSSProperty_grid_template_columns, value);
return true;
}
// 'subgrid' can appear either by itself,
// or as the beginning of <'grid-template-rows'> / <'grid-template-columns'>
nsAString* ident = NextIdent();
if (ident) {
if (ident->LowerCaseEqualsLiteral("subgrid")) {
if (!nsLayoutUtils::IsGridTemplateSubgridValueEnabled()) {
REPORT_UNEXPECTED(PESubgridNotSupported);
return false;
}
if (!ParseOptionalLineNameListAfterSubgrid(value)) {
return false;
}
AppendValue(eCSSProperty_grid_template_rows, value);
AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(eCSSUnit_None));
if (ExpectSymbol('/', true)) {
return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand);
}
if (value.GetListValue()->mNext) {
// Non-empty <line-name-list> after 'subgrid'.
// This is only valid as part of <'grid-template-rows'>,
// which must be followed by a slash.
return false;
}
// 'subgrid' by itself sets both grid-template-rows/columns.
AppendValue(eCSSProperty_grid_template_columns, value);
return true;
}
UngetToken();
}
// [ <line-names>? ] here is ambiguous:
// it can be either the start of a <track-list> (in a <'grid-template-rows'>),
// or the start of [ <line-names>? <string> <track-size>? <line-names>? ]+
nsCSSValue firstLineNames;
if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error ||
!GetToken(true)) {
return false;
}
if (mToken.mType == eCSSToken_String) {
// It's the [ <line-names>? <string> <track-size>? <line-names>? ]+ case.
if (!ParseGridTemplateAfterString(firstLineNames)) {
return false;
}
// Parse an optional [ / <explicit-track-list> ] as the columns value.
if (ExpectSymbol('/', true)) {
return ParseGridTrackList(eCSSProperty_grid_template_columns,
GridTrackListFlags::eExplicitTrackList);
}
value.SetNoneValue(); // absent means 'none'
AppendValue(eCSSProperty_grid_template_columns, value);
return true;
}
UngetToken();
// Finish parsing <'grid-template-rows'> with the |firstLineNames| we have,
// and then parse a mandatory [ / <'grid-template-columns'> ].
if (!ParseGridTrackListWithFirstLineNames(value, firstLineNames) ||
!ExpectSymbol('/', true)) {
return false;
}
AppendValue(eCSSProperty_grid_template_rows, value);
value.SetNoneValue();
AppendValue(eCSSProperty_grid_template_areas, value);
return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand);
}
// Helper for parsing the 'grid-template' shorthand:
// Parse [ <line-names>? <string> <track-size>? <line-names>? ]+
// with a <line-names>? already consumed, stored in |aFirstLineNames|,
// and the current token a <string>
bool
CSSParserImpl::ParseGridTemplateAfterString(const nsCSSValue& aFirstLineNames)
{
MOZ_ASSERT(mToken.mType == eCSSToken_String,
"ParseGridTemplateAfterString called with a non-string token");
nsCSSValue rowsValue;
RefPtr<css::GridTemplateAreasValue> areas =
new css::GridTemplateAreasValue();
nsDataHashtable<nsStringHashKey, uint32_t> areaIndices;
nsCSSValueList* rowsItem = rowsValue.SetListValue();
rowsItem->mValue = aFirstLineNames;
for (;;) {
if (!ParseGridTemplateAreasLine(mToken.mIdent, areas, areaIndices)) {
return false;
}
rowsItem->mNext = new nsCSSValueList;
rowsItem = rowsItem->mNext;
CSSParseResult result = ParseGridTrackSize(rowsItem->mValue);
if (result == CSSParseResult::Error) {
return false;
}
if (result == CSSParseResult::NotFound) {
rowsItem->mValue.SetAutoValue();
}
rowsItem->mNext = new nsCSSValueList;
rowsItem = rowsItem->mNext;
result = ParseGridLineNames(rowsItem->mValue);
if (result == CSSParseResult::Error) {
return false;
}
if (result == CSSParseResult::Ok) {
// Append to the same list as the previous call to ParseGridLineNames.
result = ParseGridLineNames(rowsItem->mValue);
if (result == CSSParseResult::Error) {
return false;
}
if (result == CSSParseResult::Ok) {
// Parsed <line-name> twice.
// The property value can not end here, we expect a string next.
if (!GetToken(true)) {
return false;
}
if (eCSSToken_String != mToken.mType) {
UngetToken();
return false;
}
continue;
}
}
// Did not find a <line-names>.
// Next, we expect either a string or the end of the property value.
if (!GetToken(true)) {
break;
}
if (eCSSToken_String != mToken.mType) {
UngetToken();
break;
}
}
AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(areas));
AppendValue(eCSSProperty_grid_template_rows, rowsValue);
return true;
}
// <'grid-template'> |
// <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? |
// [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
bool
CSSParserImpl::ParseGrid()
{
nsCSSValue value;
if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
for (const nsCSSPropertyID* subprops =
nsCSSProps::SubpropertyEntryFor(eCSSProperty_grid);
*subprops != eCSSProperty_UNKNOWN; ++subprops) {
AppendValue(*subprops, value);
}
return true;
}
// [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
auto res = ParseGridShorthandAutoProps(NS_STYLE_GRID_AUTO_FLOW_ROW);
if (res == CSSParseResult::Error) {
return false;
}
if (res == CSSParseResult::Ok) {
value.SetAutoValue();
AppendValue(eCSSProperty_grid_auto_columns, value);
nsCSSValue none(eCSSUnit_None);
AppendValue(eCSSProperty_grid_template_areas, none);
AppendValue(eCSSProperty_grid_template_rows, none);
if (!ExpectSymbol('/', true)) {
return false;
}
return ParseGridTemplateColumnsRows(eCSSProperty_grid_template_columns);
}
// Set remaining subproperties that might not be set by ParseGridTemplate to
// their initial values and then parse <'grid-template'> |
// <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? .
value.SetIntValue(NS_STYLE_GRID_AUTO_FLOW_ROW, eCSSUnit_Enumerated);
AppendValue(eCSSProperty_grid_auto_flow, value);
value.SetAutoValue();
AppendValue(eCSSProperty_grid_auto_rows, value);
AppendValue(eCSSProperty_grid_auto_columns, value);
return ParseGridTemplate(true);
}
// Parse [ auto-flow && dense? ] <'grid-auto-[rows|columns]'>? for the 'grid'
// shorthand. If aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW then we're
// parsing row values, otherwise column values.
CSSParseResult
CSSParserImpl::ParseGridShorthandAutoProps(int32_t aAutoFlowAxis)
{
MOZ_ASSERT(aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW ||
aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_COLUMN);
if (!GetToken(true)) {
return CSSParseResult::NotFound;
}
// [ auto-flow && dense? ]
int32_t autoFlowValue = 0;
if (mToken.mType == eCSSToken_Ident) {
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
if (keyword == eCSSKeyword_auto_flow) {
autoFlowValue = aAutoFlowAxis;
if (GetToken(true)) {
if (mToken.mType == eCSSToken_Ident &&
nsCSSKeywords::LookupKeyword(mToken.mIdent) == eCSSKeyword_dense) {
autoFlowValue |= NS_STYLE_GRID_AUTO_FLOW_DENSE;
} else {
UngetToken();
}
}
} else if (keyword == eCSSKeyword_dense) {
if (!GetToken(true)) {
return CSSParseResult::Error;
}
if (mToken.mType != eCSSToken_Ident ||
nsCSSKeywords::LookupKeyword(mToken.mIdent) != eCSSKeyword_auto_flow) {
UngetToken();
return CSSParseResult::Error;
}
autoFlowValue = aAutoFlowAxis | NS_STYLE_GRID_AUTO_FLOW_DENSE;
}
}
if (autoFlowValue) {
nsCSSValue value;
value.SetIntValue(autoFlowValue, eCSSUnit_Enumerated);
AppendValue(eCSSProperty_grid_auto_flow, value);
} else {
UngetToken();
return CSSParseResult::NotFound;
}
// <'grid-auto-[rows|columns]'>?
nsCSSValue autoTrackValue;
CSSParseResult result = ParseGridTrackSize(autoTrackValue);
if (result == CSSParseResult::Error) {
return result;
}
if (result == CSSParseResult::NotFound) {
autoTrackValue.SetAutoValue();
}
AppendValue(aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW ?
eCSSProperty_grid_auto_rows : eCSSProperty_grid_auto_columns,
autoTrackValue);
return CSSParseResult::Ok;
}
// Parse a <grid-line>.
// If successful, set aValue to eCSSUnit_Auto,
// or a eCSSUnit_List containing, in that order:
//
// * An optional eCSSUnit_Enumerated marking a "span" keyword.
// * An optional eCSSUnit_Integer
// * An optional eCSSUnit_Ident
//
// At least one of eCSSUnit_Integer or eCSSUnit_Ident is present.
bool
CSSParserImpl::ParseGridLine(nsCSSValue& aValue)
{
// <grid-line> =
// auto |
// <custom-ident> |
// [ <integer> && <custom-ident>? ] |
// [ span && [ <integer> || <custom-ident> ] ]
//
// Syntactically, this simplifies to:
//
// <grid-line> =
// auto |
// [ span? && [ <integer> || <custom-ident> ] ]
if (ParseSingleTokenVariant(aValue, VARIANT_AUTO, nullptr)) {
return true;
}
bool hasSpan = false;
bool hasIdent = false;
Maybe<int32_t> integer;
nsCSSValue ident;
#ifdef MOZ_VALGRIND
// Make the contained value be defined even though we really want a
// Nothing here. This works around an otherwise difficult to avoid
// Memcheck false positive when this is compiled by gcc-5.3 -O2.
// See bug 1301856.
integer.emplace(0);
integer.reset();
#endif
if (!GetToken(true)) {
return false;
}
if (mToken.mType == eCSSToken_Ident &&
mToken.mIdent.LowerCaseEqualsLiteral("span")) {
hasSpan = true;
if (!GetToken(true)) {
return false;
}
}
do {
if (!hasIdent &&
mToken.mType == eCSSToken_Ident &&
ParseCustomIdent(ident, mToken.mIdent, kGridLineKeywords)) {
hasIdent = true;
} else if (integer.isNothing() &&
mToken.mType == eCSSToken_Number &&
mToken.mIntegerValid &&
mToken.mInteger != 0) {
integer.emplace(mToken.mInteger);
} else {
UngetToken();
break;
}
} while (!(integer.isSome() && hasIdent) && GetToken(true));
// Require at least one of <integer> or <custom-ident>
if (!(integer.isSome() || hasIdent)) {
return false;
}
if (!hasSpan && GetToken(true)) {
if (mToken.mType == eCSSToken_Ident &&
mToken.mIdent.LowerCaseEqualsLiteral("span")) {
hasSpan = true;
} else {
UngetToken();
}
}
nsCSSValueList* item = aValue.SetListValue();
if (hasSpan) {
// Given "span", a negative <integer> is invalid.
if (integer.isSome() && integer.ref() < 0) {
return false;
}
// '1' here is a dummy value.
// The mere presence of eCSSUnit_Enumerated indicates a "span" keyword.
item->mValue.SetIntValue(1, eCSSUnit_Enumerated);
item->mNext = new nsCSSValueList;
item = item->mNext;
}
if (integer.isSome()) {
item->mValue.SetIntValue(integer.ref(), eCSSUnit_Integer);
if (hasIdent) {
item->mNext = new nsCSSValueList;
item = item->mNext;
}
}
if (hasIdent) {
item->mValue = ident;
}
return true;
}
bool
CSSParserImpl::ParseGridColumnRowStartEnd(nsCSSPropertyID aPropID)
{
nsCSSValue value;
if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr) ||
ParseGridLine(value)) {
AppendValue(aPropID, value);
return true;
}
return false;
}
// If |aFallback| is a List containing a single Ident, set |aValue| to that.
// Otherwise, set |aValue| to Auto.
// Used with |aFallback| from ParseGridLine()
static void
HandleGridLineFallback(const nsCSSValue& aFallback, nsCSSValue& aValue)
{
if (aFallback.GetUnit() == eCSSUnit_List &&
aFallback.GetListValue()->mValue.GetUnit() == eCSSUnit_Ident &&
!aFallback.GetListValue()->mNext) {
aValue = aFallback;
} else {
aValue.SetAutoValue();
}
}
bool
CSSParserImpl::ParseGridColumnRow(nsCSSPropertyID aStartPropID,
nsCSSPropertyID aEndPropID)
{
nsCSSValue value;
nsCSSValue secondValue;
if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
AppendValue(aStartPropID, value);
AppendValue(aEndPropID, value);
return true;
}
if (!ParseGridLine(value)) {
return false;
}
if (GetToken(true)) {
if (mToken.IsSymbol('/')) {
if (ParseGridLine(secondValue)) {
AppendValue(aStartPropID, value);
AppendValue(aEndPropID, secondValue);
return true;
} else {
return false;
}
}
UngetToken();
}
// A single <custom-ident> is repeated to both properties,
// anything else sets the grid-{column,row}-end property to 'auto'.
HandleGridLineFallback(value, secondValue);
AppendValue(aStartPropID, value);
AppendValue(aEndPropID, secondValue);
return true;
}
bool
CSSParserImpl::ParseGridArea()
{
nsCSSValue values[4];
if (ParseSingleTokenVariant(values[0], VARIANT_INHERIT, nullptr)) {
AppendValue(eCSSProperty_grid_row_start, values[0]);
AppendValue(eCSSProperty_grid_column_start, values[0]);
AppendValue(eCSSProperty_grid_row_end, values[0]);
AppendValue(eCSSProperty_grid_column_end, values[0]);
return true;
}
int32_t i = 0;
for (;;) {
if (!ParseGridLine(values[i])) {
return false;
}
if (++i == 4 || !GetToken(true)) {
break;
}
if (!mToken.IsSymbol('/')) {
UngetToken();
break;
}
}
MOZ_ASSERT(i >= 1, "should have parsed at least one grid-line (or returned)");
if (i < 2) {
HandleGridLineFallback(values[0], values[1]);
}
if (i < 3) {
HandleGridLineFallback(values[0], values[2]);
}
if (i < 4) {
HandleGridLineFallback(values[1], values[3]);
}
AppendValue(eCSSProperty_grid_row_start, values[0]);
AppendValue(eCSSProperty_grid_column_start, values[1]);
AppendValue(eCSSProperty_grid_row_end, values[2]);
AppendValue(eCSSProperty_grid_column_end, values[3]);
return true;
}
bool
CSSParserImpl::ParseGridGap()
{
nsCSSValue first;
if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) {
AppendValue(eCSSProperty_grid_row_gap, first);
AppendValue(eCSSProperty_grid_column_gap, first);
return true;
}
if (ParseNonNegativeVariant(first, VARIANT_LPCALC, nullptr) !=
CSSParseResult::Ok) {
return false;
}
nsCSSValue second;
auto result = ParseNonNegativeVariant(second, VARIANT_LPCALC, nullptr);
if (result == CSSParseResult::Error) {
return false;
}
AppendValue(eCSSProperty_grid_row_gap, first);
AppendValue(eCSSProperty_grid_column_gap,
result == CSSParseResult::NotFound ? first : second);
return true;
}
// normal | [<number> <integer>?]
bool
CSSParserImpl::ParseInitialLetter()
{
nsCSSValue value;
// 'inherit', 'initial', 'unset', 'none', and 'normal' must be alone
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NORMAL,
nullptr)) {
nsCSSValue first, second;
if (!ParseOneOrLargerNumber(first)) {
return false;
}
if (!ParseOneOrLargerInteger(second)) {
AppendValue(eCSSProperty_initial_letter, first);
return true;
} else {
RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(2);
val->Item(0) = first;
val->Item(1) = second;
value.SetArrayValue(val, eCSSUnit_Array);
}
}
AppendValue(eCSSProperty_initial_letter, value);
return true;
}
// [ $aTable && <overflow-position>? ] ?
// $aTable is for <content-position> or <self-position>
bool
CSSParserImpl::ParseAlignJustifyPosition(nsCSSValue& aResult,
const KTableEntry aTable[])
{
nsCSSValue pos, overflowPos;
int32_t value = 0;
if (ParseEnum(pos, aTable)) {
value = pos.GetIntValue();
if (ParseEnum(overflowPos, nsCSSProps::kAlignOverflowPosition)) {
value |= overflowPos.GetIntValue();
}
aResult.SetIntValue(value, eCSSUnit_Enumerated);
return true;
}
if (ParseEnum(overflowPos, nsCSSProps::kAlignOverflowPosition)) {
if (ParseEnum(pos, aTable)) {
aResult.SetIntValue(pos.GetIntValue() | overflowPos.GetIntValue(),
eCSSUnit_Enumerated);
return true;
}
return false; // <overflow-position> must be followed by a value in $table
}
return true;
}
// auto | normal | stretch | <baseline-position> |
// [ <self-position> && <overflow-position>? ] |
// [ legacy && [ left | right | center ] ]
bool
CSSParserImpl::ParseJustifyItems()
{
nsCSSValue value;
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
if (MOZ_UNLIKELY(ParseEnum(value, nsCSSProps::kAlignLegacy))) {
nsCSSValue legacy;
if (!ParseEnum(legacy, nsCSSProps::kAlignLegacyPosition)) {
return false; // leading 'legacy' not followed by 'left' etc is an error
}
value.SetIntValue(value.GetIntValue() | legacy.GetIntValue(),
eCSSUnit_Enumerated);
} else {
if (!ParseAlignEnum(value, nsCSSProps::kAlignAutoNormalStretchBaseline)) {
if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) ||
value.GetUnit() == eCSSUnit_Null) {
return false;
}
// check for a trailing 'legacy' after 'left' etc
auto val = value.GetIntValue();
if (val == NS_STYLE_JUSTIFY_CENTER ||
val == NS_STYLE_JUSTIFY_LEFT ||
val == NS_STYLE_JUSTIFY_RIGHT) {
nsCSSValue legacy;
if (ParseEnum(legacy, nsCSSProps::kAlignLegacy)) {
value.SetIntValue(val | legacy.GetIntValue(), eCSSUnit_Enumerated);
}
}
}
}
}
AppendValue(eCSSProperty_justify_items, value);
return true;
}
// normal | stretch | <baseline-position> |
// [ <overflow-position>? && <self-position> ]
bool
CSSParserImpl::ParseAlignItems()
{
nsCSSValue value;
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
if (!ParseAlignEnum(value, nsCSSProps::kAlignNormalStretchBaseline)) {
if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) ||
value.GetUnit() == eCSSUnit_Null) {
return false;
}
}
}
AppendValue(eCSSProperty_align_items, value);
return true;
}
// auto | normal | stretch | <baseline-position> |
// [ <overflow-position>? && <self-position> ]
bool
CSSParserImpl::ParseAlignJustifySelf(nsCSSPropertyID aPropID)
{
nsCSSValue value;
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
if (!ParseAlignEnum(value, nsCSSProps::kAlignAutoNormalStretchBaseline)) {
if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) ||
value.GetUnit() == eCSSUnit_Null) {
return false;
}
}
}
AppendValue(aPropID, value);
return true;
}
// normal | <baseline-position> | [ <content-distribution> ||
// [ <overflow-position>? && <content-position> ] ]
// (the part after the || is called <*-position> below)
bool
CSSParserImpl::ParseAlignJustifyContent(nsCSSPropertyID aPropID)
{
nsCSSValue value;
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
if (!ParseAlignEnum(value, nsCSSProps::kAlignNormalBaseline)) {
nsCSSValue fallbackValue;
if (!ParseEnum(value, nsCSSProps::kAlignContentDistribution)) {
if (!ParseAlignJustifyPosition(fallbackValue,
nsCSSProps::kAlignContentPosition) ||
fallbackValue.GetUnit() == eCSSUnit_Null) {
return false;
}
// optional <content-distribution> after <*-position> ...
if (!ParseEnum(value, nsCSSProps::kAlignContentDistribution)) {
// ... is missing so the <*-position> is the value, not the fallback
value = fallbackValue;
fallbackValue.Reset();
}
} else {
// any optional <*-position> is a fallback value
if (!ParseAlignJustifyPosition(fallbackValue,
nsCSSProps::kAlignContentPosition)) {
return false;
}
}
if (fallbackValue.GetUnit() != eCSSUnit_Null) {
auto fallback = fallbackValue.GetIntValue();
value.SetIntValue(value.GetIntValue() |
(fallback << NS_STYLE_ALIGN_ALL_SHIFT),
eCSSUnit_Enumerated);
}
}
}
AppendValue(aPropID, value);
return true;
}
// place-content: [ normal | <baseline-position> | <content-distribution> |
// <content-position> ]{1,2}
bool
CSSParserImpl::ParsePlaceContent()
{
nsCSSValue first;
if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) {
AppendValue(eCSSProperty_align_content, first);
AppendValue(eCSSProperty_justify_content, first);
return true;
}
if (!ParseAlignEnum(first, nsCSSProps::kAlignNormalBaseline) &&
!ParseEnum(first, nsCSSProps::kAlignContentDistribution) &&
!ParseEnum(first, nsCSSProps::kAlignContentPosition)) {
return false;
}
AppendValue(eCSSProperty_align_content, first);
nsCSSValue second;
if (!ParseAlignEnum(second, nsCSSProps::kAlignNormalBaseline) &&
!ParseEnum(second, nsCSSProps::kAlignContentDistribution) &&
!ParseEnum(second, nsCSSProps::kAlignContentPosition)) {
AppendValue(eCSSProperty_justify_content, first);
} else {
AppendValue(eCSSProperty_justify_content, second);
}
return true;
}
// place-items: <x> [ auto | <x> ]?
// <x> = [ normal | stretch | <baseline-position> | <self-position> ]
bool
CSSParserImpl::ParsePlaceItems()
{
nsCSSValue first;
if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) {
AppendValue(eCSSProperty_align_items, first);
AppendValue(eCSSProperty_justify_items, first);
return true;
}
if (!ParseAlignEnum(first, nsCSSProps::kAlignNormalStretchBaseline) &&
!ParseEnum(first, nsCSSProps::kAlignSelfPosition)) {
return false;
}
AppendValue(eCSSProperty_align_items, first);
nsCSSValue second;
// Note: 'auto' is valid for justify-items, but not align-items.
if (!ParseAlignEnum(second, nsCSSProps::kAlignAutoNormalStretchBaseline) &&
!ParseEnum(second, nsCSSProps::kAlignSelfPosition)) {
AppendValue(eCSSProperty_justify_items, first);
} else {
AppendValue(eCSSProperty_justify_items, second);
}
return true;
}
// place-self: [ auto | normal | stretch | <baseline-position> |
// <self-position> ]{1,2}
bool
CSSParserImpl::ParsePlaceSelf()
{
nsCSSValue first;
if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) {
AppendValue(eCSSProperty_align_self, first);
AppendValue(eCSSProperty_justify_self, first);
return true;
}
if (!ParseAlignEnum(first, nsCSSProps::kAlignAutoNormalStretchBaseline) &&
!ParseEnum(first, nsCSSProps::kAlignSelfPosition)) {
return false;
}
AppendValue(eCSSProperty_align_self, first);
nsCSSValue second;
if (!ParseAlignEnum(second, nsCSSProps::kAlignAutoNormalStretchBaseline) &&
!ParseEnum(second, nsCSSProps::kAlignSelfPosition)) {
AppendValue(eCSSProperty_justify_self, first);
} else {
AppendValue(eCSSProperty_justify_self, second);
}
return true;
}
// <color-stop> : <color> [ <percentage> | <length> ]?
bool
CSSParserImpl::ParseColorStop(nsCSSValueGradient* aGradient)
{
nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement();
CSSParseResult result = ParseVariant(stop->mColor, VARIANT_COLOR, nullptr);
if (result == CSSParseResult::Error) {
return false;
} else if (result == CSSParseResult::NotFound) {
stop->mIsInterpolationHint = true;
}
// Stop positions do not have to fall between the starting-point and
// ending-point, so we don't use ParseNonNegativeVariant.
result = ParseVariant(stop->mLocation, VARIANT_LP | VARIANT_CALC, nullptr);
if (result == CSSParseResult::Error) {
return false;
} else if (result == CSSParseResult::NotFound) {
if (stop->mIsInterpolationHint) {
return false;
}
stop->mLocation.SetNoneValue();
}
return true;
}
// Helper for ParseLinearGradient -- returns true iff aPosition represents a
// box-position value which was parsed with only edge keywords.
// e.g. "left top", or "bottom", but not "left 10px"
//
// (NOTE: Even though callers may want to exclude explicit "center", we still
// need to allow for _CENTER here, because omitted position-values (e.g. the
// x-component of a value like "top") will have been parsed as being *implicit*
// center. The correct way to disallow *explicit* center is to pass "false" for
// ParseBoxPositionValues()'s "aAllowExplicitCenter" parameter, before you
// call this function.)
static bool
IsBoxPositionStrictlyEdgeKeywords(nsCSSValuePair& aPosition)
{
const nsCSSValue& xValue = aPosition.mXValue;
const nsCSSValue& yValue = aPosition.mYValue;
return (xValue.GetUnit() == eCSSUnit_Enumerated &&
(xValue.GetIntValue() & (NS_STYLE_IMAGELAYER_POSITION_LEFT |
NS_STYLE_IMAGELAYER_POSITION_CENTER |
NS_STYLE_IMAGELAYER_POSITION_RIGHT)) &&
yValue.GetUnit() == eCSSUnit_Enumerated &&
(yValue.GetIntValue() & (NS_STYLE_IMAGELAYER_POSITION_TOP |
NS_STYLE_IMAGELAYER_POSITION_CENTER |
NS_STYLE_IMAGELAYER_POSITION_BOTTOM)));
}
// <gradient>
// : linear-gradient( <linear-gradient-line>? <color-stops> ')'
// | radial-gradient( <radial-gradient-line>? <color-stops> ')'
//
// <linear-gradient-line> : [ to [left | right] || [top | bottom] ] ,
// | <legacy-gradient-line>
// <radial-gradient-line> : [ <shape> || <size> ] [ at <position> ]? ,
// | [ at <position> ] ,
// | <legacy-gradient-line>? <legacy-shape-size>?
// <shape> : circle | ellipse
// <size> : closest-side | closest-corner | farthest-side | farthest-corner
// | <length> | [<length> | <percentage>]{2}
//
// <legacy-gradient-line> : [ <position> || <angle>] ,
//
// <legacy-shape-size> : [ <shape> || <legacy-size> ] ,
// <legacy-size> : closest-side | closest-corner | farthest-side
// | farthest-corner | contain | cover
//
// <color-stops> : <color-stop> , <color-stop> [, <color-stop>]*
bool
CSSParserImpl::ParseLinearGradient(nsCSSValue& aValue,
uint8_t aFlags)
{
RefPtr<nsCSSValueGradient> cssGradient
= new nsCSSValueGradient(false, aFlags & eGradient_Repeating);
if (!GetToken(true)) {
return false;
}
// Check for "to" syntax (but not if parsing a -webkit-linear-gradient)
if (!(aFlags & eGradient_WebkitLegacy) &&
mToken.mType == eCSSToken_Ident &&
mToken.mIdent.LowerCaseEqualsLiteral("to")) {
// "to" syntax doesn't allow explicit "center"
if (!ParseBoxPositionValues(cssGradient->mBgPos, false, false)) {
SkipUntil(')');
return false;
}
// [ to [left | right] || [top | bottom] ] ,
if (!IsBoxPositionStrictlyEdgeKeywords(cssGradient->mBgPos)) {
SkipUntil(')');
return false;
}
if (!ExpectSymbol(',', true)) {
SkipUntil(')');
return false;
}
return ParseGradientColorStops(cssGradient, aValue);
}
if (!(aFlags & eGradient_AnyLegacy)) {
// We're parsing an unprefixed linear-gradient, and we tried & failed to
// parse a 'to' token above. Put the token back & try to re-parse our
// expression as <angle>? <color-stop-list>
UngetToken();
// <angle> ,
if (ParseSingleTokenVariant(cssGradient->mAngle,
VARIANT_ANGLE_OR_ZERO, nullptr) &&
!ExpectSymbol(',', true)) {
SkipUntil(')');
return false;
}
return ParseGradientColorStops(cssGradient, aValue);
}
// If we get here, we're parsing a prefixed linear-gradient expression. Put
// back the first token (which we may have checked for "to" above) and try to
// parse expression as <legacy-gradient-line>? <color-stop-list>
bool haveGradientLine = IsLegacyGradientLine(mToken.mType, mToken.mIdent);
UngetToken();
if (haveGradientLine) {
// Parse a <legacy-gradient-line>
cssGradient->mIsLegacySyntax = true;
cssGradient->mIsMozLegacySyntax = (aFlags & eGradient_MozLegacy);
// In -webkit-linear-gradient expressions (handled below), we need to accept
// unitless 0 for angles, to match WebKit/Blink.
int32_t angleFlags = (aFlags & eGradient_WebkitLegacy) ?
VARIANT_ANGLE | VARIANT_ZERO_ANGLE :
VARIANT_ANGLE;
bool haveAngle =
ParseSingleTokenVariant(cssGradient->mAngle, angleFlags, nullptr);
// If we got an angle, we might now have a comma, ending the gradient-line.
bool haveAngleComma = haveAngle && ExpectSymbol(',', true);
// And in fact, if we're -webkit-linear-gradient and got an angle, there
// *must* be a comma after the angle. (Though -moz-linear-gradient is more
// permissive because it will allow box-position before the comma.)
if (aFlags & eGradient_WebkitLegacy && haveAngle && !haveAngleComma) {
SkipUntil(')');
return false;
}
// If we're prefixed & didn't get an angle + comma, then proceed to parse a
// box-position. (Note that due to the above webkit-specific early-return,
// this will only parse an *initial* box-position inside of
// -webkit-linear-gradient, vs. an initial OR post-angle box-position
// inside of -moz-linear-gradient.)
if (((aFlags & eGradient_AnyLegacy) && !haveAngleComma)) {
// (Note: 3rd arg controls whether the "center" keyword is allowed.
// -moz-linear-gradient allows it; -webkit-linear-gradient does not.)
if (!ParseBoxPositionValues(cssGradient->mBgPos, false,
(aFlags & eGradient_MozLegacy))) {
SkipUntil(')');
return false;
}
// -webkit-linear-gradient only supports edge keywords here.
if ((aFlags & eGradient_WebkitLegacy) &&
!IsBoxPositionStrictlyEdgeKeywords(cssGradient->mBgPos)) {
SkipUntil(')');
return false;
}
if (!ExpectSymbol(',', true) &&
// If we didn't already get an angle, and we're not -webkit prefixed,
// we can parse an angle+comma now. Otherwise it's an error.
(haveAngle ||
(aFlags & eGradient_WebkitLegacy) ||
!ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE,
nullptr) ||
// now we better have a comma
!ExpectSymbol(',', true))) {
SkipUntil(')');
return false;
}
}
}
return ParseGradientColorStops(cssGradient, aValue);
}
bool
CSSParserImpl::ParseRadialGradient(nsCSSValue& aValue,
uint8_t aFlags)
{
RefPtr<nsCSSValueGradient> cssGradient
= new nsCSSValueGradient(true, aFlags & eGradient_Repeating);
// [ <shape> || <size> ]
bool haveShape =
ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
nsCSSProps::kRadialGradientShapeKTable);
bool haveSize =
ParseSingleTokenVariant(cssGradient->GetRadialSize(), VARIANT_KEYWORD,
(aFlags & eGradient_AnyLegacy) ?
nsCSSProps::kRadialGradientLegacySizeKTable :
nsCSSProps::kRadialGradientSizeKTable);
if (haveSize) {
if (!haveShape) {
// <size> <shape>
haveShape =
ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
nsCSSProps::kRadialGradientShapeKTable);
}
} else if (!(aFlags & eGradient_AnyLegacy)) {
// Save RadialShape before parsing RadiusX because RadialShape and
// RadiusX share the storage.
int32_t shape =
cssGradient->GetRadialShape().GetUnit() == eCSSUnit_Enumerated ?
cssGradient->GetRadialShape().GetIntValue() : -1;
// <length> | [<length> | <percentage>]{2}
cssGradient->mIsExplicitSize = true;
haveSize =
ParseSingleTokenNonNegativeVariant(cssGradient->GetRadiusX(), VARIANT_LP,
nullptr);
if (!haveSize) {
// It was not an explicit size after all.
// Note that ParseNonNegativeVariant may have put something
// invalid into our storage, but only in the case where it was
// rejected only for being negative. Since this means the token
// was a length or a percentage, we know it's not valid syntax
// (which must be a comma, the 'at' keyword, or a color), so we
// know this value will be dropped. This means it doesn't matter
// that we have something invalid in our storage.
cssGradient->mIsExplicitSize = false;
} else {
// vertical extent is optional
bool haveYSize =
ParseSingleTokenNonNegativeVariant(cssGradient->GetRadiusY(),
VARIANT_LP, nullptr);
if (!haveShape) {
nsCSSValue shapeValue;
haveShape =
ParseSingleTokenVariant(shapeValue, VARIANT_KEYWORD,
nsCSSProps::kRadialGradientShapeKTable);
if (haveShape) {
shape = shapeValue.GetIntValue();
}
}
if (haveYSize
? shape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR
: cssGradient->GetRadiusX().GetUnit() == eCSSUnit_Percent ||
shape == NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL) {
SkipUntil(')');
return false;
}
}
}
if ((haveShape || haveSize) && ExpectSymbol(',', true)) {
// [ <shape> || <size> ] ,
return ParseGradientColorStops(cssGradient, aValue);
}
if (!GetToken(true)) {
return false;
}
if (!(aFlags & eGradient_AnyLegacy)) {
if (mToken.mType == eCSSToken_Ident &&
mToken.mIdent.LowerCaseEqualsLiteral("at")) {
// [ <shape> || <size> ]? at <position> ,
if (!ParseBoxPositionValues(cssGradient->mBgPos, false) ||
!ExpectSymbol(',', true)) {
SkipUntil(')');
return false;
}
return ParseGradientColorStops(cssGradient, aValue);
}
// <color-stops> only
UngetToken();
return ParseGradientColorStops(cssGradient, aValue);
}
MOZ_ASSERT(!cssGradient->mIsExplicitSize);
nsCSSTokenType ty = mToken.mType;
nsString id = mToken.mIdent;
UngetToken();
// <legacy-gradient-line>
bool haveGradientLine = false;
// if we already encountered a shape or size,
// we can not have a gradient-line in legacy syntax
if (!haveShape && !haveSize) {
haveGradientLine = IsLegacyGradientLine(ty, id);
}
if (haveGradientLine) {
// Note: -webkit-radial-gradient() doesn't accept angles.
bool haveAngle = (aFlags & eGradient_WebkitLegacy)
? false
: ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr);
// If we got an angle, we might now have a comma, ending the gradient-line
if (!haveAngle || !ExpectSymbol(',', true)) {
if (!ParseBoxPositionValues(cssGradient->mBgPos, false)) {
SkipUntil(')');
return false;
}
if (!ExpectSymbol(',', true) &&
// If we didn't already get an angle, and we're not -webkit prefixed,
// can parse an angle+comma now. Otherwise it's an error.
(haveAngle ||
(aFlags & eGradient_WebkitLegacy) ||
!ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE,
nullptr) ||
// now we better have a comma
!ExpectSymbol(',', true))) {
SkipUntil(')');
return false;
}
}
if (cssGradient->mAngle.GetUnit() != eCSSUnit_None) {
cssGradient->mIsLegacySyntax = true;
cssGradient->mIsMozLegacySyntax = (aFlags & eGradient_MozLegacy);
}
}
// radial gradients might have a shape and size here for legacy syntax
if (!haveShape && !haveSize) {
haveShape =
ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
nsCSSProps::kRadialGradientShapeKTable);
haveSize =
ParseSingleTokenVariant(cssGradient->GetRadialSize(), VARIANT_KEYWORD,
nsCSSProps::kRadialGradientLegacySizeKTable);
// could be in either order
if (!haveShape) {
haveShape =
ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
nsCSSProps::kRadialGradientShapeKTable);
}
}
if ((haveShape || haveSize) && !ExpectSymbol(',', true)) {
SkipUntil(')');
return false;
}
return ParseGradientColorStops(cssGradient, aValue);
}
bool
CSSParserImpl::IsLegacyGradientLine(const nsCSSTokenType& aType,
const nsString& aId)
{
// N.B. ParseBoxPositionValues is not guaranteed to put back
// everything it scanned if it fails, so we must only call it
// if there is no alternative to consuming a <box-position>.
// ParseVariant, as used here, will either succeed and consume
// a single token, or fail and consume none, so we can be more
// cavalier about calling it.
bool haveGradientLine = false;
switch (aType) {
case eCSSToken_Percentage:
case eCSSToken_Number:
case eCSSToken_Dimension:
haveGradientLine = true;
break;
case eCSSToken_Function:
if (aId.LowerCaseEqualsLiteral("calc")) {
haveGradientLine = true;
break;
}
MOZ_FALLTHROUGH;
case eCSSToken_ID:
case eCSSToken_Hash:
// this is a color
break;
case eCSSToken_Ident: {
// This is only a gradient line if it's a box position keyword.
nsCSSKeyword kw = nsCSSKeywords::LookupKeyword(aId);
int32_t junk;
if (nsCSSProps::FindKeyword(kw, nsCSSProps::kImageLayerPositionKTable,
junk)) {
haveGradientLine = true;
}
break;
}
default:
// error
break;
}
return haveGradientLine;
}
bool
CSSParserImpl::ParseGradientColorStops(nsCSSValueGradient* aGradient,
nsCSSValue& aValue)
{
// At least two color stops are required
if (!ParseColorStop(aGradient) ||
!ExpectSymbol(',', true) ||
!ParseColorStop(aGradient)) {
SkipUntil(')');
return false;
}
// Additional color stops
while (ExpectSymbol(',', true)) {
if (!ParseColorStop(aGradient)) {
SkipUntil(')');
return false;
}
}
if (!ExpectSymbol(')', true)) {
SkipUntil(')');
return false;
}
// Check if interpolation hints are in the correct location
bool previousPointWasInterpolationHint = true;
for (size_t x = 0; x < aGradient->mStops.Length(); x++) {
bool isInterpolationHint = aGradient->mStops[x].mIsInterpolationHint;
if (isInterpolationHint && previousPointWasInterpolationHint) {
return false;
}
previousPointWasInterpolationHint = isInterpolationHint;
}
if (previousPointWasInterpolationHint) {
return false;
}
aValue.SetGradientValue(aGradient);
return true;
}
// Parses the x or y component of a -webkit-gradient() <point> expression.
// See ParseWebkitGradientPoint() documentation for more.
bool
CSSParserImpl::ParseWebkitGradientPointComponent(nsCSSValue& aComponent,
bool aIsHorizontal)
{
// Attempts to use ParseVariant to process the token as a number (representing
// pixels), or a percent, or a calc expression of purely one or the other of
// those (we enforce this pureness via ComputeCalc below). If ParseVariant
// fails, the token may instead be a keyword or unknown token, in which case
// case we execute the rest of the function.
CSSParseResult status = ParseVariant(aComponent, VARIANT_PN | VARIANT_CALC,
nullptr);
if (status == CSSParseResult::Error) {
return false;
}
if (status == CSSParseResult::Ok) {
switch (aComponent.GetUnit()) {
case eCSSUnit_Number:
aComponent.SetFloatValue(aComponent.GetFloatValue(), eCSSUnit_Pixel);
return true;
case eCSSUnit_Calc: {
float result;
ReduceCalcOps<float, eCSSUnit_Number> opsNumber;
if (ComputeCalc(result, aComponent, opsNumber)) {
aComponent.SetFloatValue(result, eCSSUnit_Pixel);
return true;
}
ReduceCalcOps<float, eCSSUnit_Percent> opsPercent;
if (ComputeCalc(result, aComponent, opsPercent)) {
aComponent.SetPercentValue(result);
return true;
}
return false;
}
case eCSSUnit_Percent:
return true;
default:
MOZ_ASSERT(false, "ParseVariant returned value with unexpected unit");
return false;
}
}
if (!GetToken(true)) {
return false;
}
// Keyword tables to use for keyword-matching
// (Keyword order is important; we assume the index can be multiplied by 50%
// to convert to a percent-valued component.)
static const nsCSSKeyword kHorizKeywords[] = {
eCSSKeyword_left, // 0%
eCSSKeyword_center, // 50%
eCSSKeyword_right // 100%
};
static const nsCSSKeyword kVertKeywords[] = {
eCSSKeyword_top, // 0%
eCSSKeyword_center, // 50%
eCSSKeyword_bottom // 100%
};
static const size_t kNumKeywords = MOZ_ARRAY_LENGTH(kHorizKeywords);
static_assert(kNumKeywords == MOZ_ARRAY_LENGTH(kVertKeywords),
"Horizontal & vertical keyword tables must have same count");
// Try to parse the component as a number, or a percent, or a
// keyword-converted-to-percent.
if (mToken.mType == eCSSToken_Number) {
aComponent.SetFloatValue(mToken.mNumber, eCSSUnit_Pixel);
} else if (mToken.mType == eCSSToken_Percentage) {
aComponent.SetPercentValue(mToken.mNumber);
} else if (mToken.mType == eCSSToken_Ident) {
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
if (keyword == eCSSKeyword_UNKNOWN) {
return false;
}
// Choose our keyword table:
const nsCSSKeyword* kwTable = aIsHorizontal ? kHorizKeywords : kVertKeywords;
// Convert keyword to percent value (0%, 50%, or 100%)
bool didAcceptKeyword = false;
for (size_t i = 0; i < kNumKeywords; i++) {
if (keyword == kwTable[i]) {
// 0%, 50%, or 100%:
aComponent.SetPercentValue(i * 0.5);
didAcceptKeyword = true;
break;
}
}
if (!didAcceptKeyword) {
return false;
}
} else {
// Unrecognized token type. Put it back. (It might be a closing-paren of an
// invalid -webkit-gradient(...) expression, and we need to be sure caller
// can see it & stops parsing at that point.)
UngetToken();
return false;
}
MOZ_ASSERT(aComponent.GetUnit() == eCSSUnit_Pixel ||
aComponent.GetUnit() == eCSSUnit_Percent,
"If we get here, we should've successfully parsed a number (as a "
"pixel length), a percent, or a keyword (converted to percent)");
return true;
}
// This function parses a "<point>" expression for -webkit-gradient(...)
// Quoting https://www.webkit.org/blog/175/introducing-css-gradients/ :
// "A point is a pair of space-separated values.
// The syntax supports numbers, percentages or
// the keywords top, bottom, left and right
// for point values."
//
// Two additional notes:
// - WebKit also accepts the "center" keyword (not listed in the text above).
// - WebKit only accepts horizontal-flavored keywords (left/center/right) in
// the first ("x") component, and vertical-flavored keywords
// (top/center/bottom) in the second ("y") component. (This is different
// from the standard gradient syntax, which accepts both orderings, e.g.
// "top left" as well as "left top".)
bool
CSSParserImpl::ParseWebkitGradientPoint(nsCSSValuePair& aPoint)
{
return ParseWebkitGradientPointComponent(aPoint.mXValue, true) &&
ParseWebkitGradientPointComponent(aPoint.mYValue, false);
}
// Parse the next token as a <number> (for a <radius> in a -webkit-gradient
// expresison). Returns true on success; returns false & puts back
// whatever it parsed on failure.
bool
CSSParserImpl::ParseWebkitGradientRadius(float& aRadius)
{
nsCSSValue parseResult;
CSSParseResult status = ParseVariant(parseResult,
VARIANT_NUMBER | VARIANT_CALC, nullptr);
if (status != CSSParseResult::Ok) {
return false;
}
switch (parseResult.GetUnit()) {
case eCSSUnit_Number:
aRadius = parseResult.GetFloatValue();
return true;
case eCSSUnit_Calc: {
ReduceCalcOps<float, eCSSUnit_Number> ops;
if (!ComputeCalc(aRadius, parseResult, ops)) {
MOZ_ASSERT_UNREACHABLE("unexpected unit");
}
return true;
}
default:
MOZ_ASSERT(false, "ParseVariant returned value with unexpected unit");
return false;
}
}
// Parse one of:
// color-stop(number|percent, color)
// from(color)
// to(color)
//
// Quoting https://www.webkit.org/blog/175/introducing-css-gradients/ :
// A stop is a function, color-stop, that takes two arguments, the stop value
// (either a percentage or a number between 0 and 1.0), and a color (any
// valid CSS color). In addition the shorthand functions from and to are
// supported. These functions only require a color argument and are
// equivalent to color-stop(0, ...) and color-stop(1.0, …) respectively.
bool
CSSParserImpl::ParseWebkitGradientColorStop(nsCSSValueGradient* aGradient)
{
MOZ_ASSERT(aGradient, "null gradient");
if (!GetToken(true)) {
return false;
}
// We're expecting color-stop(...), from(...), or to(...) which are all
// functions. Bail if we got anything else.
if (mToken.mType != eCSSToken_Function) {
UngetToken();
return false;
}
nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement();
// Parse color-stop location (or infer it, for shorthands "from"/"to"):
if (mToken.mIdent.LowerCaseEqualsLiteral("color-stop")) {
// Parse stop location, followed by comma.
if (!ParseSingleTokenVariant(stop->mLocation,
VARIANT_NUMBER | VARIANT_PERCENT,
nullptr) ||
!ExpectSymbol(',', true)) {
SkipUntil(')'); // Skip to end of color-stop(...) expression.
return false;
}
// If we got a <number>, convert it to percentage for consistency:
if (stop->mLocation.GetUnit() == eCSSUnit_Number) {
stop->mLocation.SetPercentValue(stop->mLocation.GetFloatValue());
}
} else if (mToken.mIdent.LowerCaseEqualsLiteral("from")) {
// Shorthand for color-stop(0%, ...)
stop->mLocation.SetPercentValue(0.0f);
} else if (mToken.mIdent.LowerCaseEqualsLiteral("to")) {
// Shorthand for color-stop(100%, ...)
stop->mLocation.SetPercentValue(1.0f);
} else {
// Unrecognized function name (invalid for a -webkit-gradient color stop).
UngetToken();
return false;
}
CSSParseResult result = ParseVariant(stop->mColor, VARIANT_COLOR, nullptr);
if (result != CSSParseResult::Ok ||
(stop->mColor.GetUnit() == eCSSUnit_EnumColor &&
stop->mColor.GetIntValue() == NS_COLOR_CURRENTCOLOR)) {
// Parse failure, or parsed "currentColor" which is forbidden in
// -webkit-gradient for some reason.
SkipUntil(')');
return false;
}
// Parse color-stop function close-paren
if (!ExpectSymbol(')', true)) {
SkipUntil(')');
return false;
}
MOZ_ASSERT(stop->mLocation.GetUnit() == eCSSUnit_Percent,
"Should produce only percent-valued stop-locations. "
"(Caller depends on this when sorting color stops.)");
return true;
}
// Comparatison function to use for sorting -webkit-gradient() stops by
// location. This function assumes stops have percent-valued locations (and
// CSSParserImpl::ParseWebkitGradientColorStop should enforce this).
static bool
IsColorStopPctLocationLessThan(const nsCSSValueGradientStop& aStop1,
const nsCSSValueGradientStop& aStop2) {
return (aStop1.mLocation.GetPercentValue() <
aStop2.mLocation.GetPercentValue());
}
// This function parses a list of comma-separated color-stops for a
// -webkit-gradient(...) expression, and then pads & sorts the list as-needed.
bool
CSSParserImpl::ParseWebkitGradientColorStops(nsCSSValueGradient* aGradient)
{
MOZ_ASSERT(aGradient, "null gradient");
// Parse any number of ", <color-stop>" expressions. (0 or more)
// Note: This is different from unprefixed gradient syntax, which
// requires at least 2 stops.
while (ExpectSymbol(',', true)) {
if (!ParseWebkitGradientColorStop(aGradient)) {
return false;
}
}
// Pad up to 2 stops as-needed:
// (Modern gradient expressions are required to have at least 2 stops, so we
// depend on this internally -- e.g. we have an assertion about this in
// nsCSSRendering.cpp. -webkit-gradient syntax allows 0 stops or 1 stop,
// though, so we just pad up to 2 stops in this case).
// If we have no stops, pad with transparent-black:
if (aGradient->mStops.IsEmpty()) {
nsCSSValueGradientStop* stop1 = aGradient->mStops.AppendElement();
stop1->mColor.SetIntegerColorValue(NS_RGBA(0, 0, 0, 0),
eCSSUnit_RGBAColor);
stop1->mLocation.SetPercentValue(0.0f);
nsCSSValueGradientStop* stop2 = aGradient->mStops.AppendElement();
stop2->mColor.SetIntegerColorValue(NS_RGBA(0, 0, 0, 0),
eCSSUnit_RGBAColor);
stop2->mLocation.SetPercentValue(1.0f);
} else if (aGradient->mStops.Length() == 1) {
// Copy whatever the author provided in the first stop:
nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement();
*stop = aGradient->mStops[0];
} else {
// We have >2 stops. Sort them in order of increasing location.
std::stable_sort(aGradient->mStops.begin(),
aGradient->mStops.end(),
IsColorStopPctLocationLessThan);
}
return true;
}
// Compares aStartCoord to aEndCoord, and returns true iff they share the same
// unit (both pixel, or both percent) and aStartCoord is larger.
static bool
IsWebkitGradientCoordLarger(const nsCSSValue& aStartCoord,
const nsCSSValue& aEndCoord)
{
if (aStartCoord.GetUnit() == eCSSUnit_Percent &&
aEndCoord.GetUnit() == eCSSUnit_Percent) {
return aStartCoord.GetPercentValue() > aEndCoord.GetPercentValue();
}
if (aStartCoord.GetUnit() == eCSSUnit_Pixel &&
aEndCoord.GetUnit() == eCSSUnit_Pixel) {
return aStartCoord.GetFloatValue() > aEndCoord.GetFloatValue();
}
// We can't compare them, since their units differ. Returning false suggests
// that aEndCoord is larger, which is probably a decent guess anyway.
return false;
}
// Finalize our internal representation of a -webkit-gradient(linear, ...)
// expression, given the parsed points. (The parsed color stops
// should already be hanging off of the passed-in nsCSSValueGradient.)
//
// Note: linear gradients progress along a line between two points. The
// -webkit-gradient(linear, ...) syntax lets the author precisely specify the
// starting and ending point. However, our internal gradient structures only
// store ONE point (which for modern linear-gradient() expressions is a side or
// corner & represents the ending point of the gradient -- and the starting
// point is implicitly the opposite side/corner).
//
// In this function, we analyze the start & end points from a
// -webkit-gradient(linear, ...) expression, and we choose an appropriate
// target point to produce a modern linear-gradient() representation that has
// the same rough trajectory. If we can't determine a target point for some
// reason, we just fall back to the default top-to-bottom linear-gradient()
// directionality.
void
CSSParserImpl::FinalizeLinearWebkitGradient(nsCSSValueGradient* aGradient,
const nsCSSValuePair& aStartPoint,
const nsCSSValuePair& aEndPoint)
{
MOZ_ASSERT(!aGradient->mIsRadial, "passed-in gradient must be linear");
if (aStartPoint == aEndPoint ||
aStartPoint.mXValue.GetUnit() != aEndPoint.mXValue.GetUnit() ||
aStartPoint.mYValue.GetUnit() != aEndPoint.mYValue.GetUnit()) {
// Start point & end point are the same, OR they use different units for at
// least one coordinate. Either way, this isn't something we can represent
// using an unprefixed linear-gradient expression, so instead we'll just
// arbitrarily pretend this is a top-to-bottom gradient (which is the
// default for modern linear-gradient if a direction is unspecified).
aGradient->mBgPos.mYValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_BOTTOM,
eCSSUnit_Enumerated);
aGradient->mBgPos.mXValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_CENTER,
eCSSUnit_Enumerated);
return;
}
// Set the target point to one of the box's corners (or the center of an
// edge), by comparing aStartPoint and aEndPoint to extract a general
// direction.
int32_t targetX;
if (aStartPoint.mXValue == aEndPoint.mXValue) {
targetX = NS_STYLE_IMAGELAYER_POSITION_CENTER;
} else if (IsWebkitGradientCoordLarger(aStartPoint.mXValue,
aEndPoint.mXValue)) {
targetX = NS_STYLE_IMAGELAYER_POSITION_LEFT;
} else {
MOZ_ASSERT(IsWebkitGradientCoordLarger(aEndPoint.mXValue,
aStartPoint.mXValue),
"IsWebkitGradientCoordLarger returning inconsistent results?");
targetX = NS_STYLE_IMAGELAYER_POSITION_RIGHT;
}
int32_t targetY;
if (aStartPoint.mYValue == aEndPoint.mYValue) {
targetY = NS_STYLE_IMAGELAYER_POSITION_CENTER;
} else if (IsWebkitGradientCoordLarger(aStartPoint.mYValue,
aEndPoint.mYValue)) {
targetY = NS_STYLE_IMAGELAYER_POSITION_TOP;
} else {
MOZ_ASSERT(IsWebkitGradientCoordLarger(aEndPoint.mYValue,
aStartPoint.mYValue),
"IsWebkitGradientCoordLarger returning inconsistent results?");
targetY = NS_STYLE_IMAGELAYER_POSITION_BOTTOM;
}
aGradient->mBgPos.mXValue.SetIntValue(targetX, eCSSUnit_Enumerated);
aGradient->mBgPos.mYValue.SetIntValue(targetY, eCSSUnit_Enumerated);
}
// Finalize our internal representation of a -webkit-gradient(radial, ...)
// expression, given the parsed points & radii. (The parsed color-stops
// should already be hanging off of the passed-in nsCSSValueGradient).
void
CSSParserImpl::FinalizeRadialWebkitGradient(nsCSSValueGradient* aGradient,
const nsCSSValuePair& aFirstCenter,
const nsCSSValuePair& aSecondCenter,
const float aFirstRadius,
const float aSecondRadius)
{
MOZ_ASSERT(aGradient->mIsRadial, "passed-in gradient must be radial");
// NOTE: -webkit-gradient(radial, ...) has *two arbitrary circles*, with the
// gradient stretching between the circles' edges. In contrast, the standard
// syntax (and hence our data structures) can only represent *one* circle,
// with the gradient going from its center to its edge. To bridge this gap
// in expressiveness, we'll just see which of our two circles is smaller, and
// we'll treat that circle as if it were zero-sized and located at the center
// of the larger circle. Then, we'll be able to use the same data structures
// that we use for the standard radial-gradient syntax.
if (aSecondRadius >= aFirstRadius) {
// Second circle is larger.
aGradient->mBgPos = aSecondCenter;
aGradient->mIsExplicitSize = true;
aGradient->GetRadiusX().SetFloatValue(aSecondRadius, eCSSUnit_Pixel);
return;
}
// First circle is larger, so we'll have it be the outer circle.
aGradient->mBgPos = aFirstCenter;
aGradient->mIsExplicitSize = true;
aGradient->GetRadiusX().SetFloatValue(aFirstRadius, eCSSUnit_Pixel);
// For this to work properly (with the earlier color stops attached to the
// first circle), we need to also reverse the color-stop list, so that
// e.g. the author's "from" color is attached to the outer edge (the first
// circle), rather than attached to the center (the collapsed second circle).
std::reverse(aGradient->mStops.begin(), aGradient->mStops.end());
// And now invert the stop locations:
for (nsCSSValueGradientStop& colorStop : aGradient->mStops) {
float origLocation = colorStop.mLocation.GetPercentValue();
colorStop.mLocation.SetPercentValue(1.0f - origLocation);
}
}
bool
CSSParserImpl::ParseWebkitGradient(nsCSSValue& aValue)
{
// Parse type of gradient
if (!GetToken(true)) {
return false;
}
if (mToken.mType != eCSSToken_Ident) {
UngetToken(); // Important; the token might be ")", which we're about to
// seek to.
SkipUntil(')');
return false;
}
bool isRadial;
if (mToken.mIdent.LowerCaseEqualsLiteral("radial")) {
isRadial = true;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("linear")) {
isRadial = false;
} else {
// Unrecognized gradient type.
SkipUntil(')');
return false;
}
// Parse a comma + first point:
nsCSSValuePair firstPoint;
if (!ExpectSymbol(',', true) ||
!ParseWebkitGradientPoint(firstPoint)) {
SkipUntil(')');
return false;
}
// If radial, parse comma + first radius:
float firstRadius;
if (isRadial) {
if (!ExpectSymbol(',', true) ||
!ParseWebkitGradientRadius(firstRadius)) {
SkipUntil(')');
return false;
}
}
// Parse a comma + second point:
nsCSSValuePair secondPoint;
if (!ExpectSymbol(',', true) ||
!ParseWebkitGradientPoint(secondPoint)) {
SkipUntil(')');
return false;
}
// If radial, parse comma + second radius:
float secondRadius;
if (isRadial) {
if (!ExpectSymbol(',', true) ||
!ParseWebkitGradientRadius(secondRadius)) {
SkipUntil(')');
return false;
}
}
// Construct a nsCSSValueGradient object, and parse color stops into it:
RefPtr<nsCSSValueGradient> cssGradient =
new nsCSSValueGradient(isRadial, false /* aIsRepeating */);
if (!ParseWebkitGradientColorStops(cssGradient) ||
!ExpectSymbol(')', true)) {
// Failed to parse color-stops, or found trailing junk between them & ')'.
SkipUntil(')');
return false;
}
// Finish building cssGradient, based on our parsed positioning/sizing info:
if (isRadial) {
FinalizeRadialWebkitGradient(cssGradient, firstPoint, secondPoint,
firstRadius, secondRadius);
} else {
FinalizeLinearWebkitGradient(cssGradient, firstPoint, secondPoint);
}
aValue.SetGradientValue(cssGradient);
return true;
}
bool
CSSParserImpl::ParseWebkitTextStroke()
{
static const nsCSSPropertyID kWebkitTextStrokeIDs[] = {
eCSSProperty__webkit_text_stroke_width,
eCSSProperty__webkit_text_stroke_color
};
const size_t numProps = MOZ_ARRAY_LENGTH(kWebkitTextStrokeIDs);
nsCSSValue values[numProps];
int32_t found = ParseChoice(values, kWebkitTextStrokeIDs, numProps);
if (found < 1) {
return false;
}
if (!(found & 1)) { // Provide default -webkit-text-stroke-width
values[0].SetFloatValue(0, eCSSUnit_Pixel);
}
if (!(found & 2)) { // Provide default -webkit-text-stroke-color
values[1].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
}
for (size_t index = 0; index < numProps; ++index) {
AppendValue(kWebkitTextStrokeIDs[index], values[index]);
}
return true;
}
int32_t
CSSParserImpl::ParseChoice(nsCSSValue aValues[],
const nsCSSPropertyID aPropIDs[], int32_t aNumIDs)
{
int32_t found = 0;
nsAutoParseCompoundProperty compound(this);
int32_t loop;
for (loop = 0; loop < aNumIDs; loop++) {
// Try each property parser in order
int32_t hadFound = found;
int32_t index;
for (index = 0; index < aNumIDs; index++) {
int32_t bit = 1 << index;
if ((found & bit) == 0) {
CSSParseResult result =
ParseSingleValueProperty(aValues[index], aPropIDs[index]);
if (result == CSSParseResult::Error) {
return -1;
}
if (result == CSSParseResult::Ok) {
found |= bit;
// It's more efficient to break since it will reset |hadFound|
// to |found|. Furthermore, ParseListStyle depends on our going
// through the properties in order for each value..
break;
}
}
}
if (found == hadFound) { // found nothing new
break;
}
}
if (0 < found) {
if (1 == found) { // only first property
if (eCSSUnit_Inherit == aValues[0].GetUnit()) { // one inherit, all inherit
for (loop = 1; loop < aNumIDs; loop++) {
aValues[loop].SetInheritValue();
}
found = ((1 << aNumIDs) - 1);
}
else if (eCSSUnit_Initial == aValues[0].GetUnit()) { // one initial, all initial
for (loop = 1; loop < aNumIDs; loop++) {
aValues[loop].SetInitialValue();
}
found = ((1 << aNumIDs) - 1);
}
else if (eCSSUnit_Unset == aValues[0].GetUnit()) { // one unset, all unset
for (loop = 1; loop < aNumIDs; loop++) {
aValues[loop].SetUnsetValue();
}
found = ((1 << aNumIDs) - 1);
}
}
else { // more than one value, verify no inherits, initials or unsets
for (loop = 0; loop < aNumIDs; loop++) {
if (eCSSUnit_Inherit == aValues[loop].GetUnit()) {
found = -1;
break;
}
else if (eCSSUnit_Initial == aValues[loop].GetUnit()) {
found = -1;
break;
}
else if (eCSSUnit_Unset == aValues[loop].GetUnit()) {
found = -1;
break;
}
}
}
}
return found;
}
void
CSSParserImpl::AppendValue(nsCSSPropertyID aPropID, const nsCSSValue& aValue)
{
mTempData.AddLonghandProperty(aPropID, aValue);
}
/**
* Parse a "box" property. Box properties have 1 to 4 values. When less
* than 4 values are provided a standard mapping is used to replicate
* existing values.
*/
bool
CSSParserImpl::ParseBoxProperties(const nsCSSPropertyID aPropIDs[])
{
// Get up to four values for the property
int32_t count = 0;
nsCSSRect result;
NS_FOR_CSS_SIDES (index) {
CSSParseResult parseResult =
ParseBoxProperty(result.*(nsCSSRect::sides[index]), aPropIDs[index]);
if (parseResult == CSSParseResult::NotFound) {
break;
}
if (parseResult == CSSParseResult::Error) {
return false;
}
count++;
}
if (count == 0) {
return false;
}
if (1 < count) { // verify no more than single inherit, initial or unset
NS_FOR_CSS_SIDES (index) {
nsCSSUnit unit = (result.*(nsCSSRect::sides[index])).GetUnit();
if (eCSSUnit_Inherit == unit ||
eCSSUnit_Initial == unit ||
eCSSUnit_Unset == unit) {
return false;
}
}
}
// Provide missing values by replicating some of the values found
switch (count) {
case 1: // Make right == top
result.mRight = result.mTop;
MOZ_FALLTHROUGH;
case 2: // Make bottom == top
result.mBottom = result.mTop;
MOZ_FALLTHROUGH;
case 3: // Make left == right
result.mLeft = result.mRight;
}
NS_FOR_CSS_SIDES (index) {
AppendValue(aPropIDs[index], result.*(nsCSSRect::sides[index]));
}
return true;
}
// Similar to ParseBoxProperties, except there is only one property
// with the result as its value, not four.
bool
CSSParserImpl::ParseGroupedBoxProperty(int32_t aVariantMask,
/** outparam */ nsCSSValue& aValue,
uint32_t aRestrictions)
{
nsCSSRect& result = aValue.SetRectValue();
int32_t count = 0;
NS_FOR_CSS_SIDES (index) {
CSSParseResult parseResult =
ParseVariantWithRestrictions(result.*(nsCSSRect::sides[index]),
aVariantMask, nullptr,
aRestrictions);
if (parseResult == CSSParseResult::NotFound) {
break;
}
if (parseResult == CSSParseResult::Error) {
return false;
}
count++;
}
if (count == 0) {
return false;
}
// Provide missing values by replicating some of the values found
switch (count) {
case 1: // Make right == top
result.mRight = result.mTop;
MOZ_FALLTHROUGH;
case 2: // Make bottom == top
result.mBottom = result.mTop;
MOZ_FALLTHROUGH;
case 3: // Make left == right
result.mLeft = result.mRight;
}
return true;
}
bool
CSSParserImpl::ParseBoxCornerRadius(nsCSSPropertyID aPropID)
{
nsCSSValue dimenX, dimenY;
// required first value
if (ParseNonNegativeVariant(dimenX, VARIANT_HLP | VARIANT_CALC, nullptr) !=
CSSParseResult::Ok) {
return false;
}
// optional second value (forbidden if first value is inherit/initial/unset)
if (dimenX.GetUnit() != eCSSUnit_Inherit &&
dimenX.GetUnit() != eCSSUnit_Initial &&
dimenX.GetUnit() != eCSSUnit_Unset) {
if (ParseNonNegativeVariant(dimenY, VARIANT_LP | VARIANT_CALC, nullptr) ==
CSSParseResult::Error) {
return false;
}
}
if (dimenX == dimenY || dimenY.GetUnit() == eCSSUnit_Null) {
AppendValue(aPropID, dimenX);
} else {
nsCSSValue value;
value.SetPairValue(dimenX, dimenY);
AppendValue(aPropID, value);
}
return true;
}
bool
CSSParserImpl::ParseBoxCornerRadiiInternals(nsCSSValue array[])
{
// Rectangles are used as scratch storage.
// top => top-left, right => top-right,
// bottom => bottom-right, left => bottom-left.
nsCSSRect dimenX, dimenY;
int32_t countX = 0, countY = 0;
NS_FOR_CSS_SIDES (side) {
CSSParseResult result =
ParseNonNegativeVariant(dimenX.*nsCSSRect::sides[side],
(side > 0 ? 0 : VARIANT_INHERIT) |
VARIANT_LP | VARIANT_CALC,
nullptr);
if (result == CSSParseResult::Error) {
return false;
} else if (result == CSSParseResult::NotFound) {
break;
}
countX++;
}
if (countX == 0)
return false;
if (ExpectSymbol('/', true)) {
NS_FOR_CSS_SIDES (side) {
CSSParseResult result =
ParseNonNegativeVariant(dimenY.*nsCSSRect::sides[side],
VARIANT_LP | VARIANT_CALC, nullptr);
if (result == CSSParseResult::Error) {
return false;
} else if (result == CSSParseResult::NotFound) {
break;
}
countY++;
}
if (countY == 0)
return false;
}
// if 'initial', 'inherit' or 'unset' was used, it must be the only value
if (countX > 1 || countY > 0) {
nsCSSUnit unit = dimenX.mTop.GetUnit();
if (eCSSUnit_Inherit == unit ||
eCSSUnit_Initial == unit ||
eCSSUnit_Unset == unit)
return false;
}
// if we have no Y-values, use the X-values
if (countY == 0) {
dimenY = dimenX;
countY = countX;
}
// Provide missing values by replicating some of the values found
switch (countX) {
case 1: // Make top-right same as top-left
dimenX.mRight = dimenX.mTop;
MOZ_FALLTHROUGH;
case 2: // Make bottom-right same as top-left
dimenX.mBottom = dimenX.mTop;
MOZ_FALLTHROUGH;
case 3: // Make bottom-left same as top-right
dimenX.mLeft = dimenX.mRight;
}
switch (countY) {
case 1: // Make top-right same as top-left
dimenY.mRight = dimenY.mTop;
MOZ_FALLTHROUGH;
case 2: // Make bottom-right same as top-left
dimenY.mBottom = dimenY.mTop;
MOZ_FALLTHROUGH;
case 3: // Make bottom-left same as top-right
dimenY.mLeft = dimenY.mRight;
}
NS_FOR_CSS_SIDES(side) {
nsCSSValue& x = dimenX.*nsCSSRect::sides[side];
nsCSSValue& y = dimenY.*nsCSSRect::sides[side];
if (x == y) {
array[side] = x;
} else {
nsCSSValue pair;
pair.SetPairValue(x, y);
array[side] = pair;
}
}
return true;
}
bool
CSSParserImpl::ParseBoxCornerRadii(const nsCSSPropertyID aPropIDs[])
{
nsCSSValue value[4];
if (!ParseBoxCornerRadiiInternals(value)) {
return false;
}
NS_FOR_CSS_SIDES(side) {
AppendValue(aPropIDs[side], value[side]);
}
return true;
}
// These must be in CSS order (top,right,bottom,left) for indexing to work
static const nsCSSPropertyID kBorderStyleIDs[] = {
eCSSProperty_border_top_style,
eCSSProperty_border_right_style,
eCSSProperty_border_bottom_style,
eCSSProperty_border_left_style
};
static const nsCSSPropertyID kBorderWidthIDs[] = {
eCSSProperty_border_top_width,
eCSSProperty_border_right_width,
eCSSProperty_border_bottom_width,
eCSSProperty_border_left_width
};
static const nsCSSPropertyID kBorderColorIDs[] = {
eCSSProperty_border_top_color,
eCSSProperty_border_right_color,
eCSSProperty_border_bottom_color,
eCSSProperty_border_left_color
};
static const nsCSSPropertyID kBorderRadiusIDs[] = {
eCSSProperty_border_top_left_radius,
eCSSProperty_border_top_right_radius,
eCSSProperty_border_bottom_right_radius,
eCSSProperty_border_bottom_left_radius
};
static const nsCSSPropertyID kOutlineRadiusIDs[] = {
eCSSProperty__moz_outline_radius_topleft,
eCSSProperty__moz_outline_radius_topright,
eCSSProperty__moz_outline_radius_bottomright,
eCSSProperty__moz_outline_radius_bottomleft
};
void
CSSParserImpl::SaveInputState(CSSParserInputState& aState)
{
aState.mToken = mToken;
aState.mHavePushBack = mHavePushBack;
mScanner->SavePosition(aState.mPosition);
}
void
CSSParserImpl::RestoreSavedInputState(const CSSParserInputState& aState)
{
mToken = aState.mToken;
mHavePushBack = aState.mHavePushBack;
mScanner->RestoreSavedPosition(aState.mPosition);
}
bool
CSSParserImpl::ParseProperty(nsCSSPropertyID aPropID)
{
// Can't use AutoRestore<bool> because it's a bitfield.
MOZ_ASSERT(!mHashlessColorQuirk,
"hashless color quirk should not be set");
MOZ_ASSERT(!mUnitlessLengthQuirk,
"unitless length quirk should not be set");
MOZ_ASSERT(aPropID != eCSSPropertyExtra_variable);
if (mNavQuirkMode) {
mHashlessColorQuirk =
nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_HASHLESS_COLOR_QUIRK);
mUnitlessLengthQuirk =
nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_UNITLESS_LENGTH_QUIRK);
}
// Save the current input state so that we can restore it later if we
// have to re-parse the property value as a variable-reference-containing
// token stream.
CSSParserInputState stateBeforeProperty;
SaveInputState(stateBeforeProperty);
mScanner->ClearSeenVariableReference();
NS_ASSERTION(aPropID < eCSSProperty_COUNT, "index out of range");
bool allowVariables = true;
bool result;
switch (nsCSSProps::PropertyParseType(aPropID)) {
case CSS_PROPERTY_PARSE_INACCESSIBLE: {
// The user can't use these
REPORT_UNEXPECTED(PEInaccessibleProperty2);
allowVariables = false;
result = false;
break;
}
case CSS_PROPERTY_PARSE_FUNCTION: {
result = ParsePropertyByFunction(aPropID);
break;
}
case CSS_PROPERTY_PARSE_VALUE: {
result = false;
nsCSSValue value;
if (ParseSingleValueProperty(value, aPropID) == CSSParseResult::Ok) {
AppendValue(aPropID, value);
result = true;
}
// XXX Report errors?
break;
}
case CSS_PROPERTY_PARSE_VALUE_LIST: {
result = ParseValueList(aPropID);
break;
}
default: {
result = false;
allowVariables = false;
MOZ_ASSERT(false,
"Property's flags field in nsCSSPropList.h is missing "
"one of the CSS_PROPERTY_PARSE_* constants");
break;
}
}
if (result) {
// We need to call ExpectEndProperty() to decide whether to reparse
// with variables. This is needed because the property parsing may
// have stopped upon finding a variable (e.g., 'margin: 1px var(a)')
// in a way that future variable substitutions will be valid, or
// because it parsed everything that's possible but we still want to
// act as though the property contains variables even though we know
// the substitution will never work (e.g., for 'margin: 1px 2px 3px
// 4px 5px var(a)').
//
// It would be nice to find a better solution here
// (and for the SkipUntilOneOf below), though, that doesn't depend
// on using what we don't accept for doing parsing correctly.
if (!ExpectEndProperty()) {
result = false;
}
}
bool seenVariable = mScanner->SeenVariableReference() ||
(stateBeforeProperty.mHavePushBack &&
stateBeforeProperty.mToken.mType == eCSSToken_Function &&
stateBeforeProperty.mToken.mIdent.LowerCaseEqualsLiteral("var"));
bool parseAsTokenStream;
if (!result && allowVariables) {
parseAsTokenStream = true;
if (!seenVariable) {
// We might have stopped parsing the property before its end and before
// finding a variable reference. Keep checking until the end of the
// property.
CSSParserInputState stateAtError;
SaveInputState(stateAtError);
const char16_t stopChars[] = { ';', '!', '}', ')', 0 };
SkipUntilOneOf(stopChars);
UngetToken();
parseAsTokenStream = mScanner->SeenVariableReference();
if (!parseAsTokenStream) {
// If we parsed to the end of the propery and didn't find any variable
// references, then the real position we want to report the error at
// is |stateAtError|.
RestoreSavedInputState(stateAtError);
}
}
} else {
parseAsTokenStream = false;
}
if (parseAsTokenStream) {
// Go back to the start of the property value and parse it to make sure
// its variable references are syntactically valid and is otherwise
// balanced.
RestoreSavedInputState(stateBeforeProperty);
if (!mInSupportsCondition) {
mScanner->StartRecording();
}
CSSVariableDeclarations::Type type;
bool dropBackslash;
nsString impliedCharacters;
nsCSSValue value;
if (ParseValueWithVariables(&type, &dropBackslash, impliedCharacters,
nullptr, nullptr)) {
MOZ_ASSERT(type == CSSVariableDeclarations::eTokenStream,
"a non-custom property reparsed since it contained variable "
"references should not have been 'initial' or 'inherit'");
nsString propertyValue;
if (!mInSupportsCondition) {
// If we are in an @supports condition, we don't need to store the
// actual token stream on the nsCSSValue.
mScanner->StopRecording(propertyValue);
if (dropBackslash) {
MOZ_ASSERT(!propertyValue.IsEmpty() &&
propertyValue[propertyValue.Length() - 1] == '\\');
propertyValue.Truncate(propertyValue.Length() - 1);
}
propertyValue.Append(impliedCharacters);
}
if (mHavePushBack) {
// If we came to the end of a property value that had a variable
// reference and a token was pushed back, then it would have been
// ended by '!', ')', ';', ']' or '}'. We should remove it from the
// recorded property value.
MOZ_ASSERT(mToken.IsSymbol('!') ||
mToken.IsSymbol(')') ||
mToken.IsSymbol(';') ||
mToken.IsSymbol(']') ||
mToken.IsSymbol('}'));
if (!mInSupportsCondition) {
MOZ_ASSERT(!propertyValue.IsEmpty());
MOZ_ASSERT(propertyValue[propertyValue.Length() - 1] ==
mToken.mSymbol);
propertyValue.Truncate(propertyValue.Length() - 1);
}
}
if (!mInSupportsCondition) {
if (nsCSSProps::IsShorthand(aPropID)) {
// If this is a shorthand property, we store the token stream on each
// of its corresponding longhand properties.
CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aPropID, EnabledState()) {
nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream;
tokenStream->mPropertyID = *p;
tokenStream->mShorthandPropertyID = aPropID;
tokenStream->mTokenStream = propertyValue;
tokenStream->mBaseURI = mBaseURI;
tokenStream->mSheetURI = mSheetURI;
tokenStream->mSheetPrincipal = mSheetPrincipal;
// XXX Should store sheet here (see bug 952338).
// tokenStream->mSheet = mSheet;
tokenStream->mLineNumber = stateBeforeProperty.mPosition.LineNumber();
tokenStream->mLineOffset = stateBeforeProperty.mPosition.LineOffset();
value.SetTokenStreamValue(tokenStream);
AppendValue(*p, value);
}
} else {
nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream;
tokenStream->mPropertyID = aPropID;
tokenStream->mTokenStream = propertyValue;
tokenStream->mBaseURI = mBaseURI;
tokenStream->mSheetURI = mSheetURI;
tokenStream->mSheetPrincipal = mSheetPrincipal;
// XXX Should store sheet here (see bug 952338).
// tokenStream->mSheet = mSheet;
tokenStream->mLineNumber = stateBeforeProperty.mPosition.LineNumber();
tokenStream->mLineOffset = stateBeforeProperty.mPosition.LineOffset();
value.SetTokenStreamValue(tokenStream);
AppendValue(aPropID, value);
}
}
result = true;
} else {
if (!mInSupportsCondition) {
mScanner->StopRecording();
}
}
}
if (mNavQuirkMode) {
mHashlessColorQuirk = false;
mUnitlessLengthQuirk = false;
}
return result;
}
bool
CSSParserImpl::ParsePropertyByFunction(nsCSSPropertyID aPropID)
{
switch (aPropID) { // handle shorthand or multiple properties
case eCSSProperty_place_content:
return ParsePlaceContent();
case eCSSProperty_place_items:
return ParsePlaceItems();
case eCSSProperty_place_self:
return ParsePlaceSelf();
case eCSSProperty_background:
return ParseImageLayers(nsStyleImageLayers::kBackgroundLayerTable);
case eCSSProperty_background_repeat:
return ParseImageLayerRepeat(eCSSProperty_background_repeat);
case eCSSProperty_background_position:
return ParseImageLayerPosition(nsStyleImageLayers::kBackgroundLayerTable);
case eCSSProperty_background_position_x:
case eCSSProperty_background_position_y:
return ParseImageLayerPositionCoord(aPropID,
aPropID == eCSSProperty_background_position_x);
case eCSSProperty_background_size:
return ParseImageLayerSize(eCSSProperty_background_size);
case eCSSProperty_border:
return ParseBorderSide(kBorderTopIDs, true);
case eCSSProperty_border_color:
return ParseBorderColor();
case eCSSProperty_border_spacing:
return ParseBorderSpacing();
case eCSSProperty_border_style:
return ParseBorderStyle();
case eCSSProperty_border_block_end:
return ParseBorderSide(kBorderBlockEndIDs, false);
case eCSSProperty_border_block_start:
return ParseBorderSide(kBorderBlockStartIDs, false);
case eCSSProperty_border_bottom:
return ParseBorderSide(kBorderBottomIDs, false);
case eCSSProperty_border_inline_end:
return ParseBorderSide(kBorderInlineEndIDs, false);
case eCSSProperty_border_inline_start:
return ParseBorderSide(kBorderInlineStartIDs, false);
case eCSSProperty_border_left:
return ParseBorderSide(kBorderLeftIDs, false);
case eCSSProperty_border_right:
return ParseBorderSide(kBorderRightIDs, false);
case eCSSProperty_border_top:
return ParseBorderSide(kBorderTopIDs, false);
case eCSSProperty__moz_border_bottom_colors:
case eCSSProperty__moz_border_left_colors:
case eCSSProperty__moz_border_right_colors:
case eCSSProperty__moz_border_top_colors:
return ParseBorderColors(aPropID);
case eCSSProperty_border_image_slice:
return ParseBorderImageSlice(true, nullptr);
case eCSSProperty_border_image_width:
return ParseBorderImageWidth(true);
case eCSSProperty_border_image_outset:
return ParseBorderImageOutset(true);
case eCSSProperty_border_image_repeat:
return ParseBorderImageRepeat(true);
case eCSSProperty_border_image:
return ParseBorderImage();
case eCSSProperty_border_width:
return ParseBorderWidth();
case eCSSProperty_border_radius:
return ParseBoxCornerRadii(kBorderRadiusIDs);
case eCSSProperty__moz_outline_radius:
return ParseBoxCornerRadii(kOutlineRadiusIDs);
case eCSSProperty_border_top_left_radius:
case eCSSProperty_border_top_right_radius:
case eCSSProperty_border_bottom_right_radius:
case eCSSProperty_border_bottom_left_radius:
case eCSSProperty__moz_outline_radius_topleft:
case eCSSProperty__moz_outline_radius_topright:
case eCSSProperty__moz_outline_radius_bottomright:
case eCSSProperty__moz_outline_radius_bottomleft:
return ParseBoxCornerRadius(aPropID);
case eCSSProperty_box_shadow:
case eCSSProperty_text_shadow:
return ParseShadowList(aPropID);
case eCSSProperty_clip:
return ParseRect(eCSSProperty_clip);
case eCSSProperty_columns:
return ParseColumns();
case eCSSProperty_column_rule:
return ParseBorderSide(kColumnRuleIDs, false);
case eCSSProperty_content:
return ParseContent();
case eCSSProperty__moz_context_properties:
return ParseContextProperties();
case eCSSProperty_counter_increment:
case eCSSProperty_counter_reset:
return ParseCounterData(aPropID);
case eCSSProperty_cursor:
return ParseCursor();
case eCSSProperty_filter:
return ParseFilter();
case eCSSProperty_flex:
return ParseFlex();
case eCSSProperty_flex_flow:
return ParseFlexFlow();
case eCSSProperty_font:
return ParseFont();
case eCSSProperty_font_variant:
return ParseFontVariant();
case eCSSProperty_grid_auto_flow:
return ParseGridAutoFlow();
case eCSSProperty_grid_auto_columns:
case eCSSProperty_grid_auto_rows:
return ParseGridAutoColumnsRows(aPropID);
case eCSSProperty_grid_template_areas:
return ParseGridTemplateAreas();
case eCSSProperty_grid_template_columns:
case eCSSProperty_grid_template_rows:
return ParseGridTemplateColumnsRows(aPropID);
case eCSSProperty_grid_template:
return ParseGridTemplate();
case eCSSProperty_grid:
return ParseGrid();
case eCSSProperty_grid_column_start:
case eCSSProperty_grid_column_end:
case eCSSProperty_grid_row_start:
case eCSSProperty_grid_row_end:
return ParseGridColumnRowStartEnd(aPropID);
case eCSSProperty_grid_column:
return ParseGridColumnRow(eCSSProperty_grid_column_start,
eCSSProperty_grid_column_end);
case eCSSProperty_grid_row:
return ParseGridColumnRow(eCSSProperty_grid_row_start,
eCSSProperty_grid_row_end);
case eCSSProperty_grid_area:
return ParseGridArea();
case eCSSProperty_grid_gap:
return ParseGridGap();
case eCSSProperty__moz_image_region:
return ParseRect(eCSSProperty__moz_image_region);
case eCSSProperty_align_content:
case eCSSProperty_justify_content:
return ParseAlignJustifyContent(aPropID);
case eCSSProperty_align_items:
return ParseAlignItems();
case eCSSProperty_align_self:
case eCSSProperty_justify_self:
return ParseAlignJustifySelf(aPropID);
case eCSSProperty_initial_letter:
return ParseInitialLetter();
case eCSSProperty_justify_items:
return ParseJustifyItems();
case eCSSProperty_list_style:
return ParseListStyle();
case eCSSProperty_margin:
return ParseMargin();
case eCSSProperty_object_position:
return ParseObjectPosition();
case eCSSProperty_outline:
return ParseOutline();
case eCSSProperty_overflow:
return ParseOverflow();
case eCSSProperty_padding:
return ParsePadding();
case eCSSProperty_quotes:
return ParseQuotes();
case eCSSProperty_text_decoration:
return ParseTextDecoration();
case eCSSProperty_text_emphasis:
return ParseTextEmphasis();
case eCSSProperty_will_change:
return ParseWillChange();
case eCSSProperty_transform:
case eCSSProperty__moz_window_transform:
return ParseTransform(false, aPropID);
case eCSSProperty__moz_transform:
return ParseTransform(true, eCSSProperty_transform);
case eCSSProperty_transform_origin:
case eCSSProperty_perspective_origin:
case eCSSProperty__moz_window_transform_origin:
return ParseTransformOrigin(aPropID);
case eCSSProperty_transition:
return ParseTransition();
case eCSSProperty_animation:
return ParseAnimation();
case eCSSProperty_transition_property:
return ParseTransitionProperty();
case eCSSProperty_fill:
case eCSSProperty_stroke:
return ParsePaint(aPropID);
case eCSSProperty_stroke_dasharray:
return ParseDasharray();
case eCSSProperty_marker:
return ParseMarker();
case eCSSProperty_paint_order:
return ParsePaintOrder();
case eCSSProperty_scroll_snap_type:
return ParseScrollSnapType();
case eCSSProperty_mask:
return ParseImageLayers(nsStyleImageLayers::kMaskLayerTable);
case eCSSProperty_mask_repeat:
return ParseImageLayerRepeat(eCSSProperty_mask_repeat);
case eCSSProperty_mask_position:
return ParseImageLayerPosition(nsStyleImageLayers::kMaskLayerTable);
case eCSSProperty_mask_position_x:
case eCSSProperty_mask_position_y:
return ParseImageLayerPositionCoord(aPropID,
aPropID == eCSSProperty_mask_position_x);
case eCSSProperty_mask_size:
return ParseImageLayerSize(eCSSProperty_mask_size);
case eCSSProperty__webkit_text_stroke:
return ParseWebkitTextStroke();
case eCSSProperty_all:
return ParseAll();
default:
MOZ_ASSERT(false, "should not be called");
return false;
}
}
// Bits used in determining which background position info we have
#define BG_CENTER NS_STYLE_IMAGELAYER_POSITION_CENTER
#define BG_TOP NS_STYLE_IMAGELAYER_POSITION_TOP
#define BG_BOTTOM NS_STYLE_IMAGELAYER_POSITION_BOTTOM
#define BG_LEFT NS_STYLE_IMAGELAYER_POSITION_LEFT
#define BG_RIGHT NS_STYLE_IMAGELAYER_POSITION_RIGHT
#define BG_CTB (BG_CENTER | BG_TOP | BG_BOTTOM)
#define BG_TB (BG_TOP | BG_BOTTOM)
#define BG_CLR (BG_CENTER | BG_LEFT | BG_RIGHT)
#define BG_LR (BG_LEFT | BG_RIGHT)
CSSParseResult
CSSParserImpl::ParseBoxProperty(nsCSSValue& aValue,
nsCSSPropertyID aPropID)
{
if (aPropID < 0 || aPropID >= eCSSProperty_COUNT_no_shorthands) {
MOZ_ASSERT(false, "must only be called for longhand properties");
return CSSParseResult::NotFound;
}
MOZ_ASSERT(!nsCSSProps::PropHasFlags(aPropID,
CSS_PROPERTY_VALUE_PARSER_FUNCTION),
"must only be called for non-function-parsed properties");
uint32_t variant = nsCSSProps::ParserVariant(aPropID);
if (variant == 0) {
MOZ_ASSERT(false, "must only be called for variant-parsed properties");
return CSSParseResult::NotFound;
}
if (variant & ~(VARIANT_AHKLP | VARIANT_COLOR | VARIANT_CALC)) {
MOZ_ASSERT(false, "must only be called for properties that take certain "
"variants");
return CSSParseResult::NotFound;
}
const KTableEntry* kwtable = nsCSSProps::kKeywordTableTable[aPropID];
uint32_t restrictions = nsCSSProps::ValueRestrictions(aPropID);
return ParseVariantWithRestrictions(aValue, variant, kwtable, restrictions);
}
bool
CSSParserImpl::ParseSingleValuePropertyByFunction(nsCSSValue& aValue,
nsCSSPropertyID aPropID)
{
switch (aPropID) {
case eCSSProperty_clip_path:
return ParseClipPath(aValue);
case eCSSProperty_contain:
return ParseContain(aValue);
case eCSSProperty_font_family:
return ParseFamily(aValue);
case eCSSProperty_font_synthesis:
return ParseFontSynthesis(aValue);
case eCSSProperty_font_variant_alternates:
return ParseFontVariantAlternates(aValue);
case eCSSProperty_font_variant_east_asian:
return ParseFontVariantEastAsian(aValue);
case eCSSProperty_font_variant_ligatures:
return ParseFontVariantLigatures(aValue);
case eCSSProperty_font_variant_numeric:
return ParseFontVariantNumeric(aValue);
case eCSSProperty_font_feature_settings:
return ParseFontFeatureSettings(aValue);
case eCSSProperty_font_variation_settings:
return ParseFontVariationSettings(aValue);
case eCSSProperty_font_weight:
return ParseFontWeight(aValue);
case eCSSProperty_image_orientation:
return ParseImageOrientation(aValue);
case eCSSProperty_list_style_type:
return ParseListStyleType(aValue);
case eCSSProperty_scroll_snap_points_x:
return ParseScrollSnapPoints(aValue, eCSSProperty_scroll_snap_points_x);
case eCSSProperty_scroll_snap_points_y:
return ParseScrollSnapPoints(aValue, eCSSProperty_scroll_snap_points_y);
case eCSSProperty_scroll_snap_destination:
return ParseScrollSnapDestination(aValue);
case eCSSProperty_scroll_snap_coordinate:
return ParseScrollSnapCoordinate(aValue);
case eCSSProperty_shape_outside:
return ParseShapeOutside(aValue);
case eCSSProperty_text_align:
return ParseTextAlign(aValue);
case eCSSProperty_text_align_last:
return ParseTextAlignLast(aValue);
case eCSSProperty_text_decoration_line:
return ParseTextDecorationLine(aValue);
case eCSSProperty_text_combine_upright:
return ParseTextCombineUpright(aValue);
case eCSSProperty_text_emphasis_position:
return ParseTextEmphasisPosition(aValue);
case eCSSProperty_text_emphasis_style:
return ParseTextEmphasisStyle(aValue);
case eCSSProperty_text_overflow:
return ParseTextOverflow(aValue);
case eCSSProperty_touch_action:
return ParseTouchAction(aValue);
default:
MOZ_ASSERT(false, "should not reach here");
return false;
}
}
CSSParseResult
CSSParserImpl::ParseSingleValueProperty(nsCSSValue& aValue,
nsCSSPropertyID aPropID)
{
if (aPropID == eCSSPropertyExtra_x_none_value) {
return ParseVariant(aValue, VARIANT_NONE | VARIANT_INHERIT, nullptr);
}
if (aPropID == eCSSPropertyExtra_x_auto_value) {
return ParseVariant(aValue, VARIANT_AUTO | VARIANT_INHERIT, nullptr);
}
if (aPropID < 0 || aPropID >= eCSSProperty_COUNT_no_shorthands) {
MOZ_ASSERT(false, "not a single value property");
return CSSParseResult::NotFound;
}
if (nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_VALUE_PARSER_FUNCTION)) {
uint32_t lineBefore, colBefore;
if (!GetNextTokenLocation(true, &lineBefore, &colBefore)) {
// We're at EOF before parsing.
return CSSParseResult::NotFound;
}
if (ParseSingleValuePropertyByFunction(aValue, aPropID)) {
return CSSParseResult::Ok;
}
uint32_t lineAfter, colAfter;
if (!GetNextTokenLocation(true, &lineAfter, &colAfter) ||
lineAfter != lineBefore ||
colAfter != colBefore) {
// Any single token value that was invalid will have been pushed back,
// so GetNextTokenLocation encountering EOF means we failed while
// parsing a multi-token value.
return CSSParseResult::Error;
}
return CSSParseResult::NotFound;
}
uint32_t variant = nsCSSProps::ParserVariant(aPropID);
if (variant == 0) {
MOZ_ASSERT(false, "not a single value property");
return CSSParseResult::NotFound;
}
const KTableEntry* kwtable = nsCSSProps::kKeywordTableTable[aPropID];
uint32_t restrictions = nsCSSProps::ValueRestrictions(aPropID);
return ParseVariantWithRestrictions(aValue, variant, kwtable, restrictions);
}
// font-descriptor: descriptor ':' value ';'
// caller has advanced mToken to point at the descriptor
bool
CSSParserImpl::ParseFontDescriptorValue(nsCSSFontDesc aDescID,
nsCSSValue& aValue)
{
switch (aDescID) {
// These four are similar to the properties of the same name,
// possibly with more restrictions on the values they can take.
case eCSSFontDesc_Family: {
nsCSSValue value;
if (!ParseFamily(value) ||
value.GetUnit() != eCSSUnit_FontFamilyList)
return false;
// name can only be a single, non-generic name
const SharedFontList* f = value.GetFontFamilyListValue();
const nsTArray<FontFamilyName>& fontlist = f->mNames;
if (fontlist.Length() != 1 || !fontlist[0].IsNamed()) {
return false;
}
aValue.SetStringValue(fontlist[0].mName, eCSSUnit_String);
return true;
}
case eCSSFontDesc_Style:
// property is VARIANT_HMK|VARIANT_SYSFONT
return ParseSingleTokenVariant(aValue, VARIANT_KEYWORD | VARIANT_NORMAL,
nsCSSProps::kFontStyleKTable);
case eCSSFontDesc_Display:
return ParseSingleTokenVariant(aValue, VARIANT_KEYWORD,
nsCSSProps::kFontDisplayKTable);
case eCSSFontDesc_Weight:
return (ParseFontWeight(aValue) &&
aValue.GetUnit() != eCSSUnit_Inherit &&
aValue.GetUnit() != eCSSUnit_Initial &&
aValue.GetUnit() != eCSSUnit_Unset &&
(aValue.GetUnit() != eCSSUnit_Enumerated ||
(aValue.GetIntValue() != NS_STYLE_FONT_WEIGHT_BOLDER &&
aValue.GetIntValue() != NS_STYLE_FONT_WEIGHT_LIGHTER)));
case eCSSFontDesc_Stretch:
// property is VARIANT_HK|VARIANT_SYSFONT
return ParseSingleTokenVariant(aValue, VARIANT_KEYWORD,
nsCSSProps::kFontStretchKTable);
// These two are unique to @font-face and have their own special grammar.
case eCSSFontDesc_Src:
return ParseFontSrc(aValue);
case eCSSFontDesc_UnicodeRange:
return ParseFontRanges(aValue);
case eCSSFontDesc_FontFeatureSettings:
return ParseFontFeatureSettings(aValue);
case eCSSFontDesc_FontLanguageOverride:
return ParseSingleTokenVariant(aValue, VARIANT_NORMAL | VARIANT_STRING,
nullptr);
case eCSSFontDesc_UNKNOWN:
case eCSSFontDesc_COUNT:
NS_NOTREACHED("bad nsCSSFontDesc code");
}
// explicitly do NOT have a default case to let the compiler
// help find missing descriptors
return false;
}
static nsCSSValue
BoxPositionMaskToCSSValue(int32_t aMask, bool isX)
{
int32_t val = NS_STYLE_IMAGELAYER_POSITION_CENTER;
if (isX) {
if (aMask & BG_LEFT) {
val = NS_STYLE_IMAGELAYER_POSITION_LEFT;
}
else if (aMask & BG_RIGHT) {
val = NS_STYLE_IMAGELAYER_POSITION_RIGHT;
}
}
else {
if (aMask & BG_TOP) {
val = NS_STYLE_IMAGELAYER_POSITION_TOP;
}
else if (aMask & BG_BOTTOM) {
val = NS_STYLE_IMAGELAYER_POSITION_BOTTOM;
}
}
return nsCSSValue(val, eCSSUnit_Enumerated);
}
bool
CSSParserImpl::ParseImageLayers(const nsCSSPropertyID aTable[])
{
nsAutoParseCompoundProperty compound(this);
// background-color can only be set once, so it's not a list.
nsCSSValue color;
// Check first for inherit/initial/unset.
if (ParseSingleTokenVariant(color, VARIANT_INHERIT, nullptr)) {
// must be alone
for (const nsCSSPropertyID* subprops =
nsCSSProps::SubpropertyEntryFor(aTable[nsStyleImageLayers::shorthand]);
*subprops != eCSSProperty_UNKNOWN; ++subprops) {
AppendValue(*subprops, color);
}
return true;
}
nsCSSValue image, repeat, attachment, clip, origin, positionX, positionY, size,
composite, maskMode;
ImageLayersShorthandParseState state(color, image.SetListValue(),
repeat.SetPairListValue(),
attachment.SetListValue(), clip.SetListValue(),
origin.SetListValue(),
positionX.SetListValue(), positionY.SetListValue(),
size.SetPairListValue(), composite.SetListValue(),
maskMode.SetListValue());
for (;;) {
if (!ParseImageLayersItem(state, aTable)) {
return false;
}
// If we saw a color, this must be the last item.
if (color.GetUnit() != eCSSUnit_Null) {
MOZ_ASSERT(aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN);
break;
}
// If there's a comma, expect another item.
if (!ExpectSymbol(',', true)) {
break;
}
#define APPENDNEXT(propID_, propMember_, propType_) \
if (aTable[propID_] != eCSSProperty_UNKNOWN) { \
propMember_->mNext = new propType_; \
propMember_ = propMember_->mNext; \
}
// Chain another entry on all the lists.
APPENDNEXT(nsStyleImageLayers::image, state.mImage,
nsCSSValueList);
APPENDNEXT(nsStyleImageLayers::repeat, state.mRepeat,
nsCSSValuePairList);
APPENDNEXT(nsStyleImageLayers::clip, state.mClip,
nsCSSValueList);
APPENDNEXT(nsStyleImageLayers::origin, state.mOrigin,
nsCSSValueList);
APPENDNEXT(nsStyleImageLayers::positionX, state.mPositionX,
nsCSSValueList);
APPENDNEXT(nsStyleImageLayers::positionY, state.mPositionY,
nsCSSValueList);
APPENDNEXT(nsStyleImageLayers::size, state.mSize,
nsCSSValuePairList);
APPENDNEXT(nsStyleImageLayers::attachment, state.mAttachment,
nsCSSValueList);
APPENDNEXT(nsStyleImageLayers::maskMode, state.mMode,
nsCSSValueList);
APPENDNEXT(nsStyleImageLayers::composite, state.mComposite,
nsCSSValueList);
#undef APPENDNEXT
}
// If we get to this point without seeing a color, provide a default.
if (aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) {
if (color.GetUnit() == eCSSUnit_Null) {
color.SetIntegerColorValue(NS_RGBA(0,0,0,0), eCSSUnit_RGBAColor);
}
}
#define APPENDVALUE(propID_, propValue_) \
if (propID_ != eCSSProperty_UNKNOWN) { \
AppendValue(propID_, propValue_); \
}
APPENDVALUE(aTable[nsStyleImageLayers::image], image);
APPENDVALUE(aTable[nsStyleImageLayers::repeat], repeat);
APPENDVALUE(aTable[nsStyleImageLayers::clip], clip);
APPENDVALUE(aTable[nsStyleImageLayers::origin], origin);
APPENDVALUE(aTable[nsStyleImageLayers::positionX], positionX);
APPENDVALUE(aTable[nsStyleImageLayers::positionY], positionY);
APPENDVALUE(aTable[nsStyleImageLayers::size], size);
APPENDVALUE(aTable[nsStyleImageLayers::color], color);
APPENDVALUE(aTable[nsStyleImageLayers::attachment], attachment);
APPENDVALUE(aTable[nsStyleImageLayers::maskMode], maskMode);
APPENDVALUE(aTable[nsStyleImageLayers::composite], composite);
#undef APPENDVALUE
return true;
}
// Helper for ParseImageLayersItem. Returns true if the passed-in nsCSSToken is
// a function which is accepted for background-image.
bool
CSSParserImpl::IsFunctionTokenValidForImageLayerImage(
const nsCSSToken& aToken) const
{
MOZ_ASSERT(aToken.mType == eCSSToken_Function,
"Should only be called for function-typed tokens");
const nsAString& funcName = aToken.mIdent;
return funcName.LowerCaseEqualsLiteral("linear-gradient") ||
funcName.LowerCaseEqualsLiteral("radial-gradient") ||
funcName.LowerCaseEqualsLiteral("repeating-linear-gradient") ||
funcName.LowerCaseEqualsLiteral("repeating-radial-gradient") ||
funcName.LowerCaseEqualsLiteral("-moz-linear-gradient") ||
funcName.LowerCaseEqualsLiteral("-moz-radial-gradient") ||
funcName.LowerCaseEqualsLiteral("-moz-repeating-linear-gradient") ||
funcName.LowerCaseEqualsLiteral("-moz-repeating-radial-gradient") ||
funcName.LowerCaseEqualsLiteral("-moz-image-rect") ||
funcName.LowerCaseEqualsLiteral("-moz-element") ||
(StylePrefs::sWebkitPrefixedAliasesEnabled &&
(funcName.LowerCaseEqualsLiteral("-webkit-gradient") ||
funcName.LowerCaseEqualsLiteral("-webkit-linear-gradient") ||
funcName.LowerCaseEqualsLiteral("-webkit-radial-gradient") ||
funcName.LowerCaseEqualsLiteral("-webkit-repeating-linear-gradient") ||
funcName.LowerCaseEqualsLiteral("-webkit-repeating-radial-gradient")));
}
// Parse one item of the background shorthand property.
bool
CSSParserImpl::ParseImageLayersItem(
CSSParserImpl::ImageLayersShorthandParseState& aState,
const nsCSSPropertyID aTable[])
{
// Fill in the values that the shorthand will set if we don't find
// other values.
aState.mImage->mValue.SetNoneValue();
aState.mAttachment->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ATTACHMENT_SCROLL,
eCSSUnit_Enumerated);
aState.mClip->mValue.SetEnumValue(StyleGeometryBox::BorderBox);
aState.mRepeat->mXValue.SetIntValue(uint8_t(StyleImageLayerRepeat::Repeat),
eCSSUnit_Enumerated);
aState.mRepeat->mYValue.Reset();
RefPtr<nsCSSValue::Array> positionXArr = nsCSSValue::Array::Create(2);
RefPtr<nsCSSValue::Array> positionYArr = nsCSSValue::Array::Create(2);
aState.mPositionX->mValue.SetArrayValue(positionXArr, eCSSUnit_Array);
aState.mPositionY->mValue.SetArrayValue(positionYArr, eCSSUnit_Array);
if (eCSSProperty_mask == aTable[nsStyleImageLayers::shorthand]) {
aState.mOrigin->mValue.SetEnumValue(StyleGeometryBox::BorderBox);
} else {
aState.mOrigin->mValue.SetEnumValue(StyleGeometryBox::PaddingBox);
}
positionXArr->Item(1).SetPercentValue(0.0f);
positionYArr->Item(1).SetPercentValue(0.0f);
aState.mSize->mXValue.SetAutoValue();
aState.mSize->mYValue.SetAutoValue();
aState.mComposite->mValue.SetIntValue(NS_STYLE_MASK_COMPOSITE_ADD,
eCSSUnit_Enumerated);
aState.mMode->mValue.SetIntValue(NS_STYLE_MASK_MODE_MATCH_SOURCE,
eCSSUnit_Enumerated);
bool haveColor = false,
haveImage = false,
haveRepeat = false,
haveAttach = false,
havePositionAndSize = false,
haveOrigin = false,
haveClip = false,
haveComposite = false,
haveMode = false,
haveSomething = false;
const KTableEntry* originTable =
nsCSSProps::kKeywordTableTable[aTable[nsStyleImageLayers::origin]];
const KTableEntry* clipTable =
nsCSSProps::kKeywordTableTable[aTable[nsStyleImageLayers::clip]];
while (GetToken(true)) {
nsCSSTokenType tt = mToken.mType;
UngetToken(); // ...but we'll still cheat and use mToken
if (tt == eCSSToken_Symbol) {
// ExpectEndProperty only looks for symbols, and nothing else will
// show up as one.
break;
}
if (tt == eCSSToken_Ident) {
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
int32_t dummy;
if (keyword == eCSSKeyword_inherit ||
keyword == eCSSKeyword_initial ||
keyword == eCSSKeyword_unset) {
return false;
} else if (!haveImage && keyword == eCSSKeyword_none) {
haveImage = true;
if (ParseSingleValueProperty(aState.mImage->mValue,
aTable[nsStyleImageLayers::image]) !=
CSSParseResult::Ok) {
NS_NOTREACHED("should be able to parse");
return false;
}
} else if (!haveAttach &&
aTable[nsStyleImageLayers::attachment] !=
eCSSProperty_UNKNOWN &&
nsCSSProps::FindKeyword(
keyword, nsCSSProps::kImageLayerAttachmentKTable, dummy)) {
haveAttach = true;
if (ParseSingleValueProperty(aState.mAttachment->mValue,
aTable[nsStyleImageLayers::attachment]) !=
CSSParseResult::Ok) {
NS_NOTREACHED("should be able to parse");
return false;
}
} else if (!haveRepeat &&
nsCSSProps::FindKeyword(
keyword, nsCSSProps::kImageLayerRepeatKTable, dummy)) {
haveRepeat = true;
nsCSSValuePair scratch;
if (!ParseImageLayerRepeatValues(scratch)) {
NS_NOTREACHED("should be able to parse");
return false;
}
aState.mRepeat->mXValue = scratch.mXValue;
aState.mRepeat->mYValue = scratch.mYValue;
} else if (!havePositionAndSize &&
nsCSSProps::FindKeyword(keyword,
nsCSSProps::kImageLayerPositionKTable, dummy)) {
havePositionAndSize = true;
if (!ParsePositionValueSeparateCoords(aState.mPositionX->mValue,
aState.mPositionY->mValue)) {
return false;
}
if (ExpectSymbol('/', true)) {
nsCSSValuePair scratch;
if (!ParseImageLayerSizeValues(scratch)) {
return false;
}
aState.mSize->mXValue = scratch.mXValue;
aState.mSize->mYValue = scratch.mYValue;
}
} else if (!haveOrigin &&
nsCSSProps::FindKeyword(keyword, originTable, dummy)) {
haveOrigin = true;
if (ParseSingleValueProperty(aState.mOrigin->mValue,
aTable[nsStyleImageLayers::origin]) !=
CSSParseResult::Ok) {
NS_NOTREACHED("should be able to parse");
return false;
}
// Set clip value to origin if clip is not set yet.
// Note that we don't set haveClip here so that it can be
// overridden if we see it later.
if (!haveClip) {
#ifdef DEBUG
for (size_t i = 0; originTable[i].mValue != -1; i++) {
// For each keyword & value in kOriginKTable, ensure that
// kBackgroundKTable has a matching entry at the same position.
MOZ_ASSERT(originTable[i].mKeyword == clipTable[i].mKeyword);
MOZ_ASSERT(originTable[i].mValue == clipTable[i].mValue);
}
#endif
aState.mClip->mValue = aState.mOrigin->mValue;
}
} else if (!haveClip &&
nsCSSProps::FindKeyword(keyword, clipTable, dummy)) {
// It is important that we try parsing clip later than origin
// because if there are two <box> / <geometry-box> values, the
// first should be origin, and the second should be clip.
haveClip = true;
if (ParseSingleValueProperty(aState.mClip->mValue,
aTable[nsStyleImageLayers::clip]) !=
CSSParseResult::Ok) {
NS_NOTREACHED("should be able to parse");
return false;
}
} else if (!haveComposite &&
aTable[nsStyleImageLayers::composite] !=
eCSSProperty_UNKNOWN &&
nsCSSProps::FindKeyword(
keyword, nsCSSProps::kImageLayerCompositeKTable, dummy)) {
haveComposite = true;
if (ParseSingleValueProperty(aState.mComposite->mValue,
aTable[nsStyleImageLayers::composite]) !=
CSSParseResult::Ok) {
NS_NOTREACHED("should be able to parse");
return false;
}
} else if (!haveMode &&
aTable[nsStyleImageLayers::maskMode] != eCSSProperty_UNKNOWN &&
nsCSSProps::FindKeyword(
keyword, nsCSSProps::kImageLayerModeKTable, dummy)) {
haveMode = true;
if (ParseSingleValueProperty(aState.mMode->mValue,
aTable[nsStyleImageLayers::maskMode]) !=
CSSParseResult::Ok) {
NS_NOTREACHED("should be able to parse");
return false;
}
} else if (!haveColor &&
aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) {
haveColor = true;
if (ParseSingleValueProperty(aState.mColor,
aTable[nsStyleImageLayers::color]) !=
CSSParseResult::Ok) {
return false;
}
} else {
return false;
}
} else if (tt == eCSSToken_URL ||
(tt == eCSSToken_Function &&
IsFunctionTokenValidForImageLayerImage(mToken))) {
if (haveImage)
return false;
haveImage = true;
if (ParseSingleValueProperty(aState.mImage->mValue,
aTable[nsStyleImageLayers::image]) !=
CSSParseResult::Ok) {
return false;
}
} else if (tt == eCSSToken_Dimension ||
tt == eCSSToken_Number ||
tt == eCSSToken_Percentage ||
(tt == eCSSToken_Function &&
mToken.mIdent.LowerCaseEqualsLiteral("calc"))) {
if (havePositionAndSize)
return false;
havePositionAndSize = true;
if (!ParsePositionValueSeparateCoords(aState.mPositionX->mValue,
aState.mPositionY->mValue)) {
return false;
}
if (ExpectSymbol('/', true)) {
nsCSSValuePair scratch;
if (!ParseImageLayerSizeValues(scratch)) {
return false;
}
aState.mSize->mXValue = scratch.mXValue;
aState.mSize->mYValue = scratch.mYValue;
}
} else if (aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) {
if (haveColor)
return false;
haveColor = true;
// Note: This parses 'inherit', 'initial' and 'unset', but
// we've already checked for them, so it's ok.
if (ParseSingleValueProperty(aState.mColor,
aTable[nsStyleImageLayers::color]) !=
CSSParseResult::Ok) {
return false;
}
} else {
return false;
}
haveSomething = true;
}
return haveSomething;
}
// This function is very similar to ParseScrollSnapCoordinate,
// ParseImageLayerPosition, and ParseImageLayersSize.
bool
CSSParserImpl::ParseValueList(nsCSSPropertyID aPropID)
{
// aPropID is a single value prop-id
nsCSSValue value;
// 'initial', 'inherit' and 'unset' stand alone, no list permitted.
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
nsCSSValueList* item = value.SetListValue();
for (;;) {
if (ParseSingleValueProperty(item->mValue, aPropID) !=
CSSParseResult::Ok) {
return false;
}
if (!ExpectSymbol(',', true)) {
break;
}
item->mNext = new nsCSSValueList;
item = item->mNext;
}
}
AppendValue(aPropID, value);
return true;
}
bool
CSSParserImpl::ParseImageLayerRepeat(nsCSSPropertyID aPropID)
{
nsCSSValue value;
// 'initial', 'inherit' and 'unset' stand alone, no list permitted.
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
nsCSSValuePair valuePair;
if (!ParseImageLayerRepeatValues(valuePair)) {
return false;
}
nsCSSValuePairList* item = value.SetPairListValue();
for (;;) {
item->mXValue = valuePair.mXValue;
item->mYValue = valuePair.mYValue;
if (!ExpectSymbol(',', true)) {
break;
}
if (!ParseImageLayerRepeatValues(valuePair)) {
return false;
}
item->mNext = new nsCSSValuePairList;
item = item->mNext;
}
}
AppendValue(aPropID, value);
return true;
}
bool
CSSParserImpl::ParseImageLayerRepeatValues(nsCSSValuePair& aValue)
{
nsCSSValue& xValue = aValue.mXValue;
nsCSSValue& yValue = aValue.mYValue;
if (ParseEnum(xValue, nsCSSProps::kImageLayerRepeatKTable)) {
int32_t value = xValue.GetIntValue();
// For single values set yValue as eCSSUnit_Null.
if (value == uint8_t(StyleImageLayerRepeat::RepeatX) ||
value == uint8_t(StyleImageLayerRepeat::RepeatY) ||
!ParseEnum(yValue, nsCSSProps::kImageLayerRepeatPartKTable)) {
// the caller will fail cases like "repeat-x no-repeat"
// by expecting a list separator or an end property.
yValue.Reset();
}
return true;
}
return false;
}
bool
CSSParserImpl::ParseImageLayerPosition(const nsCSSPropertyID aTable[])
{
// 'initial', 'inherit' and 'unset' stand alone, no list permitted.
nsCSSValue position;
if (ParseSingleTokenVariant(position, VARIANT_INHERIT, nullptr)) {
AppendValue(aTable[nsStyleImageLayers::positionX], position);
AppendValue(aTable[nsStyleImageLayers::positionY], position);
return true;
}
nsCSSValue itemValueX;
nsCSSValue itemValueY;
if (!ParsePositionValueSeparateCoords(itemValueX, itemValueY)) {
return false;
}
nsCSSValue valueX;
nsCSSValue valueY;
nsCSSValueList* itemX = valueX.SetListValue();
nsCSSValueList* itemY = valueY.SetListValue();
for (;;) {
itemX->mValue = itemValueX;
itemY->mValue = itemValueY;
if (!ExpectSymbol(',', true)) {
break;
}
if (!ParsePositionValueSeparateCoords(itemValueX, itemValueY)) {
return false;
}
itemX->mNext = new nsCSSValueList;
itemY->mNext = new nsCSSValueList;
itemX = itemX->mNext;
itemY = itemY->mNext;
}
AppendValue(aTable[nsStyleImageLayers::positionX], valueX);
AppendValue(aTable[nsStyleImageLayers::positionY], valueY);
return true;
}
bool
CSSParserImpl::ParseImageLayerPositionCoord(nsCSSPropertyID aPropID, bool aIsHorizontal)
{
nsCSSValue value;
// 'initial', 'inherit' and 'unset' stand alone, no list permitted.
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
nsCSSValue itemValue;
if (!ParseImageLayerPositionCoordItem(itemValue, aIsHorizontal)) {
return false;
}
nsCSSValueList* item = value.SetListValue();
for (;;) {
item->mValue = itemValue;
if (!ExpectSymbol(',', true)) {
break;
}
if (!ParseImageLayerPositionCoordItem(itemValue, aIsHorizontal)) {
return false;
}
item->mNext = new nsCSSValueList;
item = item->mNext;
}
}
AppendValue(aPropID, value);
return true;
}
/**
* BoxPositionMaskToCSSValue and ParseBoxPositionValues are used
* for parsing the CSS 2.1 background-position syntax (which has at
* most two values). (Compare to the css3-background syntax which
* takes up to four values.) Some current CSS specifications that
* use background-position-like syntax still use this old syntax.
**
* Parses two values that correspond to positions in a box. These can be
* values corresponding to percentages of the box, raw offsets, or keywords
* like "top," "left center," etc.
*
* @param aOut The nsCSSValuePair in which to place the result.
* @param aAcceptsInherit If true, 'inherit', 'initial' and 'unset' are
* legal values
* @param aAllowExplicitCenter If true, 'center' is a legal value
* @return Whether or not the operation succeeded.
*/
bool CSSParserImpl::ParseBoxPositionValues(nsCSSValuePair &aOut,
bool aAcceptsInherit,
bool aAllowExplicitCenter)
{
// First try a percentage or a length value
nsCSSValue &xValue = aOut.mXValue,
&yValue = aOut.mYValue;
int32_t variantMask =
(aAcceptsInherit ? VARIANT_INHERIT : 0) | VARIANT_LP | VARIANT_CALC;
CSSParseResult result = ParseVariant(xValue, variantMask, nullptr);
if (result == CSSParseResult::Error) {
return false;
} else if (result == CSSParseResult::Ok) {
if (eCSSUnit_Inherit == xValue.GetUnit() ||
eCSSUnit_Initial == xValue.GetUnit() ||
eCSSUnit_Unset == xValue.GetUnit()) { // both are inherit, initial or unset
yValue = xValue;
return true;
}
// We have one percentage/length/calc. Get the optional second
// percentage/length/calc/keyword.
result = ParseVariant(yValue, VARIANT_LP | VARIANT_CALC, nullptr);
if (result == CSSParseResult::Error) {
return false;
} else if (result == CSSParseResult::Ok) {
// We have two numbers
return true;
}
if (ParseEnum(yValue, nsCSSProps::kImageLayerPositionKTable)) {
int32_t yVal = yValue.GetIntValue();
if (!(yVal & BG_CTB)) {
// The second keyword can only be 'center', 'top', or 'bottom'
return false;
}
yValue = BoxPositionMaskToCSSValue(yVal, false);
return true;
}
// If only one percentage or length value is given, it sets the
// horizontal position only, and the vertical position will be 50%.
yValue.SetPercentValue(0.5f);
return true;
}
// Now try keywords. We do this manually to allow for the first
// appearance of "center" to apply to the either the x or y
// position (it's ambiguous so we have to disambiguate). Each
// allowed keyword value is assigned it's own bit. We don't allow
// any duplicate keywords other than center. We try to get two
// keywords but it's okay if there is only one.
int32_t mask = 0;
if (ParseEnum(xValue, nsCSSProps::kImageLayerPositionKTable)) {
int32_t bit = xValue.GetIntValue();
mask |= bit;
if (ParseEnum(xValue, nsCSSProps::kImageLayerPositionKTable)) {
bit = xValue.GetIntValue();
if (mask & (bit & ~BG_CENTER)) {
// Only the 'center' keyword can be duplicated.
return false;
}
mask |= bit;
}
else {
// Only one keyword. See if we have a length, percentage, or calc.
result = ParseVariant(yValue, VARIANT_LP | VARIANT_CALC, nullptr);
if (result == CSSParseResult::Error) {
return false;
} else if (result == CSSParseResult::Ok) {
if (!(mask & BG_CLR)) {
// The first keyword can only be 'center', 'left', or 'right'
return false;
}
xValue = BoxPositionMaskToCSSValue(mask, true);
return true;
}
}
}
// Check for bad input. Bad input consists of no matching keywords,
// or pairs of x keywords or pairs of y keywords.
if ((mask == 0) || (mask == (BG_TOP | BG_BOTTOM)) ||
(mask == (BG_LEFT | BG_RIGHT)) ||
(!aAllowExplicitCenter && (mask & BG_CENTER))) {
return false;
}
// Create style values
xValue = BoxPositionMaskToCSSValue(mask, true);
yValue = BoxPositionMaskToCSSValue(mask, false);
return true;
}
// Parses a CSS <position> value, for e.g. the 'background-position' property.
// Spec reference: http://www.w3.org/TR/css3-background/#ltpositiongt
// Invariants:
// - Always produces a four-value array on a successful parse.
// - The values are: X edge, X offset, Y edge, Y offset.
// - Edges are always keywords or null.
// - A |center| edge will not have an offset.
bool
CSSParserImpl::ParsePositionValue(nsCSSValue& aOut)
{
RefPtr<nsCSSValue::Array> value = nsCSSValue::Array::Create(4);
aOut.SetArrayValue(value, eCSSUnit_Array);
// The following clarifies organisation of the array.
nsCSSValue &xEdge = value->Item(0),
&xOffset = value->Item(1),
&yEdge = value->Item(2),
&yOffset = value->Item(3);
// Parse all the values into the array.
uint32_t valueCount = 0;
for (int32_t i = 0; i < 4; i++) {
CSSParseResult result =
ParseVariant(value->Item(i), VARIANT_LPCALC | VARIANT_KEYWORD,
nsCSSProps::kImageLayerPositionKTable);
if (result == CSSParseResult::Error) {
return false;
} else if (result == CSSParseResult::NotFound) {
break;
}
++valueCount;
}
switch (valueCount) {
case 4:
// "If three or four values are given, then each <percentage> or <length>
// represents an offset and must be preceded by a keyword, which specifies
// from which edge the offset is given."
if (eCSSUnit_Enumerated != xEdge.GetUnit() ||
BG_CENTER == xEdge.GetIntValue() ||
eCSSUnit_Enumerated == xOffset.GetUnit() ||
eCSSUnit_Enumerated != yEdge.GetUnit() ||
BG_CENTER == yEdge.GetIntValue() ||
eCSSUnit_Enumerated == yOffset.GetUnit()) {
return false;
}
break;
case 3:
// "If three or four values are given, then each <percentage> or<length>
// represents an offset and must be preceded by a keyword, which specifies
// from which edge the offset is given." ... "If three values are given,
// the missing offset is assumed to be zero."
if (eCSSUnit_Enumerated != value->Item(1).GetUnit()) {
// keyword offset keyword
// Second value is non-keyword, thus first value must be a non-center
// keyword.
if (eCSSUnit_Enumerated != value->Item(0).GetUnit() ||
BG_CENTER == value->Item(0).GetIntValue()) {
return false;
}
// Remaining value must be a keyword.
if (eCSSUnit_Enumerated != value->Item(2).GetUnit()) {
return false;
}
yOffset.Reset(); // Everything else is in the correct position.
} else if (eCSSUnit_Enumerated != value->Item(2).GetUnit()) {
// keyword keyword offset
// Third value is non-keyword, thus second value must be non-center
// keyword.
if (BG_CENTER == value->Item(1).GetIntValue()) {
return false;
}
// Remaining value must be a keyword.
if (eCSSUnit_Enumerated != value->Item(0).GetUnit()) {
return false;
}
// Move the values to the correct position in the array.
value->Item(3) = value->Item(2); // yOffset
value->Item(2) = value->Item(1); // yEdge
value->Item(1).Reset(); // xOffset
} else {
return false;
}
break;
case 2:
// "If two values are given and at least one value is not a keyword, then
// the first value represents the horizontal position (or offset) and the
// second represents the vertical position (or offset)"
if (eCSSUnit_Enumerated == value->Item(0).GetUnit()) {
if (eCSSUnit_Enumerated == value->Item(1).GetUnit()) {
// keyword keyword
value->Item(2) = value->Item(1); // move yEdge to correct position
xOffset.Reset();
yOffset.Reset();
} else {
// keyword offset
// First value must represent horizontal position.
if ((BG_TOP | BG_BOTTOM) & value->Item(0).GetIntValue()) {
return false;
}
value->Item(3) = value->Item(1); // move yOffset to correct position
xOffset.Reset();
yEdge.Reset();
}
} else {
if (eCSSUnit_Enumerated == value->Item(1).GetUnit()) {
// offset keyword
// Second value must represent vertical position.
if ((BG_LEFT | BG_RIGHT) & value->Item(1).GetIntValue()) {
return false;
}
value->Item(2) = value->Item(1); // move yEdge to correct position
value->Item(1) = value->Item(0); // move xOffset to correct position
xEdge.Reset();
yOffset.Reset();
} else {
// offset offset
value->Item(3) = value->Item(1); // move yOffset to correct position
value->Item(1) = value->Item(0); // move xOffset to correct position
xEdge.Reset();
yEdge.Reset();
}
}
break;
case 1:
// "If only one value is specified, the second value is assumed to be
// center."
if (eCSSUnit_Enumerated == value->Item(0).GetUnit()) {
xOffset.Reset();
} else {
value->Item(1) = value->Item(0); // move xOffset to correct position
xEdge.Reset();
}
yEdge.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_CENTER, eCSSUnit_Enumerated);
yOffset.Reset();
break;
default:
return false;
}
// For compatibility with CSS2.1 code the edges can be unspecified.
// Unspecified edges are recorded as nullptr.
NS_ASSERTION((eCSSUnit_Enumerated == xEdge.GetUnit() ||
eCSSUnit_Null == xEdge.GetUnit()) &&
(eCSSUnit_Enumerated == yEdge.GetUnit() ||
eCSSUnit_Null == yEdge.GetUnit()) &&
eCSSUnit_Enumerated != xOffset.GetUnit() &&
eCSSUnit_Enumerated != yOffset.GetUnit(),
"Unexpected units");
// Keywords in first and second pairs can not both be vertical or
// horizontal keywords. (eg. left right, bottom top). Additionally,
// non-center keyword can not be duplicated (eg. left left).
int32_t xEdgeEnum =
xEdge.GetUnit() == eCSSUnit_Enumerated ? xEdge.GetIntValue() : 0;
int32_t yEdgeEnum =
yEdge.GetUnit() == eCSSUnit_Enumerated ? yEdge.GetIntValue() : 0;
if ((xEdgeEnum | yEdgeEnum) == (BG_LEFT | BG_RIGHT) ||
(xEdgeEnum | yEdgeEnum) == (BG_TOP | BG_BOTTOM) ||
(xEdgeEnum & yEdgeEnum & ~BG_CENTER)) {
return false;
}
// The values could be in an order that is different than expected.
// eg. x contains vertical information, y contains horizontal information.
// Swap if incorrect order.
if (xEdgeEnum & (BG_TOP | BG_BOTTOM) ||
yEdgeEnum & (BG_LEFT | BG_RIGHT)) {
nsCSSValue swapEdge = xEdge;
nsCSSValue swapOffset = xOffset;
xEdge = yEdge;
xOffset = yOffset;
yEdge = swapEdge;
yOffset = swapOffset;
}
return true;
}
static void
AdjustEdgeOffsetPairForBasicShape(nsCSSValue& aEdge,
nsCSSValue& aOffset,
uint8_t aDefaultEdge)
{
// 0 length offsets are 0%
if (aOffset.IsLengthUnit() && aOffset.GetFloatValue() == 0.0) {
aOffset.SetPercentValue(0);
}
// Default edge is top/left in the 4-value case
// In case of 1 or 0 values, the default is center,
// but ParsePositionValue already handles this case
if (eCSSUnit_Null == aEdge.GetUnit()) {
aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
}
// Default offset is 0%
if (eCSSUnit_Null == aOffset.GetUnit()) {
aOffset.SetPercentValue(0.0);
}
if (eCSSUnit_Enumerated == aEdge.GetUnit() &&
eCSSUnit_Percent == aOffset.GetUnit()) {
switch (aEdge.GetIntValue()) {
case NS_STYLE_IMAGELAYER_POSITION_CENTER:
aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
MOZ_ASSERT(aOffset.GetPercentValue() == 0.0,
"center cannot be used with an offset");
aOffset.SetPercentValue(0.5);
break;
case NS_STYLE_IMAGELAYER_POSITION_BOTTOM:
MOZ_ASSERT(aDefaultEdge == NS_STYLE_IMAGELAYER_POSITION_TOP);
aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
aOffset.SetPercentValue(1 - aOffset.GetPercentValue());
break;
case NS_STYLE_IMAGELAYER_POSITION_RIGHT:
MOZ_ASSERT(aDefaultEdge == NS_STYLE_IMAGELAYER_POSITION_LEFT);
aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
aOffset.SetPercentValue(1 - aOffset.GetPercentValue());
}
}
}
// https://drafts.csswg.org/css-shapes/#basic-shape-serialization
// We set values to defaults while parsing for basic shapes
// Invariants:
// - Always produces a four-value array on a successful parse.
// - The values are: X edge, X offset, Y edge, Y offset
// - Edges are always keywords (not including center)
// - Offsets are nonnull
// - Percentage offsets have keywords folded into them,
// so "bottom 40%" or "right 20%" will not exist.
bool
CSSParserImpl::ParsePositionValueForBasicShape(nsCSSValue& aOut)
{
if (!ParsePositionValue(aOut)) {
return false;
}
nsCSSValue::Array* value = aOut.GetArrayValue();
nsCSSValue& xEdge = value->Item(0);
nsCSSValue& xOffset = value->Item(1);
nsCSSValue& yEdge = value->Item(2);
nsCSSValue& yOffset = value->Item(3);
// A keyword edge + percent offset pair can be contracted
// into the percentage with the default value in the edge.
// Offset lengths which are 0 can also be rewritten as 0%
AdjustEdgeOffsetPairForBasicShape(xEdge, xOffset,
NS_STYLE_IMAGELAYER_POSITION_LEFT);
AdjustEdgeOffsetPairForBasicShape(yEdge, yOffset,
NS_STYLE_IMAGELAYER_POSITION_TOP);
return true;
}
bool
CSSParserImpl::ParsePositionValueSeparateCoords(nsCSSValue& aOutX, nsCSSValue& aOutY)
{
nsCSSValue scratch;
if (!ParsePositionValue(scratch)) {
return false;
}
// Separate the four values into two pairs of two values for X and Y.
RefPtr<nsCSSValue::Array> valueX = nsCSSValue::Array::Create(2);
RefPtr<nsCSSValue::Array> valueY = nsCSSValue::Array::Create(2);
aOutX.SetArrayValue(valueX, eCSSUnit_Array);
aOutY.SetArrayValue(valueY, eCSSUnit_Array);
RefPtr<nsCSSValue::Array> value = scratch.GetArrayValue();
valueX->Item(0) = value->Item(0);
valueX->Item(1) = value->Item(1);
valueY->Item(0) = value->Item(2);
valueY->Item(1) = value->Item(3);
return true;
}
// Parses one item in a list of values for the 'background-position-x' or
// 'background-position-y' property. Does not support the start/end keywords.
// Spec reference: https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-x
bool
CSSParserImpl::ParseImageLayerPositionCoordItem(nsCSSValue& aOut, bool aIsHorizontal)
{
RefPtr<nsCSSValue::Array> value = nsCSSValue::Array::Create(2);
aOut.SetArrayValue(value, eCSSUnit_Array);
nsCSSValue &edge = value->Item(0),
&offset = value->Item(1);
nsCSSValue edgeOrOffset;
CSSParseResult result =
ParseVariant(edgeOrOffset, VARIANT_LPCALC | VARIANT_KEYWORD,
nsCSSProps::kImageLayerPositionKTable);
if (result != CSSParseResult::Ok) {
return false;
}
if (edgeOrOffset.GetUnit() == eCSSUnit_Enumerated) {
edge = edgeOrOffset;
// The edge can be followed by an optional offset.
result = ParseVariant(offset, VARIANT_LPCALC, nullptr);
if (result == CSSParseResult::Error) {
return false;
}
} else {
offset = edgeOrOffset;
}
// Keywords for horizontal properties cannot be vertical keywords, and
// keywords for vertical properties cannot be horizontal keywords.
// Also, if an offset is specified, the edge cannot be center.
int32_t edgeEnum =
edge.GetUnit() == eCSSUnit_Enumerated ? edge.GetIntValue() : 0;
int32_t allowedKeywords =
(aIsHorizontal ? (BG_LEFT | BG_RIGHT) : (BG_TOP | BG_BOTTOM)) |
(offset.GetUnit() == eCSSUnit_Null ? BG_CENTER : 0);
if (edgeEnum & ~allowedKeywords) {
return false;
}
NS_ASSERTION((eCSSUnit_Enumerated == edge.GetUnit() ||
eCSSUnit_Null == edge.GetUnit()) &&
eCSSUnit_Enumerated != offset.GetUnit(),
"Unexpected units");
return true;
}
// This function is very similar to ParseScrollSnapCoordinate,
// ParseImageLayers, and ParseImageLayerPosition.
bool
CSSParserImpl::ParseImageLayerSize(nsCSSPropertyID aPropID)
{
nsCSSValue value;
// 'initial', 'inherit' and 'unset' stand alone, no list permitted.
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
nsCSSValuePair valuePair;
if (!ParseImageLayerSizeValues(valuePair)) {
return false;
}
nsCSSValuePairList* item = value.SetPairListValue();
for (;;) {
item->mXValue = valuePair.mXValue;
item->mYValue = valuePair.mYValue;
if (!ExpectSymbol(',', true)) {
break;
}
if (!ParseImageLayerSizeValues(valuePair)) {
return false;
}
item->mNext = new nsCSSValuePairList;
item = item->mNext;
}
}
AppendValue(aPropID, value);
return true;
}
/**
* Parses two values that correspond to lengths for the background-size
* property. These can be one or two lengths (or the 'auto' keyword) or
* percentages corresponding to the element's dimensions or the single keywords
* 'contain' or 'cover'. 'initial', 'inherit' and 'unset' must be handled by
* the caller if desired.
*
* @param aOut The nsCSSValuePair in which to place the result.
* @return Whether or not the operation succeeded.
*/
#define BG_SIZE_VARIANT (VARIANT_LP | VARIANT_AUTO | VARIANT_CALC)
bool CSSParserImpl::ParseImageLayerSizeValues(nsCSSValuePair &aOut)
{
// First try a percentage or a length value
nsCSSValue &xValue = aOut.mXValue,
&yValue = aOut.mYValue;
CSSParseResult result =
ParseNonNegativeVariant(xValue, BG_SIZE_VARIANT, nullptr);
if (result == CSSParseResult::Error) {
return false;
} else if (result == CSSParseResult::Ok) {
// We have one percentage/length/calc/auto. Get the optional second
// percentage/length/calc/keyword.
result = ParseNonNegativeVariant(yValue, BG_SIZE_VARIANT, nullptr);
if (result == CSSParseResult::Error) {
return false;
} else if (result == CSSParseResult::Ok) {
// We have a second percentage/length/calc/auto.
return true;
}
// If only one percentage or length value is given, it sets the
// horizontal size only, and the vertical size will be as if by 'auto'.
yValue.SetAutoValue();
return true;
}
// Now address 'contain' and 'cover'.
if (!ParseEnum(xValue, nsCSSProps::kImageLayerSizeKTable))
return false;
yValue.Reset();
return true;
}
#undef BG_SIZE_VARIANT
bool
CSSParserImpl::ParseBorderColor()
{
return ParseBoxProperties(kBorderColorIDs);
}
void
CSSParserImpl::SetBorderImageInitialValues()
{
// border-image-source: none
nsCSSValue source;
source.SetNoneValue();
AppendValue(eCSSProperty_border_image_source, source);
// border-image-slice: 100%
nsCSSValue sliceBoxValue;
nsCSSRect& sliceBox = sliceBoxValue.SetRectValue();
sliceBox.SetAllSidesTo(nsCSSValue(1.0f, eCSSUnit_Percent));
nsCSSValue slice;
nsCSSValueList* sliceList = slice.SetListValue();
sliceList->mValue = sliceBoxValue;
AppendValue(eCSSProperty_border_image_slice, slice);
// border-image-width: 1
nsCSSValue width;
nsCSSRect& widthBox = width.SetRectValue();
widthBox.SetAllSidesTo(nsCSSValue(1.0f, eCSSUnit_Number));
AppendValue(eCSSProperty_border_image_width, width);
// border-image-outset: 0
nsCSSValue outset;
nsCSSRect& outsetBox = outset.SetRectValue();
outsetBox.SetAllSidesTo(nsCSSValue(0.0f, eCSSUnit_Number));
AppendValue(eCSSProperty_border_image_outset, outset);
// border-image-repeat: repeat
nsCSSValue repeat;
nsCSSValuePair repeatPair;
repeatPair.SetBothValuesTo(nsCSSValue(NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH,
eCSSUnit_Enumerated));
repeat.SetPairValue(&repeatPair);
AppendValue(eCSSProperty_border_image_repeat, repeat);
}
bool
CSSParserImpl::ParseBorderImageSlice(bool aAcceptsInherit,
bool* aConsumedTokens)
{
// border-image-slice: initial | [<number>|<percentage>]{1,4} && fill?
nsCSSValue value;
if (aConsumedTokens) {
*aConsumedTokens = true;
}
if (aAcceptsInherit &&
ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
// Keywords "inherit", "initial" and "unset" can not be mixed, so we
// are done.
AppendValue(eCSSProperty_border_image_slice, value);
return true;
}
// Try parsing "fill" value.
nsCSSValue imageSliceFillValue;
bool hasFill = ParseEnum(imageSliceFillValue,
nsCSSProps::kBorderImageSliceKTable);
// Parse the box dimensions.
nsCSSValue imageSliceBoxValue;
if (!ParseGroupedBoxProperty(VARIANT_PN, imageSliceBoxValue,
CSS_PROPERTY_VALUE_NONNEGATIVE)) {
if (!hasFill && aConsumedTokens) {
*aConsumedTokens = false;
}
return false;
}
// Try parsing "fill" keyword again if the first time failed because keyword
// and slice dimensions can be in any order.
if (!hasFill) {
hasFill = ParseEnum(imageSliceFillValue,
nsCSSProps::kBorderImageSliceKTable);
}
nsCSSValueList* borderImageSlice = value.SetListValue();
// Put the box value into the list.
borderImageSlice->mValue = imageSliceBoxValue;
if (hasFill) {
// Put the "fill" value into the list.
borderImageSlice->mNext = new nsCSSValueList;
borderImageSlice->mNext->mValue = imageSliceFillValue;
}
AppendValue(eCSSProperty_border_image_slice, value);
return true;
}
bool
CSSParserImpl::ParseBorderImageWidth(bool aAcceptsInherit)
{
// border-image-width: initial | [<length>|<number>|<percentage>|auto]{1,4}
nsCSSValue value;
if (aAcceptsInherit &&
ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
// Keywords "inherit", "initial" and "unset" can not be mixed, so we
// are done.
AppendValue(eCSSProperty_border_image_width, value);
return true;
}
// Parse the box dimensions.
if (!ParseGroupedBoxProperty(VARIANT_ALPN, value, CSS_PROPERTY_VALUE_NONNEGATIVE)) {
return false;
}
AppendValue(eCSSProperty_border_image_width, value);
return true;
}
bool
CSSParserImpl::ParseBorderImageOutset(bool aAcceptsInherit)
{
// border-image-outset: initial | [<length>|<number>]{1,4}
nsCSSValue value;
if (aAcceptsInherit &&
ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
// Keywords "inherit", "initial" and "unset" can not be mixed, so we
// are done.
AppendValue(eCSSProperty_border_image_outset, value);
return true;
}
// Parse the box dimensions.
if (!ParseGroupedBoxProperty(VARIANT_LN, value, CSS_PROPERTY_VALUE_NONNEGATIVE)) {
return false;
}
AppendValue(eCSSProperty_border_image_outset, value);
return true;
}
bool
CSSParserImpl::ParseBorderImageRepeat(bool aAcceptsInherit)
{
nsCSSValue value;
if (aAcceptsInherit &&
ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
// Keywords "inherit", "initial" and "unset" can not be mixed, so we
// are done.
AppendValue(eCSSProperty_border_image_repeat, value);
return true;
}
nsCSSValuePair result;
if (!ParseEnum(result.mXValue, nsCSSProps::kBorderImageRepeatKTable)) {
return false;
}
// optional second keyword, defaults to first
if (!ParseEnum(result.mYValue, nsCSSProps::kBorderImageRepeatKTable)) {
result.mYValue = result.mXValue;
}
value.SetPairValue(&result);
AppendValue(eCSSProperty_border_image_repeat, value);
return true;
}
bool
CSSParserImpl::ParseBorderImage()
{
nsAutoParseCompoundProperty compound(this);
// border-image: inherit | initial |
// <border-image-source> ||
// <border-image-slice>
// [ / <border-image-width> |
// / <border-image-width>? / <border-image-outset> ]? ||
// <border-image-repeat>
nsCSSValue value;
if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
AppendValue(eCSSProperty_border_image_source, value);
AppendValue(eCSSProperty_border_image_slice, value);
AppendValue(eCSSProperty_border_image_width, value);
AppendValue(eCSSProperty_border_image_outset, value);
AppendValue(eCSSProperty_border_image_repeat, value);
// Keywords "inherit", "initial" and "unset" can't be mixed, so we are done.
return true;
}
// No empty property.
if (CheckEndProperty()) {
return false;
}
// Shorthand properties are required to set everything they can.
SetBorderImageInitialValues();
bool foundSource = false;
bool foundSliceWidthOutset = false;
bool foundRepeat = false;
// This loop is used to handle the parsing of border-image properties which
// can appear in any order.
nsCSSValue imageSourceValue;
while (!CheckEndProperty()) {
// <border-image-source>
if (!foundSource) {
CSSParseResult result =
ParseVariant(imageSourceValue, VARIANT_IMAGE, nullptr);
if (result == CSSParseResult::Error) {
return false;
} else if (result == CSSParseResult::Ok) {
AppendValue(eCSSProperty_border_image_source, imageSourceValue);
foundSource = true;
continue;
}
}
// <border-image-slice>
// ParseBorderImageSlice is weird. It may consume tokens and then return
// false, because it parses a property with two required components that
// can appear in either order. Since the tokens that were consumed cannot
// parse as anything else we care about, this isn't a problem.
if (!foundSliceWidthOutset) {
bool sliceConsumedTokens = false;
if (ParseBorderImageSlice(false, &sliceConsumedTokens)) {
foundSliceWidthOutset = true;
// [ / <border-image-width>?
if (ExpectSymbol('/', true)) {
bool foundBorderImageWidth = ParseBorderImageWidth(false);
// [ / <border-image-outset>
if (ExpectSymbol('/', true)) {
if (!ParseBorderImageOutset(false)) {
return false;
}
} else if (!foundBorderImageWidth) {
// If this part has an trailing slash, the whole declaration is
// invalid.
return false;
}
}
continue;
} else {
// If we consumed some tokens for <border-image-slice> but did not
// successfully parse it, we have an error.
if (sliceConsumedTokens) {
return false;
}
}
}
// <border-image-repeat>
if (!foundRepeat && ParseBorderImageRepeat(false)) {
foundRepeat = true;
continue;
}
return false;
}
return true;
}
bool
CSSParserImpl::ParseBorderSpacing()
{
nsCSSValue xValue, yValue;
if (ParseNonNegativeVariant(xValue, VARIANT_HL | VARIANT_CALC, nullptr) !=
CSSParseResult::Ok) {
return false;
}
// If we have one length, get the optional second length.
// set the second value equal to the first.
if (xValue.IsLengthUnit() || xValue.IsCalcUnit()) {
if (ParseNonNegativeVariant(yValue, VARIANT_LENGTH | VARIANT_CALC,
nullptr) == CSSParseResult::Error) {
return false;
}
}
if (yValue == xValue || yValue.GetUnit() == eCSSUnit_Null) {
AppendValue(eCSSProperty_border_spacing, xValue);
} else {
nsCSSValue pair;
pair.SetPairValue(xValue, yValue);
AppendValue(eCSSProperty_border_spacing, pair);
}
return true;
}
bool
CSSParserImpl::ParseBorderSide(const nsCSSPropertyID aPropIDs[],
bool aSetAllSides)
{
const int32_t numProps = 3;
nsCSSValue values[numProps];
int32_t found = ParseChoice(values, aPropIDs, numProps);
if (found < 1) {
return false;
}
if ((found & 1) == 0) { // Provide default border-width
values[0].SetIntValue(NS_STYLE_BORDER_WIDTH_MEDIUM, eCSSUnit_Enumerated);
}
if ((found & 2) == 0) { // Provide default border-style
values[1].SetIntValue(NS_STYLE_BORDER_STYLE_NONE, eCSSUnit_Enumerated);
}
if ((found & 4) == 0) { // text color will be used
values[2].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
}
if (aSetAllSides) {
// Parsing "border" shorthand; set all four sides to the same thing
for (int32_t index = 0; index < 4; index++) {
NS_ASSERTION(numProps == 3, "This code needs updating");
AppendValue(kBorderWidthIDs[index], values[0]);
AppendValue(kBorderStyleIDs[index], values[1]);
AppendValue(kBorderColorIDs[index], values[2]);
}
static const nsCSSPropertyID kBorderColorsProps[] = {
eCSSProperty__moz_border_top_colors,
eCSSProperty__moz_border_right_colors,
eCSSProperty__moz_border_bottom_colors,
eCSSProperty__moz_border_left_colors
};
// Set the other properties that the border shorthand sets to their
// initial values.
nsCSSValue extraValue;
switch (values[0].GetUnit()) {
case eCSSUnit_Inherit:
case eCSSUnit_Initial:
case eCSSUnit_Unset:
extraValue = values[0];
// Set value of border-image properties to initial/inherit/unset
AppendValue(eCSSProperty_border_image_source, extraValue);
AppendValue(eCSSProperty_border_image_slice, extraValue);
AppendValue(eCSSProperty_border_image_width, extraValue);
AppendValue(eCSSProperty_border_image_outset, extraValue);
AppendValue(eCSSProperty_border_image_repeat, extraValue);
break;
default:
extraValue.SetNoneValue();
SetBorderImageInitialValues();
break;
}
NS_FOR_CSS_SIDES(side) {
AppendValue(kBorderColorsProps[side], extraValue);
}
}
else {
// Just set our one side
for (int32_t index = 0; index < numProps; index++) {
AppendValue(aPropIDs[index], values[index]);
}
}
return true;
}
bool
CSSParserImpl::ParseBorderStyle()
{
return ParseBoxProperties(kBorderStyleIDs);
}
bool
CSSParserImpl::ParseBorderWidth()
{
return ParseBoxProperties(kBorderWidthIDs);
}
bool
CSSParserImpl::ParseBorderColors(nsCSSPropertyID aProperty)
{
nsCSSValue value;
// 'inherit', 'initial', 'unset' and 'none' are only allowed on their own
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
nullptr)) {
nsCSSValueList *cur = value.SetListValue();
for (;;) {
if (ParseVariant(cur->mValue, VARIANT_COLOR, nullptr) !=
CSSParseResult::Ok) {
return false;
}
if (CheckEndProperty()) {
break;
}
cur->mNext = new nsCSSValueList;
cur = cur->mNext;
}
}
AppendValue(aProperty, value);
return true;
}
// Parse the top level of a calc() expression.
bool
CSSParserImpl::ParseCalc(nsCSSValue &aValue, uint32_t aVariantMask)
{
// Parsing calc expressions requires, in a number of cases, looking
// for a token that is *either* a value of the property or a number.
// This can be done without lookahead when we assume that the property
// values cannot themselves be numbers.
MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask");
MOZ_ASSERT(!(aVariantMask & VARIANT_LPN) != !(aVariantMask & VARIANT_INTEGER),
"variant mask must intersect with exactly one of VARIANT_LPN "
"or VARIANT_INTEGER");
bool oldUnitlessLengthQuirk = mUnitlessLengthQuirk;
mUnitlessLengthQuirk = false;
// One-iteration loop so we can break to the error-handling case.
do {
// The toplevel of a calc() is always an nsCSSValue::Array of length 1.
RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(1);
if (!ParseCalcAdditiveExpression(arr->Item(0), aVariantMask))
break;
if (!ExpectSymbol(')', true))
break;
aValue.SetArrayValue(arr, eCSSUnit_Calc);
mUnitlessLengthQuirk = oldUnitlessLengthQuirk;
return true;
} while (false);
SkipUntil(')');
mUnitlessLengthQuirk = oldUnitlessLengthQuirk;
return false;
}
// We optimize away the <value-expression> production given that
// ParseVariant consumes initial whitespace and we call
// ExpectSymbol(')') with true for aSkipWS.
// * If aVariantMask is VARIANT_NUMBER, this function parses the
// <number-additive-expression> production.
// * If aVariantMask does not contain VARIANT_NUMBER, this function
// parses the <value-additive-expression> production.
// * Otherwise (VARIANT_NUMBER and other bits) this function parses
// whichever one of the productions matches ***and modifies
// aVariantMask*** to reflect which one it has parsed by either
// removing VARIANT_NUMBER or removing all other bits.
// It does so iteratively, but builds the correct recursive
// data structure.
bool
CSSParserImpl::ParseCalcAdditiveExpression(nsCSSValue& aValue,
uint32_t& aVariantMask)
{
MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask");
nsCSSValue *storage = &aValue;
for (;;) {
bool haveWS;
if (!ParseCalcMultiplicativeExpression(*storage, aVariantMask, &haveWS))
return false;
if (!haveWS || !GetToken(false))
return true;
nsCSSUnit unit;
if (mToken.IsSymbol('+')) {
unit = eCSSUnit_Calc_Plus;
} else if (mToken.IsSymbol('-')) {
unit = eCSSUnit_Calc_Minus;
} else {
UngetToken();
return true;
}
if (!RequireWhitespace())
return false;
RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(2);
arr->Item(0) = aValue;
storage = &arr->Item(1);
aValue.SetArrayValue(arr, unit);
}
}
// * If aVariantMask is VARIANT_NUMBER, this function parses the
// <number-multiplicative-expression> production.
// * If aVariantMask does not contain VARIANT_NUMBER, this function
// parses the <value-multiplicative-expression> production.
// * Otherwise (VARIANT_NUMBER and other bits) this function parses
// whichever one of the productions matches ***and modifies
// aVariantMask*** to reflect which one it has parsed by either
// removing VARIANT_NUMBER or removing all other bits.
// It does so iteratively, but builds the correct recursive data
// structure.
// This function always consumes *trailing* whitespace when it returns
// true; whether there was any such whitespace is returned in the
// aHadFinalWS parameter.
bool
CSSParserImpl::ParseCalcMultiplicativeExpression(nsCSSValue& aValue,
uint32_t& aVariantMask,
bool *aHadFinalWS)
{
MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask");
bool gotValue = false; // already got the part with the unit
bool afterDivision = false;
nsCSSValue *storage = &aValue;
for (;;) {
uint32_t variantMask;
if (aVariantMask & VARIANT_INTEGER) {
MOZ_ASSERT(aVariantMask == VARIANT_INTEGER,
"integers in calc expressions can't be mixed with anything "
"else.");
variantMask = aVariantMask;
} else if (afterDivision || gotValue) {
// At this point in the calc expression, we expect a coefficient or a
// divisor, which must be a number. (Not a length/%/etc.)
variantMask = VARIANT_NUMBER;
} else {
// At this point in the calc expression, we'll accept a coefficient
// (a number) or a value of whatever type |aVariantMask| specifies.
variantMask = aVariantMask | VARIANT_NUMBER;
}
if (!ParseCalcTerm(*storage, variantMask))
return false;
MOZ_ASSERT(variantMask != 0,
"ParseCalcTerm did not set variantMask appropriately");
MOZ_ASSERT(!(variantMask & VARIANT_NUMBER) ||
!(variantMask & ~int32_t(VARIANT_NUMBER)),
"ParseCalcTerm did not set variantMask appropriately");
if (variantMask & VARIANT_NUMBER) {
// Simplify the value immediately so we can check for division by
// zero.
float number;
mozilla::css::ReduceCalcOps<float, eCSSUnit_Number> ops;
if (!mozilla::css::ComputeCalc(number, *storage, ops)) {
MOZ_ASSERT_UNREACHABLE("unexpected unit");
}
if (number == 0.0 && afterDivision)
return false;
storage->SetFloatValue(number, eCSSUnit_Number);
} else {
gotValue = true;
if (storage != &aValue) {
// Simplify any numbers in the Times_L position (which are
// not simplified by the check above).
MOZ_ASSERT(storage == &aValue.GetArrayValue()->Item(1),
"unexpected relationship to current storage");
nsCSSValue &leftValue = aValue.GetArrayValue()->Item(0);
if (variantMask & VARIANT_INTEGER) {
int integer;
mozilla::css::ReduceCalcOps<int, eCSSUnit_Integer> ops;
if (!mozilla::css::ComputeCalc(integer, leftValue, ops)) {
MOZ_ASSERT_UNREACHABLE("unexpected unit");
}
leftValue.SetIntValue(integer, eCSSUnit_Integer);
} else {
float number;
mozilla::css::ReduceCalcOps<float, eCSSUnit_Number> ops;
if (!mozilla::css::ComputeCalc(number, leftValue, ops)) {
MOZ_ASSERT_UNREACHABLE("unexpected unit");
}
leftValue.SetFloatValue(number, eCSSUnit_Number);
}
}
}
bool hadWS = RequireWhitespace();
if (!GetToken(false)) {
*aHadFinalWS = hadWS;
break;
}
nsCSSUnit unit;
if (mToken.IsSymbol('*')) {
unit = gotValue ? eCSSUnit_Calc_Times_R : eCSSUnit_Calc_Times_L;
afterDivision = false;
} else if (mToken.IsSymbol('/')) {
if (variantMask & VARIANT_INTEGER) {
// Integers aren't mixed with anything else (see the assert at the top
// of CSSParserImpl::ParseCalc).
// We don't allow division at all in calc()s for expressions where an
// integer is expected, because calc() division can't be resolved to
// an integer, as implied by spec text about '/' here:
// https://drafts.csswg.org/css-values-3/#calc-type-checking
// We've consumed the '/' token, but it doesn't matter as we're in an
// error-handling situation where we've already consumed a lot of
// other tokens (e.g. the token before the '/'). ParseVariant will
// indicate this with CSSParseResult::Error.
return false;
}
unit = eCSSUnit_Calc_Divided;
afterDivision = true;
} else {
UngetToken();
*aHadFinalWS = hadWS;
break;
}
RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(2);
arr->Item(0) = aValue;
storage = &arr->Item(1);
aValue.SetArrayValue(arr, unit);
}
// Adjust aVariantMask (see comments above function) to reflect which
// option we took.
if (aVariantMask & VARIANT_NUMBER) {
if (gotValue) {
aVariantMask &= ~int32_t(VARIANT_NUMBER);
} else {
aVariantMask = VARIANT_NUMBER;
}
} else {
if (!gotValue) {
// We had to find a value, but we didn't.
return false;
}
}
return true;
}
// * If aVariantMask is VARIANT_NUMBER, this function parses the
// <number-term> production.
// * If aVariantMask does not contain VARIANT_NUMBER, this function
// parses the <value-term> production.
// * Otherwise (VARIANT_NUMBER and other bits) this function parses
// whichever one of the productions matches ***and modifies
// aVariantMask*** to reflect which one it has parsed by either
// removing VARIANT_NUMBER or removing all other bits.
bool
CSSParserImpl::ParseCalcTerm(nsCSSValue& aValue, uint32_t& aVariantMask)
{
MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask");
if (!GetToken(true))
return false;
// Either an additive expression in parentheses...
if (mToken.IsSymbol('(') ||
// Treat nested calc() as plain parenthesis.
IsCSSTokenCalcFunction(mToken)) {
if (!ParseCalcAdditiveExpression(aValue, aVariantMask) ||
!ExpectSymbol(')', true)) {
SkipUntil(')');
return false;
}
return true;
}
// ... or just a value
UngetToken();
if (aVariantMask & VARIANT_INTEGER) {
// Integers aren't mixed with anything else (see the assert at the
// top of CSSParserImpl::ParseCalc).
if (ParseVariant(aValue, aVariantMask, nullptr) != CSSParseResult::Ok) {
return false;
}
} else {
// Always pass VARIANT_NUMBER to ParseVariant so that unitless zero
// always gets picked up (we want to catch unitless zeroes using
// VARIANT_NUMBER and then error out)
if (ParseVariant(aValue, aVariantMask | VARIANT_NUMBER, nullptr) !=
CSSParseResult::Ok) {
return false;
}
// ...and do the VARIANT_NUMBER check ourselves.
if (!(aVariantMask & VARIANT_NUMBER) && aValue.GetUnit() == eCSSUnit_Number) {
return false;
}
}
// If we did the value parsing, we need to adjust aVariantMask to
// reflect which option we took (see above).
if (aVariantMask & VARIANT_NUMBER) {
if (aValue.GetUnit() == eCSSUnit_Number) {
aVariantMask = VARIANT_NUMBER;
} else {
aVariantMask &= ~int32_t(VARIANT_NUMBER);
}
}
return true;
}
// This function consumes all consecutive whitespace and returns whether
// there was any.
bool
CSSParserImpl::RequireWhitespace()
{
if (!GetToken(false))
return false;
if (mToken.mType != eCSSToken_Whitespace) {
UngetToken();
return false;
}
// Skip any additional whitespace tokens.
if (GetToken(true)) {
UngetToken();
}
return true;
}
bool
CSSParserImpl::ParseRect(nsCSSPropertyID aPropID)
{
nsCSSValue val;
if (ParseSingleTokenVariant(val, VARIANT_INHERIT | VARIANT_AUTO, nullptr)) {
AppendValue(aPropID, val);
return true;
}
if (! GetToken(true)) {
return false;
}
if (mToken.mType == eCSSToken_Function &&
mToken.mIdent.LowerCaseEqualsLiteral("rect")) {
nsCSSRect& rect = val.SetRectValue();
bool useCommas;
NS_FOR_CSS_SIDES(side) {
if (!ParseSingleTokenVariant(rect.*(nsCSSRect::sides[side]),
VARIANT_AL, nullptr)) {
return false;
}
if (side == 0) {
useCommas = ExpectSymbol(',', true);
} else if (useCommas && side < 3) {
// Skip optional commas between elements, but only if the first
// separator was a comma.
if (!ExpectSymbol(',', true)) {
return false;
}
}
}
if (!ExpectSymbol(')', true)) {
return false;
}
} else {
UngetToken();
return false;
}
AppendValue(aPropID, val);
return true;
}
bool
CSSParserImpl::ParseColumns()
{
// We use a similar "fake value" hack to ParseListStyle, because
// "auto" is acceptable for both column-count and column-width.
// If the fake "auto" value is found, and one of the real values isn't,
// that means the fake auto value is meant for the real value we didn't
// find.
static const nsCSSPropertyID columnIDs[] = {
eCSSPropertyExtra_x_auto_value,
eCSSProperty_column_count,
eCSSProperty_column_width
};
const int32_t numProps = MOZ_ARRAY_LENGTH(columnIDs);
nsCSSValue values[numProps];
int32_t found = ParseChoice(values, columnIDs, numProps);
if (found < 1) {
return false;
}
if ((found & (1|2|4)) == (1|2|4) &&
values[0].GetUnit() == eCSSUnit_Auto) {
// We filled all 3 values, which is invalid
return false;
}
if ((found & 2) == 0) {
// Provide auto column-count
values[1].SetAutoValue();
}
if ((found & 4) == 0) {
// Provide auto column-width
values[2].SetAutoValue();
}
// Start at index 1 to skip the fake auto value.
for (int32_t index = 1; index < numProps; index++) {
AppendValue(columnIDs[index], values[index]);
}
return true;
}
#define VARIANT_CONTENT (VARIANT_STRING | VARIANT_URL | VARIANT_COUNTER | VARIANT_ATTR | \
VARIANT_KEYWORD)
bool
CSSParserImpl::ParseContent()
{
// We need to divide the 'content' keywords into two classes for
// ParseVariant's sake, so we can't just use nsCSSProps::kContentKTable.
static const KTableEntry kContentListKWs[] = {
{ eCSSKeyword_open_quote, uint8_t(StyleContent::OpenQuote) },
{ eCSSKeyword_close_quote, uint8_t(StyleContent::CloseQuote) },
{ eCSSKeyword_no_open_quote, uint8_t(StyleContent::NoOpenQuote) },
{ eCSSKeyword_no_close_quote, uint8_t(StyleContent::NoCloseQuote) },
{ eCSSKeyword_UNKNOWN, -1 }
};
static const KTableEntry kContentSolitaryKWs[] = {
{ eCSSKeyword__moz_alt_content, uint8_t(StyleContent::AltContent) },
{ eCSSKeyword_UNKNOWN, -1 }
};
// Verify that these two lists add up to the size of
// nsCSSProps::kContentKTable.
MOZ_ASSERT(nsCSSProps::kContentKTable[
ArrayLength(kContentListKWs) +
ArrayLength(kContentSolitaryKWs) - 2].mKeyword ==
eCSSKeyword_UNKNOWN &&
nsCSSProps::kContentKTable[
ArrayLength(kContentListKWs) +
ArrayLength(kContentSolitaryKWs) - 2].mValue == -1,
"content keyword tables out of sync");
nsCSSValue value;
// 'inherit', 'initial', 'unset', 'normal', 'none', and 'alt-content' must
// be alone
if (!ParseSingleTokenVariant(value, VARIANT_HMK | VARIANT_NONE,
kContentSolitaryKWs)) {
nsCSSValueList* cur = value.SetListValue();
for (;;) {
if (ParseVariant(cur->mValue, VARIANT_CONTENT, kContentListKWs) !=
CSSParseResult::Ok) {
return false;
}
if (CheckEndProperty()) {
break;
}
cur->mNext = new nsCSSValueList;
cur = cur->mNext;
}
}
AppendValue(eCSSProperty_content, value);
return true;
}
bool
CSSParserImpl::ParseCounterData(nsCSSPropertyID aPropID)
{
static const nsCSSKeyword kCounterDataKTable[] = {
eCSSKeyword_none,
eCSSKeyword_UNKNOWN
};
nsCSSValue value;
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
nullptr)) {
if (!GetToken(true)) {
return false;
}
if (mToken.mType != eCSSToken_Ident) {
UngetToken();
return false;
}
nsCSSValuePairList *cur = value.SetPairListValue();
for (;;) {
if (!ParseCustomIdent(cur->mXValue, mToken.mIdent, kCounterDataKTable)) {
return false;
}
int32_t value = aPropID == eCSSProperty_counter_increment ? 1 : 0;
if (GetToken(true)) {
if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid) {
value = mToken.mInteger;
} else {
UngetToken();
}
}
cur->mYValue.SetIntValue(value, eCSSUnit_Integer);
if (!GetToken(true)) {
break;
}
if (mToken.mType != eCSSToken_Ident) {
UngetToken();
break;
}
cur->mNext = new nsCSSValuePairList;
cur = cur->mNext;
}
}
AppendValue(aPropID, value);
return true;
}
bool
CSSParserImpl::ParseCursor()
{
nsCSSValue value;
// 'inherit', 'initial' and 'unset' must be alone
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
nsCSSValueList* cur = value.SetListValue();
for (;;) {
if (!ParseSingleTokenVariant(cur->mValue, VARIANT_UK,
nsCSSProps::kCursorKTable)) {
return false;
}
if (cur->mValue.GetUnit() != eCSSUnit_URL) { // keyword must be last
break;
}
// We have a URL, so make a value array with three values.
RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(3);
val->Item(0) = cur->mValue;
// Parse optional x and y position of cursor hotspot (css3-ui).
if (ParseSingleTokenVariant(val->Item(1), VARIANT_NUMBER, nullptr)) {
// If we have one number, we must have two.
if (!ParseSingleTokenVariant(val->Item(2), VARIANT_NUMBER, nullptr)) {
return false;
}
}
cur->mValue.SetArrayValue(val, eCSSUnit_Array);
if (!ExpectSymbol(',', true)) { // url must not be last
return false;
}
cur->mNext = new nsCSSValueList;
cur = cur->mNext;
}
}
AppendValue(eCSSProperty_cursor, value);
return true;
}
bool
CSSParserImpl::ParseFont()
{
nsCSSValue family;
if (ParseSingleTokenVariant(family, VARIANT_HK, nsCSSProps::kFontKTable)) {
if (eCSSUnit_Inherit == family.GetUnit() ||
eCSSUnit_Initial == family.GetUnit() ||
eCSSUnit_Unset == family.GetUnit()) {
AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None));
AppendValue(eCSSProperty_font_family, family);
AppendValue(eCSSProperty_font_style, family);
AppendValue(eCSSProperty_font_weight, family);
AppendValue(eCSSProperty_font_size, family);
AppendValue(eCSSProperty_line_height, family);
AppendValue(eCSSProperty_font_stretch, family);
AppendValue(eCSSProperty_font_size_adjust, family);
AppendValue(eCSSProperty_font_feature_settings, family);
AppendValue(eCSSProperty_font_language_override, family);
AppendValue(eCSSProperty_font_kerning, family);
AppendValue(eCSSProperty_font_variant_alternates, family);
AppendValue(eCSSProperty_font_variant_caps, family);
AppendValue(eCSSProperty_font_variant_east_asian, family);
AppendValue(eCSSProperty_font_variant_ligatures, family);
AppendValue(eCSSProperty_font_variant_numeric, family);
AppendValue(eCSSProperty_font_variant_position, family);
}
else {
AppendValue(eCSSProperty__x_system_font, family);
nsCSSValue systemFont(eCSSUnit_System_Font);
AppendValue(eCSSProperty_font_family, systemFont);
AppendValue(eCSSProperty_font_style, systemFont);
AppendValue(eCSSProperty_font_weight, systemFont);
AppendValue(eCSSProperty_font_size, systemFont);
AppendValue(eCSSProperty_line_height, systemFont);
AppendValue(eCSSProperty_font_stretch, systemFont);
AppendValue(eCSSProperty_font_size_adjust, systemFont);
AppendValue(eCSSProperty_font_feature_settings, systemFont);
AppendValue(eCSSProperty_font_language_override, systemFont);
AppendValue(eCSSProperty_font_kerning, systemFont);
AppendValue(eCSSProperty_font_variant_alternates, systemFont);
AppendValue(eCSSProperty_font_variant_caps, systemFont);
AppendValue(eCSSProperty_font_variant_east_asian, systemFont);
AppendValue(eCSSProperty_font_variant_ligatures, systemFont);
AppendValue(eCSSProperty_font_variant_numeric, systemFont);
AppendValue(eCSSProperty_font_variant_position, systemFont);
}
return true;
}
// Get optional font-style, font-variant, font-weight, font-stretch
// (in any order)
// Indexes into fontIDs[] and values[] arrays.
const int kFontStyleIndex = 0;
const int kFontVariantIndex = 1;
const int kFontWeightIndex = 2;
const int kFontStretchIndex = 3;
// The order of the initializers here must match the order of the indexes
// defined above!
static const nsCSSPropertyID fontIDs[] = {
eCSSProperty_font_style,
eCSSProperty_font_variant_caps,
eCSSProperty_font_weight,
eCSSProperty_font_stretch
};
const int32_t numProps = MOZ_ARRAY_LENGTH(fontIDs);
nsCSSValue values[numProps];
int32_t found = ParseChoice(values, fontIDs, numProps);
if (found < 0 ||
eCSSUnit_Inherit == values[kFontStyleIndex].GetUnit() ||
eCSSUnit_Initial == values[kFontStyleIndex].GetUnit() ||
eCSSUnit_Unset == values[kFontStyleIndex].GetUnit()) { // illegal data
return false;
}
if ((found & (1 << kFontStyleIndex)) == 0) {
// Provide default font-style
values[kFontStyleIndex].SetIntValue(NS_FONT_STYLE_NORMAL,
eCSSUnit_Enumerated);
}
if ((found & (1 << kFontVariantIndex)) == 0) {
// Provide default font-variant
values[kFontVariantIndex].SetNormalValue();
} else {
if (values[kFontVariantIndex].GetUnit() == eCSSUnit_Enumerated &&
values[kFontVariantIndex].GetIntValue() !=
NS_FONT_VARIANT_CAPS_SMALLCAPS) {
// only normal or small-caps is allowed in font shorthand
// this also assumes other values for font-variant-caps never overlap
// possible values for style or weight
return false;
}
}
if ((found & (1 << kFontWeightIndex)) == 0) {
// Provide default font-weight
values[kFontWeightIndex].SetIntValue(NS_FONT_WEIGHT_NORMAL,
eCSSUnit_Enumerated);
}
if ((found & (1 << kFontStretchIndex)) == 0) {
// Provide default font-stretch
values[kFontStretchIndex].SetIntValue(NS_FONT_STRETCH_NORMAL,
eCSSUnit_Enumerated);
}
// Get mandatory font-size
nsCSSValue size;
if (!ParseSingleTokenNonNegativeVariant(size, VARIANT_KEYWORD | VARIANT_LP,
nsCSSProps::kFontSizeKTable)) {
return false;
}
// Get optional "/" line-height
nsCSSValue lineHeight;
if (ExpectSymbol('/', true)) {
if (ParseNonNegativeVariant(lineHeight,
VARIANT_NUMBER | VARIANT_LP |
VARIANT_NORMAL | VARIANT_CALC,
nullptr) != CSSParseResult::Ok) {
return false;
}
}
else {
lineHeight.SetNormalValue();
}
// Get final mandatory font-family
nsAutoParseCompoundProperty compound(this);
if (ParseFamily(family)) {
if (eCSSUnit_Inherit != family.GetUnit() &&
eCSSUnit_Initial != family.GetUnit() &&
eCSSUnit_Unset != family.GetUnit()) {
AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None));
AppendValue(eCSSProperty_font_family, family);
AppendValue(eCSSProperty_font_style, values[kFontStyleIndex]);
AppendValue(eCSSProperty_font_variant_caps, values[kFontVariantIndex]);
AppendValue(eCSSProperty_font_weight, values[kFontWeightIndex]);
AppendValue(eCSSProperty_font_size, size);
AppendValue(eCSSProperty_line_height, lineHeight);
AppendValue(eCSSProperty_font_stretch, values[kFontStretchIndex]);
AppendValue(eCSSProperty_font_size_adjust, nsCSSValue(eCSSUnit_None));
AppendValue(eCSSProperty_font_feature_settings, nsCSSValue(eCSSUnit_Normal));
AppendValue(eCSSProperty_font_language_override, nsCSSValue(eCSSUnit_Normal));
AppendValue(eCSSProperty_font_kerning,
nsCSSValue(NS_FONT_KERNING_AUTO, eCSSUnit_Enumerated));
AppendValue(eCSSProperty_font_variant_alternates,
nsCSSValue(eCSSUnit_Normal));
AppendValue(eCSSProperty_font_variant_east_asian,
nsCSSValue(eCSSUnit_Normal));
AppendValue(eCSSProperty_font_variant_ligatures,
nsCSSValue(eCSSUnit_Normal));
AppendValue(eCSSProperty_font_variant_numeric,
nsCSSValue(eCSSUnit_Normal));
AppendValue(eCSSProperty_font_variant_position,
nsCSSValue(eCSSUnit_Normal));
return true;
}
}
return false;
}
bool
CSSParserImpl::ParseFontSynthesis(nsCSSValue& aValue)
{
if (!ParseSingleTokenVariant(aValue, VARIANT_HK | VARIANT_NONE,
nsCSSProps::kFontSynthesisKTable)) {
return false;
}
// first value 'none' ==> done
if (eCSSUnit_None == aValue.GetUnit() ||
eCSSUnit_Initial == aValue.GetUnit() ||
eCSSUnit_Inherit == aValue.GetUnit() ||
eCSSUnit_Unset == aValue.GetUnit())
{
return true;
}
// look for a second value
int32_t intValue = aValue.GetIntValue();
nsCSSValue nextValue;
if (ParseEnum(nextValue, nsCSSProps::kFontSynthesisKTable)) {
int32_t nextIntValue = nextValue.GetIntValue();
if (nextIntValue & intValue) {
return false;
}
aValue.SetIntValue(nextIntValue | intValue, eCSSUnit_Enumerated);
}
return true;
}
// font-variant-alternates allows for a combination of multiple
// simple enumerated values and functional values. Functional values have
// parameter lists with one or more idents which are later resolved
// based on values defined in @font-feature-value rules.
//
// font-variant-alternates: swash(flowing) historical-forms styleset(alt-g, alt-m);
//
// So for this the nsCSSValue is set to a pair value, with one
// value for a bitmask of both simple and functional property values
// and another value containing a ValuePairList with lists of idents
// for each functional property value.
//
// pairValue
// o intValue
// NS_FONT_VARIANT_ALTERNATES_SWASH |
// NS_FONT_VARIANT_ALTERNATES_STYLESET
// o valuePairList, each element with
// - intValue - indicates which alternate
// - string or valueList of strings
//
// Note: when only 'historical-forms' is specified, there are no
// functional values to store, in which case the valuePairList is a
// single element dummy list. In all other cases, the length of the
// list will match the number of functional values.
#define MAX_ALLOWED_FEATURES 512
static uint16_t
MaxElementsForAlternateType(nsCSSKeyword keyword)
{
uint16_t maxElems = 1;
if (keyword == eCSSKeyword_styleset ||
keyword == eCSSKeyword_character_variant) {
maxElems = MAX_ALLOWED_FEATURES;
}
return maxElems;
}
bool
CSSParserImpl::ParseSingleAlternate(int32_t& aWhichFeature,
nsCSSValue& aValue)
{
if (!GetToken(true)) {
return false;
}
bool isIdent = (mToken.mType == eCSSToken_Ident);
if (mToken.mType != eCSSToken_Function && !isIdent) {
UngetToken();
return false;
}
// ident ==> simple enumerated prop val (e.g. historical-forms)
// function ==> e.g. swash(flowing) styleset(alt-g, alt-m)
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
if (!nsCSSProps::FindKeyword(keyword,
(isIdent ?
nsCSSProps::kFontVariantAlternatesKTable :
nsCSSProps::kFontVariantAlternatesFuncsKTable),
aWhichFeature))
{
// failed, pop token
UngetToken();
return false;
}
if (isIdent) {
aValue.SetIntValue(aWhichFeature, eCSSUnit_Enumerated);
return true;
}
return ParseFunction(keyword, nullptr, VARIANT_IDENTIFIER,
1, MaxElementsForAlternateType(keyword), aValue);
}
bool
CSSParserImpl::ParseFontVariantAlternates(nsCSSValue& aValue)
{
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
nullptr)) {
return true;
}
// iterate through parameters
nsCSSValue listValue;
int32_t feature, featureFlags = 0;
// if no functional values, this may be a list with a single, unused element
listValue.SetListValue();
nsCSSValueList* list = nullptr;
nsCSSValue value;
while (ParseSingleAlternate(feature, value)) {
// check to make sure value not already set
if (feature == 0 ||
feature & featureFlags) {
return false;
}
featureFlags |= feature;
// if function, need to add to the list of functions
if (value.GetUnit() == eCSSUnit_Function) {
if (!list) {
list = listValue.GetListValue();
} else {
list->mNext = new nsCSSValueList;
list = list->mNext;
}
list->mValue = value;
}
}
if (featureFlags == 0) {
// ParseSingleAlternate failed the first time through the loop.
return false;
}
nsCSSValue featureValue;
featureValue.SetIntValue(featureFlags, eCSSUnit_Enumerated);
aValue.SetPairValue(featureValue, listValue);
return true;
}
bool
CSSParserImpl::MergeBitmaskValue(int32_t aNewValue,
const int32_t aMasks[],
int32_t& aMergedValue)
{
// check to make sure value not already set
if (aNewValue & aMergedValue) {
return false;
}
const int32_t *m = aMasks;
int32_t c = 0;
while (*m != MASK_END_VALUE) {
if (*m & aNewValue) {
c = aMergedValue & *m;
break;
}
m++;
}
if (c) {
return false;
}
aMergedValue |= aNewValue;
return true;
}
// aMasks - array of masks for mutually-exclusive property values,
// e.g. proportial-nums, tabular-nums
bool
CSSParserImpl::ParseBitmaskValues(nsCSSValue& aValue,
const KTableEntry aKeywordTable[],
const int32_t aMasks[])
{
// Parse at least one keyword
if (!ParseEnum(aValue, aKeywordTable)) {
return false;
}
// look for more values
nsCSSValue nextValue;
int32_t mergedValue = aValue.GetIntValue();
while (ParseEnum(nextValue, aKeywordTable))
{
if (!MergeBitmaskValue(nextValue.GetIntValue(), aMasks, mergedValue)) {
return false;
}
}
aValue.SetIntValue(mergedValue, eCSSUnit_Enumerated);
return true;
}
static const int32_t maskEastAsian[] = {
NS_FONT_VARIANT_EAST_ASIAN_VARIANT_MASK,
NS_FONT_VARIANT_EAST_ASIAN_WIDTH_MASK,
MASK_END_VALUE
};
bool
CSSParserImpl::ParseFontVariantEastAsian(nsCSSValue& aValue)
{
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
nullptr)) {
return true;
}
NS_ASSERTION(maskEastAsian[ArrayLength(maskEastAsian) - 1] ==
MASK_END_VALUE,
"incorrectly terminated array");
return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantEastAsianKTable,
maskEastAsian);
}
bool
CSSParserImpl::ParseContain(nsCSSValue& aValue)
{
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NONE,
nullptr)) {
return true;
}
static const int32_t maskContain[] = { MASK_END_VALUE };
if (!ParseBitmaskValues(aValue, nsCSSProps::kContainKTable, maskContain)) {
return false;
}
if (aValue.GetIntValue() & NS_STYLE_CONTAIN_STRICT) {
if (aValue.GetIntValue() != NS_STYLE_CONTAIN_STRICT) {
// Disallow any other keywords in combination with 'strict'.
return false;
}
// Strict implies layout, style, and paint.
// However, for serialization purposes, we keep the strict bit around.
aValue.SetIntValue(NS_STYLE_CONTAIN_STRICT |
NS_STYLE_CONTAIN_ALL_BITS, eCSSUnit_Enumerated);
}
return true;
}
static const int32_t maskLigatures[] = {
NS_FONT_VARIANT_LIGATURES_COMMON_MASK,
NS_FONT_VARIANT_LIGATURES_DISCRETIONARY_MASK,
NS_FONT_VARIANT_LIGATURES_HISTORICAL_MASK,
NS_FONT_VARIANT_LIGATURES_CONTEXTUAL_MASK,
MASK_END_VALUE
};
bool
CSSParserImpl::ParseFontVariantLigatures(nsCSSValue& aValue)
{
if (ParseSingleTokenVariant(aValue,
VARIANT_INHERIT | VARIANT_NORMAL | VARIANT_NONE,
nullptr)) {
return true;
}
NS_ASSERTION(maskLigatures[ArrayLength(maskLigatures) - 1] ==
MASK_END_VALUE,
"incorrectly terminated array");
return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantLigaturesKTable,
maskLigatures);
}
static const int32_t maskNumeric[] = {
NS_FONT_VARIANT_NUMERIC_FIGURE_MASK,
NS_FONT_VARIANT_NUMERIC_SPACING_MASK,
NS_FONT_VARIANT_NUMERIC_FRACTION_MASK,
MASK_END_VALUE
};
bool
CSSParserImpl::ParseFontVariantNumeric(nsCSSValue& aValue)
{
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
nullptr)) {
return true;
}
NS_ASSERTION(maskNumeric[ArrayLength(maskNumeric) - 1] ==
MASK_END_VALUE,
"incorrectly terminated array");
return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantNumericKTable,
maskNumeric);
}
bool
CSSParserImpl::ParseFontVariant()
{
// parse single values - normal/inherit/none
nsCSSValue value;
nsCSSValue normal(eCSSUnit_Normal);
if (ParseSingleTokenVariant(value,
VARIANT_INHERIT | VARIANT_NORMAL | VARIANT_NONE,
nullptr)) {
AppendValue(eCSSProperty_font_variant_ligatures, value);
if (eCSSUnit_None == value.GetUnit()) {
// 'none' applies the value 'normal' to all properties other
// than 'font-variant-ligatures'
value.SetNormalValue();
}
AppendValue(eCSSProperty_font_variant_alternates, value);
AppendValue(eCSSProperty_font_variant_caps, value);
AppendValue(eCSSProperty_font_variant_east_asian, value);
AppendValue(eCSSProperty_font_variant_numeric, value);
AppendValue(eCSSProperty_font_variant_position, value);
return true;
}
// set each of the individual subproperties
int32_t altFeatures = 0, capsFeatures = 0, eastAsianFeatures = 0,
ligFeatures = 0, numericFeatures = 0, posFeatures = 0;
nsCSSValue altListValue;
nsCSSValueList* altList = nullptr;
// if no functional values, this may be a list with a single, unused element
altListValue.SetListValue();
bool foundValid = false; // found at least one proper value
while (GetToken(true)) {
// only an ident or a function at this point
bool isFunction = (mToken.mType == eCSSToken_Function);
if (mToken.mType != eCSSToken_Ident && !isFunction) {
UngetToken();
break;
}
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
if (keyword == eCSSKeyword_UNKNOWN) {
UngetToken();
return false;
}
int32_t feature;
// function? ==> font-variant-alternates
if (isFunction) {
if (!nsCSSProps::FindKeyword(keyword,
nsCSSProps::kFontVariantAlternatesFuncsKTable,
feature) ||
(feature & altFeatures)) {
UngetToken();
return false;
}
altFeatures |= feature;
nsCSSValue funcValue;
if (!ParseFunction(keyword, nullptr, VARIANT_IDENTIFIER, 1,
MaxElementsForAlternateType(keyword), funcValue) ||
funcValue.GetUnit() != eCSSUnit_Function) {
UngetToken();
return false;
}
if (!altList) {
altList = altListValue.GetListValue();
} else {
altList->mNext = new nsCSSValueList;
altList = altList->mNext;
}
altList->mValue = funcValue;
} else if (nsCSSProps::FindKeyword(keyword,
nsCSSProps::kFontVariantCapsKTable,
feature)) {
if (capsFeatures != 0) {
// multiple values for font-variant-caps
UngetToken();
return false;
}
capsFeatures = feature;
} else if (nsCSSProps::FindKeyword(keyword,
nsCSSProps::kFontVariantAlternatesKTable,
feature)) {
if (feature & altFeatures) {
// same value repeated
UngetToken();
return false;
}
altFeatures |= feature;
} else if (nsCSSProps::FindKeyword(keyword,
nsCSSProps::kFontVariantEastAsianKTable,
feature)) {
if (!MergeBitmaskValue(feature, maskEastAsian, eastAsianFeatures)) {
// multiple mutually exclusive values
UngetToken();
return false;
}
} else if (nsCSSProps::FindKeyword(keyword,
nsCSSProps::kFontVariantLigaturesKTable,
feature)) {
if (keyword == eCSSKeyword_none ||
!MergeBitmaskValue(feature, maskLigatures, ligFeatures)) {
// none or multiple mutually exclusive values
UngetToken();
return false;
}
} else if (nsCSSProps::FindKeyword(keyword,
nsCSSProps::kFontVariantNumericKTable,
feature)) {
if (!MergeBitmaskValue(feature, maskNumeric, numericFeatures)) {
// multiple mutually exclusive values
UngetToken();
return false;
}
} else if (nsCSSProps::FindKeyword(keyword,
nsCSSProps::kFontVariantPositionKTable,
feature)) {
if (posFeatures != 0) {
// multiple values for font-variant-caps
UngetToken();
return false;
}
posFeatures = feature;
} else {
// bogus keyword, bail...
UngetToken();
return false;
}
foundValid = true;
}
if (!foundValid) {
return false;
}
if (altFeatures) {
nsCSSValue featureValue;
featureValue.SetIntValue(altFeatures, eCSSUnit_Enumerated);
value.SetPairValue(featureValue, altListValue);
AppendValue(eCSSProperty_font_variant_alternates, value);
} else {
AppendValue(eCSSProperty_font_variant_alternates, normal);
}
if (capsFeatures) {
value.SetIntValue(capsFeatures, eCSSUnit_Enumerated);
AppendValue(eCSSProperty_font_variant_caps, value);
} else {
AppendValue(eCSSProperty_font_variant_caps, normal);
}
if (eastAsianFeatures) {
value.SetIntValue(eastAsianFeatures, eCSSUnit_Enumerated);
AppendValue(eCSSProperty_font_variant_east_asian, value);
} else {
AppendValue(eCSSProperty_font_variant_east_asian, normal);
}
if (ligFeatures) {
value.SetIntValue(ligFeatures, eCSSUnit_Enumerated);
AppendValue(eCSSProperty_font_variant_ligatures, value);
} else {
AppendValue(eCSSProperty_font_variant_ligatures, normal);
}
if (numericFeatures) {
value.SetIntValue(numericFeatures, eCSSUnit_Enumerated);
AppendValue(eCSSProperty_font_variant_numeric, value);
} else {
AppendValue(eCSSProperty_font_variant_numeric, normal);
}
if (posFeatures) {
value.SetIntValue(posFeatures, eCSSUnit_Enumerated);
AppendValue(eCSSProperty_font_variant_position, value);
} else {
AppendValue(eCSSProperty_font_variant_position, normal);
}
return true;
}
bool
CSSParserImpl::ParseFontWeight(nsCSSValue& aValue)
{
if (ParseSingleTokenVariant(aValue, VARIANT_HKI | VARIANT_SYSFONT,
nsCSSProps::kFontWeightKTable)) {
if (eCSSUnit_Integer == aValue.GetUnit()) { // ensure unit value
int32_t intValue = aValue.GetIntValue();
if ((100 <= intValue) &&
(intValue <= 900) &&
(0 == (intValue % 100))) {
return true;
} else {
UngetToken();
return false;
}
}
return true;
}
return false;
}
bool
CSSParserImpl::ParseOneFamily(nsAString& aFamily,
bool& aOneKeyword,
bool& aQuoted)
{
if (!GetToken(true))
return false;
nsCSSToken* tk = &mToken;
aOneKeyword = false;
aQuoted = false;
if (eCSSToken_Ident == tk->mType) {
aOneKeyword = true;
aFamily.Append(tk->mIdent);
for (;;) {
if (!GetToken(false))
break;
if (eCSSToken_Ident == tk->mType) {
aOneKeyword = false;
// We had at least another keyword before.
// "If a sequence of identifiers is given as a font family name,
// the computed value is the name converted to a string by joining
// all the identifiers in the sequence by single spaces."
// -- CSS 2.1, section 15.3
// Whitespace tokens do not actually matter,
// identifier tokens can be separated by comments.
aFamily.Append(char16_t(' '));
aFamily.Append(tk->mIdent);
} else if (eCSSToken_Whitespace != tk->mType) {
UngetToken();
break;
}
}
return true;
} else if (eCSSToken_String == tk->mType) {
aQuoted = true;
aFamily.Append(tk->mIdent); // XXX What if it had escaped quotes?
return true;
} else {
UngetToken();
return false;
}
}
static bool
AppendGeneric(nsCSSKeyword aKeyword, nsTArray<FontFamilyName>& aFamilyList)
{
switch (aKeyword) {
case eCSSKeyword_serif:
aFamilyList.AppendElement(FontFamilyName(eFamily_serif));
return true;
case eCSSKeyword_sans_serif:
aFamilyList.AppendElement(FontFamilyName(eFamily_sans_serif));
return true;
case eCSSKeyword_monospace:
aFamilyList.AppendElement(FontFamilyName(eFamily_monospace));
return true;
case eCSSKeyword_cursive:
aFamilyList.AppendElement(FontFamilyName(eFamily_cursive));
return true;
case eCSSKeyword_fantasy:
aFamilyList.AppendElement(FontFamilyName(eFamily_fantasy));
return true;
case eCSSKeyword__moz_fixed:
aFamilyList.AppendElement(FontFamilyName(eFamily_moz_fixed));
return true;
default:
break;
}
return false;
}
bool
CSSParserImpl::ParseFamily(nsCSSValue& aValue)
{
nsTArray<FontFamilyName> families;
nsAutoString family;
bool single, quoted;
// keywords only have meaning in the first position
if (!ParseOneFamily(family, single, quoted))
return false;
// check for keywords, but only when keywords appear by themselves
// i.e. not in compounds such as font-family: default blah;
bool foundGeneric = false;
if (single) {
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(family);
switch (keyword) {
case eCSSKeyword_inherit:
aValue.SetInheritValue();
return true;
case eCSSKeyword_default:
// 605231 - don't parse unquoted 'default' reserved keyword
return false;
case eCSSKeyword_initial:
aValue.SetInitialValue();
return true;
case eCSSKeyword_unset:
if (nsLayoutUtils::UnsetValueEnabled()) {
aValue.SetUnsetValue();
return true;
}
break;
case eCSSKeyword__moz_use_system_font:
if (!IsParsingCompoundProperty()) {
aValue.SetSystemFontValue();
return true;
}
break;
default:
foundGeneric = AppendGeneric(keyword, families);
}
}
if (!foundGeneric) {
families.AppendElement(
FontFamilyName(family, (quoted ? eQuotedName : eUnquotedName)));
}
for (;;) {
if (!ExpectSymbol(',', true))
break;
nsAutoString nextFamily;
if (!ParseOneFamily(nextFamily, single, quoted))
return false;
// at this point unquoted keywords are not allowed
// as font family names but can appear within names
foundGeneric = false;
if (single) {
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(nextFamily);
switch (keyword) {
case eCSSKeyword_inherit:
case eCSSKeyword_initial:
case eCSSKeyword_default:
case eCSSKeyword__moz_use_system_font:
return false;
case eCSSKeyword_unset:
if (nsLayoutUtils::UnsetValueEnabled()) {
return false;
}
break;
default:
foundGeneric = AppendGeneric(keyword, families);
break;
}
}
if (!foundGeneric) {
families.AppendElement(
FontFamilyName(nextFamily, (quoted ? eQuotedName : eUnquotedName)));
}
}
if (families.IsEmpty()) {
return false;
}
RefPtr<SharedFontList> familyList =
new SharedFontList(Move(families));
aValue.SetFontFamilyListValue(familyList.forget());
return true;
}
// src: ( uri-src | local-src ) (',' ( uri-src | local-src ) )*
// uri-src: uri [ 'format(' string ( ',' string )* ')' ]
// local-src: 'local(' ( string | ident ) ')'
bool
CSSParserImpl::ParseFontSrc(nsCSSValue& aValue)
{
// could we maybe turn nsCSSValue::Array into InfallibleTArray<nsCSSValue>?
InfallibleTArray<nsCSSValue> values;
nsCSSValue cur;
for (;;) {
if (!GetToken(true))
break;
if (mToken.mType == eCSSToken_URL) {
SetValueToURL(cur, mToken.mIdent);
values.AppendElement(cur);
if (!ParseFontSrcFormat(values))
return false;
} else if (mToken.mType == eCSSToken_Function &&
mToken.mIdent.LowerCaseEqualsLiteral("local")) {
// css3-fonts does not specify a formal grammar for local().
// The text permits both unquoted identifiers and quoted
// strings. We resolve this ambiguity in the spec by
// assuming that the appropriate production is a single
// <family-name>, possibly surrounded by whitespace.
nsAutoString family;
bool single, quoted;
if (!ParseOneFamily(family, single, quoted)) {
SkipUntil(')');
return false;
}
if (!ExpectSymbol(')', true)) {
SkipUntil(')');
return false;
}
// reject generics
if (single) {
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(family);
switch (keyword) {
case eCSSKeyword_serif:
case eCSSKeyword_sans_serif:
case eCSSKeyword_monospace:
case eCSSKeyword_cursive:
case eCSSKeyword_fantasy:
case eCSSKeyword__moz_fixed:
return false;
default:
break;
}
}
cur.SetStringValue(family, eCSSUnit_Local_Font);
values.AppendElement(cur);
} else {
// We don't know what to do with this token; unget it and error out
UngetToken();
return false;
}
if (!ExpectSymbol(',', true))
break;
}
if (values.Length() == 0)
return false;
RefPtr<nsCSSValue::Array> srcVals
= nsCSSValue::Array::Create(values.Length());
uint32_t i;
for (i = 0; i < values.Length(); i++)
srcVals->Item(i) = values[i];
aValue.SetArrayValue(srcVals, eCSSUnit_Array);
return true;
}
bool
CSSParserImpl::ParseFontSrcFormat(InfallibleTArray<nsCSSValue> & values)
{
if (!GetToken(true))
return true; // EOF harmless here
if (mToken.mType != eCSSToken_Function ||
!mToken.mIdent.LowerCaseEqualsLiteral("format")) {
UngetToken();
return true;
}
do {
if (!GetToken(true))
return false; // EOF - no need for SkipUntil
if (mToken.mType != eCSSToken_String) {
UngetToken();
SkipUntil(')');
return false;
}
nsCSSValue cur(mToken.mIdent, eCSSUnit_Font_Format);
values.AppendElement(cur);
} while (ExpectSymbol(',', true));
if (!ExpectSymbol(')', true)) {
SkipUntil(')');
return false;
}
return true;
}
// font-ranges: urange ( ',' urange )*
bool
CSSParserImpl::ParseFontRanges(nsCSSValue& aValue)
{
InfallibleTArray<uint32_t> ranges;
for (;;) {
if (!GetToken(true))
break;
if (mToken.mType != eCSSToken_URange) {
UngetToken();
break;
}
// An invalid range token is a parsing error, causing the entire
// descriptor to be ignored.
if (!mToken.mIntegerValid)
return false;
uint32_t low = mToken.mInteger;
uint32_t high = mToken.mInteger2;
// A range that descends, or high end exceeds the current range of
// Unicode (U+0-10FFFF) invalidates the descriptor.
if (low > high || high > 0x10FFFF) {
return false;
}
ranges.AppendElement(low);
ranges.AppendElement(high);
if (!ExpectSymbol(',', true))
break;
}
if (ranges.Length() == 0)
return false;
RefPtr<nsCSSValue::Array> srcVals
= nsCSSValue::Array::Create(ranges.Length());
for (uint32_t i = 0; i < ranges.Length(); i++)
srcVals->Item(i).SetIntValue(ranges[i], eCSSUnit_Integer);
aValue.SetArrayValue(srcVals, eCSSUnit_Array);
return true;
}
// font-feature-settings: normal | <feature-tag-value> [, <feature-tag-value>]*
// <feature-tag-value> = <string> [ <integer> | on | off ]?
// minimum - "tagx", "tagy", "tagz"
// edge error case - "tagx" on 1, "tagx" "tagy", "tagx" -1, "tagx" big
// pair value is always x = string, y = int
// font feature tags must be four ASCII characters
#define FEATURE_TAG_LENGTH 4
static bool
ValidFontFeatureTag(const nsString& aTag)
{
if (aTag.Length() != FEATURE_TAG_LENGTH) {
return false;
}
uint32_t i;
for (i = 0; i < FEATURE_TAG_LENGTH; i++) {
uint32_t ch = aTag[i];
if (ch < 0x20 || ch > 0x7e) {
return false;
}
}
return true;
}
bool
CSSParserImpl::ParseFontFeatureSettings(nsCSSValue& aValue)
{
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
nullptr)) {
return true;
}
auto resultHead = MakeUnique<nsCSSValuePairList>();
nsCSSValuePairList* cur = resultHead.get();
for (;;) {
// feature tag
if (!GetToken(true)) {
return false;
}
if (mToken.mType != eCSSToken_String ||
!ValidFontFeatureTag(mToken.mIdent)) {
UngetToken();
return false;
}
cur->mXValue.SetStringValue(mToken.mIdent, eCSSUnit_String);
if (!GetToken(true)) {
cur->mYValue.SetIntValue(1, eCSSUnit_Integer);
break;
}
// optional value or on/off keyword
if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid &&
mToken.mInteger >= 0) {
cur->mYValue.SetIntValue(mToken.mInteger, eCSSUnit_Integer);
} else if (mToken.mType == eCSSToken_Ident &&
mToken.mIdent.LowerCaseEqualsLiteral("on")) {
cur->mYValue.SetIntValue(1, eCSSUnit_Integer);
} else if (mToken.mType == eCSSToken_Ident &&
mToken.mIdent.LowerCaseEqualsLiteral("off")) {
cur->mYValue.SetIntValue(0, eCSSUnit_Integer);
} else {
// something other than value/on/off, set default value
cur->mYValue.SetIntValue(1, eCSSUnit_Integer);
UngetToken();
}
if (!ExpectSymbol(',', true)) {
break;
}
cur->mNext = new nsCSSValuePairList;
cur = cur->mNext;
}
aValue.AdoptPairListValue(Move(resultHead));
return true;
}
bool
CSSParserImpl::ParseFontVariationSettings(nsCSSValue& aValue)
{
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
nullptr)) {
return true;
}
auto resultHead = MakeUnique<nsCSSValuePairList>();
nsCSSValuePairList* cur = resultHead.get();
for (;;) {
// variation tag
if (!GetToken(true)) {
return false;
}
// variation tags are subject to the same validation as feature tags
if (mToken.mType != eCSSToken_String ||
!ValidFontFeatureTag(mToken.mIdent)) {
UngetToken();
return false;
}
cur->mXValue.SetStringValue(mToken.mIdent, eCSSUnit_String);
if (!GetToken(true)) {
return false;
}
if (mToken.mType == eCSSToken_Number) {
cur->mYValue.SetFloatValue(mToken.mNumber, eCSSUnit_Number);
} else {
UngetToken();
return false;
}
if (!ExpectSymbol(',', true)) {
break;
}
cur->mNext = new nsCSSValuePairList;
cur = cur->mNext;
}
aValue.AdoptPairListValue(Move(resultHead));
return true;
}
bool
CSSParserImpl::ParseListStyle()
{
// 'list-style' can accept 'none' for two different subproperties,
// 'list-style-type' and 'list-style-image'. In order to accept
// 'none' as the value of either but still allow another value for
// either, we need to ensure that the first 'none' we find gets
// allocated to a dummy property instead. Since parse function for
// 'list-style-type' could accept values for 'list-style-position',
// we put position in front of type.
static const nsCSSPropertyID listStyleIDs[] = {
eCSSPropertyExtra_x_none_value,
eCSSProperty_list_style_position,
eCSSProperty_list_style_type,
eCSSProperty_list_style_image
};
nsCSSValue values[MOZ_ARRAY_LENGTH(listStyleIDs)];
int32_t found =
ParseChoice(values, listStyleIDs, ArrayLength(listStyleIDs));
if (found < 1) {
return false;
}
if ((found & (1|4|8)) == (1|4|8)) {
if (values[0].GetUnit() == eCSSUnit_None) {
// We found a 'none' plus another value for both of
// 'list-style-type' and 'list-style-image'. This is a parse
// error, since the 'none' has to count for at least one of them.
return false;
} else {
NS_ASSERTION(found == (1|2|4|8) && values[0] == values[1] &&
values[0] == values[2] && values[0] == values[3],
"should be a special value");
}
}
if ((found & 2) == 0) {
values[1].SetIntValue(NS_STYLE_LIST_STYLE_POSITION_OUTSIDE,
eCSSUnit_Enumerated);
}
if ((found & 4) == 0) {
// Provide default values
nsAtom* type = (found & 1) ? nsGkAtoms::none : nsGkAtoms::disc;
values[2].SetAtomIdentValue(do_AddRef(type));
}
if ((found & 8) == 0) {
values[3].SetNoneValue();
}
// Start at 1 to avoid appending fake value.
for (uint32_t index = 1; index < ArrayLength(listStyleIDs); ++index) {
AppendValue(listStyleIDs[index], values[index]);
}
return true;
}
bool
CSSParserImpl::ParseListStyleType(nsCSSValue& aValue)
{
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_STRING,
nullptr)) {
return true;
}
if (ParseCounterStyleNameValue(aValue) || ParseSymbols(aValue)) {
return true;
}
return false;
}
bool
CSSParserImpl::ParseMargin()
{
static const nsCSSPropertyID kMarginSideIDs[] = {
eCSSProperty_margin_top,
eCSSProperty_margin_right,
eCSSProperty_margin_bottom,
eCSSProperty_margin_left
};
return ParseBoxProperties(kMarginSideIDs);
}
bool
CSSParserImpl::ParseObjectPosition()
{
nsCSSValue value;
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr) &&
!ParsePositionValue(value)) {
return false;
}
AppendValue(eCSSProperty_object_position, value);
return true;
}
bool
CSSParserImpl::ParseOutline()
{
const int32_t numProps = 3;
static const nsCSSPropertyID kOutlineIDs[] = {
eCSSProperty_outline_color,
eCSSProperty_outline_style,
eCSSProperty_outline_width
};
nsCSSValue values[numProps];
int32_t found = ParseChoice(values, kOutlineIDs, numProps);
if (found < 1) {
return false;
}
// Provide default values
if ((found & 1) == 0) { // Provide default outline-color
values[0].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
}
if ((found & 2) == 0) { // Provide default outline-style
values[1].SetIntValue(NS_STYLE_BORDER_STYLE_NONE, eCSSUnit_Enumerated);
}
if ((found & 4) == 0) { // Provide default outline-width
values[2].SetIntValue(NS_STYLE_BORDER_WIDTH_MEDIUM, eCSSUnit_Enumerated);
}
int32_t index;
for (index = 0; index < numProps; index++) {
AppendValue(kOutlineIDs[index], values[index]);
}
return true;
}
bool
CSSParserImpl::ParseOverflow()
{
nsCSSValue overflow;
if (!ParseSingleTokenVariant(overflow, VARIANT_HK,
nsCSSProps::kOverflowKTable)) {
return false;
}
nsCSSValue overflowX(overflow);
nsCSSValue overflowY(overflow);
if (eCSSUnit_Enumerated == overflow.GetUnit())
switch(overflow.GetIntValue()) {
case NS_STYLE_OVERFLOW_SCROLLBARS_HORIZONTAL:
overflowX.SetIntValue(NS_STYLE_OVERFLOW_SCROLL, eCSSUnit_Enumerated);
overflowY.SetIntValue(NS_STYLE_OVERFLOW_HIDDEN, eCSSUnit_Enumerated);
break;
case NS_STYLE_OVERFLOW_SCROLLBARS_VERTICAL:
overflowX.SetIntValue(NS_STYLE_OVERFLOW_HIDDEN, eCSSUnit_Enumerated);
overflowY.SetIntValue(NS_STYLE_OVERFLOW_SCROLL, eCSSUnit_Enumerated);
break;
}
AppendValue(eCSSProperty_overflow_x, overflowX);
AppendValue(eCSSProperty_overflow_y, overflowY);
return true;
}
bool
CSSParserImpl::ParsePadding()
{
static const nsCSSPropertyID kPaddingSideIDs[] = {
eCSSProperty_padding_top,
eCSSProperty_padding_right,
eCSSProperty_padding_bottom,
eCSSProperty_padding_left
};
return ParseBoxProperties(kPaddingSideIDs);
}
bool
CSSParserImpl::ParseQuotes()
{
nsCSSValue value;
if (!ParseSingleTokenVariant(value, VARIANT_HOS, nullptr)) {
return false;
}
if (value.GetUnit() == eCSSUnit_String) {
nsCSSValue open = value;
nsCSSValuePairList* quotes = value.SetPairListValue();
for (;;) {
quotes->mXValue = open;
// get mandatory close
if (!ParseSingleTokenVariant(quotes->mYValue, VARIANT_STRING, nullptr)) {
return false;
}
// look for another open
if (!ParseSingleTokenVariant(open, VARIANT_STRING, nullptr)) {
break;
}
quotes->mNext = new nsCSSValuePairList;
quotes = quotes->mNext;
}
}
AppendValue(eCSSProperty_quotes, value);
return true;
}
bool
CSSParserImpl::ParseTextDecoration()
{
static const nsCSSPropertyID kTextDecorationIDs[] = {
eCSSProperty_text_decoration_line,
eCSSProperty_text_decoration_style,
eCSSProperty_text_decoration_color
};
const int32_t numProps = MOZ_ARRAY_LENGTH(kTextDecorationIDs);
nsCSSValue values[numProps];
int32_t found = ParseChoice(values, kTextDecorationIDs, numProps);
if (found < 1) {
return false;
}
// Provide default values
if ((found & 1) == 0) { // Provide default text-decoration-line
values[0].SetIntValue(NS_STYLE_TEXT_DECORATION_LINE_NONE,
eCSSUnit_Enumerated);
}
if ((found & 2) == 0) { // Provide default text-decoration-style
values[1].SetIntValue(NS_STYLE_TEXT_DECORATION_STYLE_SOLID,
eCSSUnit_Enumerated);
}
if ((found & 4) == 0) { // Provide default text-decoration-color
values[2].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
}
for (int32_t index = 0; index < numProps; index++) {
AppendValue(kTextDecorationIDs[index], values[index]);
}
return true;
}
bool
CSSParserImpl::ParseTextEmphasis()
{
static constexpr nsCSSPropertyID kTextEmphasisIDs[] = {
eCSSProperty_text_emphasis_style,
eCSSProperty_text_emphasis_color
};
constexpr int32_t numProps = MOZ_ARRAY_LENGTH(kTextEmphasisIDs);
nsCSSValue values[numProps];
int32_t found = ParseChoice(values, kTextEmphasisIDs, numProps);
if (found < 1) {
return false;
}
if (!(found & 1)) { // Provide default text-emphasis-style
values[0].SetNoneValue();
}
if (!(found & 2)) { // Provide default text-emphasis-color
values[1].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
}
for (int32_t index = 0; index < numProps; index++) {
AppendValue(kTextEmphasisIDs[index], values[index]);
}
return true;
}
bool
CSSParserImpl::ParseTextEmphasisPosition(nsCSSValue& aValue)
{
static_assert((NS_STYLE_TEXT_EMPHASIS_POSITION_OVER ^
NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER ^
NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT ^
NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT) ==
(NS_STYLE_TEXT_EMPHASIS_POSITION_OVER |
NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER |
NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT |
NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT),
"text-emphasis-position constants should be bitmasks");
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
return true;
}
nsCSSValue first, second;
const auto& kTable = nsCSSProps::kTextEmphasisPositionKTable;
if (!ParseSingleTokenVariant(first, VARIANT_KEYWORD, kTable) ||
!ParseSingleTokenVariant(second, VARIANT_KEYWORD, kTable)) {
return false;
}
auto firstValue = first.GetIntValue();
auto secondValue = second.GetIntValue();
if ((firstValue == NS_STYLE_TEXT_EMPHASIS_POSITION_OVER ||
firstValue == NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER) ==
(secondValue == NS_STYLE_TEXT_EMPHASIS_POSITION_OVER ||
secondValue == NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER)) {
return false;
}
aValue.SetIntValue(firstValue | secondValue, eCSSUnit_Enumerated);
return true;
}
bool
CSSParserImpl::ParseTextEmphasisStyle(nsCSSValue& aValue)
{
static_assert((NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK ^
NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK) ==
(NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK |
NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK),
"text-emphasis-style shape and fill constants "
"should not intersect");
static_assert(NS_STYLE_TEXT_EMPHASIS_STYLE_FILLED == 0,
"Making 'filled' zero ensures that if neither 'filled' nor "
"'open' is specified, we compute it to 'filled' per spec");
if (ParseSingleTokenVariant(aValue, VARIANT_HOS, nullptr)) {
return true;
}
nsCSSValue first, second;
const auto& fillKTable = nsCSSProps::kTextEmphasisStyleFillKTable;
const auto& shapeKTable = nsCSSProps::kTextEmphasisStyleShapeKTable;
// Parse a fill value and/or a shape value, in either order.
// (Require at least one of them, and treat the second as optional.)
if (ParseSingleTokenVariant(first, VARIANT_KEYWORD, fillKTable)) {
Unused << ParseSingleTokenVariant(second, VARIANT_KEYWORD, shapeKTable);
} else if (ParseSingleTokenVariant(first, VARIANT_KEYWORD, shapeKTable)) {
Unused << ParseSingleTokenVariant(second, VARIANT_KEYWORD, fillKTable);
} else {
return false;
}
auto value = first.GetIntValue();
if (second.GetUnit() == eCSSUnit_Enumerated) {
value |= second.GetIntValue();
}
aValue.SetIntValue(value, eCSSUnit_Enumerated);
return true;
}
bool
CSSParserImpl::ParseTextAlign(nsCSSValue& aValue, const KTableEntry aTable[])
{
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
// 'inherit', 'initial' and 'unset' must be alone
return true;
}
nsCSSValue left;
if (!ParseSingleTokenVariant(left, VARIANT_KEYWORD, aTable)) {
return false;
}
if (!nsLayoutUtils::IsTextAlignUnsafeValueEnabled()) {
aValue = left;
return true;
}
nsCSSValue right;
if (ParseSingleTokenVariant(right, VARIANT_KEYWORD, aTable)) {
// 'true' must be combined with some other value than 'true'.
if (left.GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE &&
right.GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) {
return false;
}
aValue.SetPairValue(left, right);
} else {
// Single value 'true' is not allowed.
if (left.GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) {
return false;
}
aValue = left;
}
return true;
}
bool
CSSParserImpl::ParseTextAlign(nsCSSValue& aValue)
{
return ParseTextAlign(aValue, nsCSSProps::kTextAlignKTable);
}
bool
CSSParserImpl::ParseTextAlignLast(nsCSSValue& aValue)
{
return ParseTextAlign(aValue, nsCSSProps::kTextAlignLastKTable);
}
bool
CSSParserImpl::ParseTextDecorationLine(nsCSSValue& aValue)
{
static_assert((NS_STYLE_TEXT_DECORATION_LINE_NONE ^
NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE ^
NS_STYLE_TEXT_DECORATION_LINE_OVERLINE ^
NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH ^
NS_STYLE_TEXT_DECORATION_LINE_BLINK) ==
(NS_STYLE_TEXT_DECORATION_LINE_NONE |
NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE |
NS_STYLE_TEXT_DECORATION_LINE_OVERLINE |
NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH |
NS_STYLE_TEXT_DECORATION_LINE_BLINK),
"text decoration constants need to be bitmasks");
if (ParseSingleTokenVariant(aValue, VARIANT_HK,
nsCSSProps::kTextDecorationLineKTable)) {
if (eCSSUnit_Enumerated == aValue.GetUnit()) {
int32_t intValue = aValue.GetIntValue();
if (intValue != NS_STYLE_TEXT_DECORATION_LINE_NONE) {
// look for more keywords
nsCSSValue keyword;
int32_t index;
for (index = 0; index < 3; index++) {
if (ParseEnum(keyword, nsCSSProps::kTextDecorationLineKTable)) {
int32_t newValue = keyword.GetIntValue();
if (newValue == NS_STYLE_TEXT_DECORATION_LINE_NONE ||
newValue & intValue) {
// 'none' keyword in conjuction with others is not allowed, and
// duplicate keyword is not allowed.
return false;
}
intValue |= newValue;
}
else {
break;
}
}
aValue.SetIntValue(intValue, eCSSUnit_Enumerated);
}
}
return true;
}
return false;
}
bool
CSSParserImpl::ParseTextOverflow(nsCSSValue& aValue)
{
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
// 'inherit', 'initial' and 'unset' must be alone
return true;
}
nsCSSValue left;
if (!ParseSingleTokenVariant(left, VARIANT_KEYWORD | VARIANT_STRING,
nsCSSProps::kTextOverflowKTable))
return false;
nsCSSValue right;
if (ParseSingleTokenVariant(right, VARIANT_KEYWORD | VARIANT_STRING,
nsCSSProps::kTextOverflowKTable))
aValue.SetPairValue(left, right);
else {
aValue = left;
}
return true;
}
bool
CSSParserImpl::ParseTouchAction(nsCSSValue& aValue)
{
// Avaliable values of property touch-action:
// auto | none | [pan-x || pan-y] | manipulation
if (!ParseSingleTokenVariant(aValue, VARIANT_HK,
nsCSSProps::kTouchActionKTable)) {
return false;
}
// Auto and None keywords aren't allowed in conjunction with others.
// Also inherit, initial and unset values are available.
if (eCSSUnit_Enumerated != aValue.GetUnit()) {
return true;
}
int32_t intValue = aValue.GetIntValue();
nsCSSValue nextValue;
if (ParseEnum(nextValue, nsCSSProps::kTouchActionKTable)) {
int32_t nextIntValue = nextValue.GetIntValue();
// duplicates aren't allowed.
if (nextIntValue & intValue) {
return false;
}
// Auto and None and Manipulation is not allowed in conjunction with others.
if ((intValue | nextIntValue) & (NS_STYLE_TOUCH_ACTION_NONE |
NS_STYLE_TOUCH_ACTION_AUTO |
NS_STYLE_TOUCH_ACTION_MANIPULATION)) {
return false;
}
aValue.SetIntValue(nextIntValue | intValue, eCSSUnit_Enumerated);
}
return true;
}
bool
CSSParserImpl::ParseTextCombineUpright(nsCSSValue& aValue)
{
if (!ParseSingleTokenVariant(aValue, VARIANT_HK,
nsCSSProps::kTextCombineUprightKTable)) {
return false;
}
// if 'digits', need to check for an explicit number [2, 3, 4]
if (eCSSUnit_Enumerated == aValue.GetUnit() &&
aValue.GetIntValue() == NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_2) {
if (!nsLayoutUtils::TextCombineUprightDigitsEnabled()) {
return false;
}
if (!GetToken(true)) {
return true;
}
if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid) {
switch (mToken.mInteger) {
case 2: // already set, nothing to do
break;
case 3:
aValue.SetIntValue(NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_3,
eCSSUnit_Enumerated);
break;
case 4:
aValue.SetIntValue(NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_4,
eCSSUnit_Enumerated);
break;
default:
// invalid digits value
return false;
}
} else {
UngetToken();
}
}
return true;
}
///////////////////////////////////////////////////////
// transform Parsing Implementation
/* Reads a function list of arguments and consumes the closing parenthesis.
* Do not call this function directly; it's meant to be called from
* ParseFunction.
*/
bool
CSSParserImpl::ParseFunctionInternals(const uint32_t aVariantMask[],
uint32_t aVariantMaskAll,
uint16_t aMinElems,
uint16_t aMaxElems,
InfallibleTArray<nsCSSValue> &aOutput)
{
NS_ASSERTION((aVariantMask && !aVariantMaskAll) ||
(!aVariantMask && aVariantMaskAll),
"only one of the two variant mask parameters can be set");
for (uint16_t index = 0; index < aMaxElems; ++index) {
nsCSSValue newValue;
uint32_t m = aVariantMaskAll ? aVariantMaskAll : aVariantMask[index];
if (ParseVariant(newValue, m, nullptr) != CSSParseResult::Ok) {
break;
}
if (nsCSSValue::IsFloatUnit(newValue.GetUnit())) {
// Clamp infinity or -infinity values to max float or -max float to avoid
// calculations with infinity.
newValue.SetFloatValue(
mozilla::clamped(newValue.GetFloatValue(),
-std::numeric_limits<float>::max(),
std::numeric_limits<float>::max()),
newValue.GetUnit());
}
aOutput.AppendElement(newValue);
if (ExpectSymbol(',', true)) {
// Move on to the next argument if we see a comma.
continue;
}
if (ExpectSymbol(')', true)) {
// Make sure we've read enough symbols if we see a closing parenthesis.
return (index + 1) >= aMinElems;
}
// Only a comma or a closing parenthesis is valid after an argument.
break;
}
// If we're here, we've hit an error without seeing a closing parenthesis or
// we've read too many elements without seeing a closing parenthesis.
SkipUntil(')');
return false;
}
/* Parses a function [ input of the form (a [, b]*) ] and stores it
* as an nsCSSValue that holds a function of the form
* function-name arg1 arg2 ... argN
*
* On error, the return value is false.
*
* @param aFunction The name of the function that we're reading.
* @param aAllowedTypes An array of values corresponding to the legal
* types for each element in the function. The zeroth element in the
* array corresponds to the first function parameter, etc. The length
* of this array _must_ be greater than or equal to aMaxElems or the
* behavior is undefined. If not null, aAllowTypesAll must be 0.
* @param aAllowedTypesAll If set, every element tested for these types
* @param aMinElems Minimum number of elements to read. Reading fewer than
* this many elements will result in the function failing.
* @param aMaxElems Maximum number of elements to read. Reading more than
* this many elements will result in the function failing.
* @param aValue (out) The value that was parsed.
*/
bool
CSSParserImpl::ParseFunction(nsCSSKeyword aFunction,
const uint32_t aAllowedTypes[],
uint32_t aAllowedTypesAll,
uint16_t aMinElems, uint16_t aMaxElems,
nsCSSValue &aValue)
{
NS_ASSERTION((aAllowedTypes && !aAllowedTypesAll) ||
(!aAllowedTypes && aAllowedTypesAll),
"only one of the two allowed type parameter can be set");
typedef InfallibleTArray<nsCSSValue>::size_type arrlen_t;
/* 2^16 - 2, so that if we have 2^16 - 2 transforms, we have 2^16 - 1
* elements stored in the the nsCSSValue::Array.
*/
static const arrlen_t MAX_ALLOWED_ELEMS = 0xFFFE;
/* Read in a list of values as an array, failing if we can't or if
* it's out of bounds.
*
* We reserve 16 entries in the foundValues array in order to avoid
* having to resize the array dynamically when parsing some well-formed
* functions. The number 16 is coming from the number of arguments that
* matrix3d() accepts.
*/
AutoTArray<nsCSSValue, 16> foundValues;
if (!ParseFunctionInternals(aAllowedTypes, aAllowedTypesAll, aMinElems,
aMaxElems, foundValues)) {
return false;
}
/*
* In case the user has given us more than 2^16 - 2 arguments,
* we'll truncate them at 2^16 - 2 arguments.
*/
uint16_t numArgs = std::min(foundValues.Length(), MAX_ALLOWED_ELEMS);
RefPtr<nsCSSValue::Array> convertedArray =
aValue.InitFunction(aFunction, numArgs);
/* Copy things over. */
for (uint16_t index = 0; index < numArgs; ++index)
convertedArray->Item(index + 1) = foundValues[static_cast<arrlen_t>(index)];
/* Return it! */
return true;
}
/**
* Given a token, determines the minimum and maximum number of function
* parameters to read, along with the mask that should be used to read
* those function parameters. If the token isn't a transform function,
* returns an error.
*
* @param aToken The token identifying the function.
* @param aIsPrefixed If true, parse matrices using the matrix syntax
* for -moz-transform.
* @param aDisallowRelativeValues If true, only allow variants that are
* numbers or have non-relative dimensions.
* @param aMinElems [out] The minimum number of elements to read.
* @param aMaxElems [out] The maximum number of elements to read
* @param aVariantMask [out] The variant mask to use during parsing
* @return Whether the information was loaded successfully.
*/
static bool GetFunctionParseInformation(nsCSSKeyword aToken,
bool aIsPrefixed,
bool aDisallowRelativeValues,
uint16_t &aMinElems,
uint16_t &aMaxElems,
const uint32_t *& aVariantMask)
{
/* These types represent the common variant masks that will be used to
* parse out the individual functions. The order in the enumeration
* must match the order in which the masks are declared.
*/
enum { eLengthPercentCalc,
eLengthCalc,
eAbsoluteLengthCalc,
eTwoLengthPercentCalcs,
eTwoAbsoluteLengthCalcs,
eTwoLengthPercentCalcsOneLengthCalc,
eThreeAbsoluteLengthCalc,
eAngle,
eTwoAngles,
eNumber,
eNonNegativeLength,
eNonNegativeAbsoluteLength,
eTwoNumbers,
eThreeNumbers,
eThreeNumbersOneAngle,
eMatrix,
eMatrixPrefixed,
eMatrix3d,
eMatrix3dPrefixed,
eNumVariantMasks };
static const int32_t kMaxElemsPerFunction = 16;
static const uint32_t kVariantMasks[eNumVariantMasks][kMaxElemsPerFunction] = {
{VARIANT_LPCALC},
{VARIANT_LCALC},
{VARIANT_LB},
{VARIANT_LPCALC, VARIANT_LPCALC},
{VARIANT_LBCALC, VARIANT_LBCALC},
{VARIANT_LPCALC, VARIANT_LPCALC, VARIANT_LCALC},
{VARIANT_LBCALC, VARIANT_LBCALC, VARIANT_LBCALC},
{VARIANT_ANGLE_OR_ZERO},
{VARIANT_ANGLE_OR_ZERO, VARIANT_ANGLE_OR_ZERO},
{VARIANT_NUMBER},
{VARIANT_LENGTH|VARIANT_NONNEGATIVE_DIMENSION},
{VARIANT_LB|VARIANT_NONNEGATIVE_DIMENSION},
{VARIANT_NUMBER, VARIANT_NUMBER},
{VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER},
{VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_ANGLE_OR_ZERO},
{VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
VARIANT_NUMBER, VARIANT_NUMBER},
{VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
VARIANT_LPNCALC, VARIANT_LPNCALC},
{VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER},
{VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
VARIANT_LPNCALC, VARIANT_LPNCALC, VARIANT_LNCALC, VARIANT_NUMBER}};
// Map from a mask to a congruent mask that excludes relative variants.
static const int32_t kNonRelativeVariantMap[eNumVariantMasks] = {
eAbsoluteLengthCalc,
eAbsoluteLengthCalc,
eAbsoluteLengthCalc,
eTwoAbsoluteLengthCalcs,
eTwoAbsoluteLengthCalcs,
eThreeAbsoluteLengthCalc,
eThreeAbsoluteLengthCalc,
eAngle,
eTwoAngles,
eNumber,
eNonNegativeAbsoluteLength,
eNonNegativeAbsoluteLength,
eTwoNumbers,
eThreeNumbers,
eThreeNumbersOneAngle,
eMatrix,
eMatrix,
eMatrix3d,
eMatrix3d };
#ifdef DEBUG
static const uint8_t kVariantMaskLengths[eNumVariantMasks] =
{1, 1, 1, 2, 2, 3, 3, 1, 2, 1, 1, 1, 2, 3, 4, 6, 6, 16, 16};
#endif
int32_t variantIndex = eNumVariantMasks;
switch (aToken) {
case eCSSKeyword_translatex:
case eCSSKeyword_translatey:
/* Exactly one length or percent. */
variantIndex = eLengthPercentCalc;
aMinElems = 1U;
aMaxElems = 1U;
break;
case eCSSKeyword_translatez:
/* Exactly one length */
variantIndex = eLengthCalc;
aMinElems = 1U;
aMaxElems = 1U;
break;
case eCSSKeyword_translate3d:
/* Exactly two lengthds or percents and a number */
variantIndex = eTwoLengthPercentCalcsOneLengthCalc;
aMinElems = 3U;
aMaxElems = 3U;
break;
case eCSSKeyword_scalez:
case eCSSKeyword_scalex:
case eCSSKeyword_scaley:
/* Exactly one scale factor. */
variantIndex = eNumber;
aMinElems = 1U;
aMaxElems = 1U;
break;
case eCSSKeyword_scale3d:
/* Exactly three scale factors. */
variantIndex = eThreeNumbers;
aMinElems = 3U;
aMaxElems = 3U;
break;
case eCSSKeyword_rotatex:
case eCSSKeyword_rotatey:
case eCSSKeyword_rotate:
case eCSSKeyword_rotatez:
/* Exactly one angle. */
variantIndex = eAngle;
aMinElems = 1U;
aMaxElems = 1U;
break;
case eCSSKeyword_rotate3d:
variantIndex = eThreeNumbersOneAngle;
aMinElems = 4U;
aMaxElems = 4U;
break;
case eCSSKeyword_translate:
/* One or two lengths or percents. */
variantIndex = eTwoLengthPercentCalcs;
aMinElems = 1U;
aMaxElems = 2U;
break;
case eCSSKeyword_skew:
/* Exactly one or two angles. */
variantIndex = eTwoAngles;
aMinElems = 1U;
aMaxElems = 2U;
break;
case eCSSKeyword_scale:
/* One or two scale factors. */
variantIndex = eTwoNumbers;
aMinElems = 1U;
aMaxElems = 2U;
break;
case eCSSKeyword_skewx:
/* Exactly one angle. */
variantIndex = eAngle;
aMinElems = 1U;
aMaxElems = 1U;
break;
case eCSSKeyword_skewy:
/* Exactly one angle. */
variantIndex = eAngle;
aMinElems = 1U;
aMaxElems = 1U;
break;
case eCSSKeyword_matrix:
/* Six values, all numbers. */
variantIndex = aIsPrefixed ? eMatrixPrefixed : eMatrix;
aMinElems = 6U;
aMaxElems = 6U;
break;
case eCSSKeyword_matrix3d:
/* 16 matrix values, all numbers */
variantIndex = aIsPrefixed ? eMatrix3dPrefixed : eMatrix3d;
aMinElems = 16U;
aMaxElems = 16U;
break;
case eCSSKeyword_perspective:
/* Exactly one scale number. */
variantIndex = eNonNegativeLength;
aMinElems = 1U;
aMaxElems = 1U;
break;
default:
/* Oh dear, we didn't match. Report an error. */
return false;
}
if (aDisallowRelativeValues) {
variantIndex = kNonRelativeVariantMap[variantIndex];
}
NS_ASSERTION(aMinElems > 0, "Didn't update minimum elements!");
NS_ASSERTION(aMaxElems > 0, "Didn't update maximum elements!");
NS_ASSERTION(aMinElems <= aMaxElems, "aMinElems > aMaxElems!");
NS_ASSERTION(variantIndex >= 0, "Invalid variant mask!");
NS_ASSERTION(variantIndex < eNumVariantMasks, "Invalid variant mask!");
#ifdef DEBUG
NS_ASSERTION(aMaxElems <= kVariantMaskLengths[variantIndex],
"Invalid aMaxElems for this variant mask.");
#endif
// Convert the index into a mask.
aVariantMask = kVariantMasks[variantIndex];
return true;
}
bool CSSParserImpl::ParseWillChange()
{
nsCSSValue listValue;
nsCSSValueList* currentListValue = listValue.SetListValue();
bool first = true;
for (;;) {
const uint32_t variantMask = VARIANT_IDENTIFIER |
VARIANT_INHERIT |
VARIANT_NONE |
VARIANT_ALL |
VARIANT_AUTO;
nsCSSValue value;
if (!ParseSingleTokenVariant(value, variantMask, nullptr)) {
return false;
}
if (value.GetUnit() == eCSSUnit_None ||
value.GetUnit() == eCSSUnit_All)
{
return false;
}
if (value.GetUnit() != eCSSUnit_Ident) {
if (first) {
AppendValue(eCSSProperty_will_change, value);
return true;
} else {
return false;
}
}
value.AtomizeIdentValue();
nsAtom* atom = value.GetAtomValue();
if (atom == nsGkAtoms::_default || atom == nsGkAtoms::willChange) {
return false;
}
currentListValue->mValue = Move(value);
if (!ExpectSymbol(',', true)) {
break;
}
currentListValue->mNext = new nsCSSValueList;
currentListValue = currentListValue->mNext;
first = false;
}
AppendValue(eCSSProperty_will_change, listValue);
return true;
}
/* Reads a single transform function from the tokenizer stream, reporting an
* error if something goes wrong.
*/
bool
CSSParserImpl::ParseSingleTransform(bool aIsPrefixed,
bool aDisallowRelativeValues,
nsCSSValue& aValue)
{
if (!GetToken(true))
return false;
if (mToken.mType != eCSSToken_Function) {
UngetToken();
return false;
}
const uint32_t* variantMask;
uint16_t minElems, maxElems;
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
if (!GetFunctionParseInformation(keyword, aIsPrefixed,
aDisallowRelativeValues,
minElems, maxElems,
variantMask))
return false;
return ParseFunction(keyword, variantMask, 0, minElems, maxElems, aValue);
}
/* Parses a transform property list by continuously reading in properties
* and constructing a matrix from it.
* aProperty can be transform or -moz-window-transform.
* FIXME: For -moz-window-transform, it would be nice to reject non-2d
* transforms at parse time, because the implementation only supports 2d
* transforms. Instead, at the moment, non-2d transforms are treated as the
* identity transform very late in the pipeline.
*/
bool
CSSParserImpl::ParseTransform(bool aIsPrefixed, nsCSSPropertyID aProperty,
bool aDisallowRelativeValues)
{
nsCSSValue value;
// 'inherit', 'initial', 'unset' and 'none' must be alone
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
nullptr)) {
nsCSSValueSharedList* list = new nsCSSValueSharedList;
value.SetSharedListValue(list);
list->mHead = new nsCSSValueList;
nsCSSValueList* cur = list->mHead;
for (;;) {
if (!ParseSingleTransform(aIsPrefixed, aDisallowRelativeValues,
cur->mValue)) {
return false;
}
if (CheckEndProperty()) {
break;
}
cur->mNext = new nsCSSValueList;
cur = cur->mNext;
}
}
AppendValue(aProperty, value);
return true;
}
/* Reads a polygon function's argument list.
*/
bool
CSSParserImpl::ParsePolygonFunction(nsCSSValue& aValue)
{
uint16_t numArgs = 1;
nsCSSValue fillRuleValue;
if (ParseEnum(fillRuleValue, nsCSSProps::kFillRuleKTable)) {
numArgs++;
// The fill-rule must be comma separated from the polygon points.
if (!ExpectSymbol(',', true)) {
REPORT_UNEXPECTED_TOKEN(PEExpectedComma);
SkipUntil(')');
return false;
}
}
nsCSSValue coordinates;
nsCSSValuePairList* item = coordinates.SetPairListValue();
for (;;) {
nsCSSValue xValue, yValue;
if (ParseVariant(xValue, VARIANT_LPCALC, nullptr) != CSSParseResult::Ok ||
ParseVariant(yValue, VARIANT_LPCALC, nullptr) != CSSParseResult::Ok) {
REPORT_UNEXPECTED_TOKEN(PECoordinatePair);
SkipUntil(')');
return false;
}
item->mXValue = xValue;
item->mYValue = yValue;
// See whether to continue or whether to look for end of function.
if (!ExpectSymbol(',', true)) {
// We need to read the closing parenthesis.
if (!ExpectSymbol(')', true)) {
REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
SkipUntil(')');
return false;
}
break;
}
item->mNext = new nsCSSValuePairList;
item = item->mNext;
}
RefPtr<nsCSSValue::Array> functionArray =
aValue.InitFunction(eCSSKeyword_polygon, numArgs);
functionArray->Item(numArgs) = coordinates;
if (numArgs > 1) {
functionArray->Item(1) = fillRuleValue;
}
return true;
}
bool
CSSParserImpl::ParseCircleOrEllipseFunction(nsCSSKeyword aKeyword,
nsCSSValue& aValue)
{
nsCSSValue radiusX, radiusY, position;
bool hasRadius = false, hasPosition = false;
int32_t mask = VARIANT_LPCALC | VARIANT_NONNEGATIVE_DIMENSION |
VARIANT_KEYWORD;
CSSParseResult result =
ParseVariant(radiusX, mask, nsCSSProps::kShapeRadiusKTable);
if (result == CSSParseResult::Error) {
return false;
} else if (result == CSSParseResult::Ok) {
if (aKeyword == eCSSKeyword_ellipse) {
if (ParseVariant(radiusY, mask, nsCSSProps::kShapeRadiusKTable) !=
CSSParseResult::Ok) {
REPORT_UNEXPECTED_TOKEN(PEExpectedRadius);
SkipUntil(')');
return false;
}
}
hasRadius = true;
}
if (!ExpectSymbol(')', true)) {
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEPositionEOF);
return false;
}
if (mToken.mType != eCSSToken_Ident ||
!mToken.mIdent.LowerCaseEqualsLiteral("at") ||
!ParsePositionValueForBasicShape(position)) {
REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
SkipUntil(')');
return false;
}
if (!ExpectSymbol(')', true)) {
REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
SkipUntil(')');
return false;
}
hasPosition = true;
}
size_t count = aKeyword == eCSSKeyword_circle ? 2 : 3;
RefPtr<nsCSSValue::Array> functionArray =
aValue.InitFunction(aKeyword, count);
if (hasRadius) {
functionArray->Item(1) = radiusX;
if (aKeyword == eCSSKeyword_ellipse) {
functionArray->Item(2) = radiusY;
}
}
if (hasPosition) {
functionArray->Item(count) = position;
}
return true;
}
bool
CSSParserImpl::ParseInsetFunction(nsCSSValue& aValue)
{
RefPtr<nsCSSValue::Array> functionArray =
aValue.InitFunction(eCSSKeyword_inset, 5);
int count = 0;
while (count < 4) {
CSSParseResult result =
ParseVariant(functionArray->Item(count + 1), VARIANT_LPCALC, nullptr);
if (result == CSSParseResult::Error) {
count = 0;
break;
} else if (result == CSSParseResult::NotFound) {
break;
}
++count;
}
if (count == 0) {
REPORT_UNEXPECTED_TOKEN(PEExpectedShapeArg);
SkipUntil(')');
return false;
}
if (!ExpectSymbol(')', true)) {
if (!GetToken(true)) {
NS_NOTREACHED("ExpectSymbol should have returned true");
return false;
}
RefPtr<nsCSSValue::Array> radiusArray = nsCSSValue::Array::Create(4);
functionArray->Item(5).SetArrayValue(radiusArray, eCSSUnit_Array);
if (mToken.mType != eCSSToken_Ident ||
!mToken.mIdent.LowerCaseEqualsLiteral("round") ||
!ParseBoxCornerRadiiInternals(radiusArray->ItemStorage())) {
REPORT_UNEXPECTED_TOKEN(PEExpectedRadius);
SkipUntil(')');
return false;
}
if (!ExpectSymbol(')', true)) {
REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
SkipUntil(')');
return false;
}
}
return true;
}
bool
CSSParserImpl::ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens)
{
if (!GetToken(true)) {
return false;
}
if (mToken.mType != eCSSToken_Function) {
UngetToken();
return false;
}
// Specific shape function parsing always consumes tokens.
*aConsumedTokens = true;
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
switch (keyword) {
case eCSSKeyword_polygon:
return ParsePolygonFunction(aValue);
case eCSSKeyword_circle:
case eCSSKeyword_ellipse:
return ParseCircleOrEllipseFunction(keyword, aValue);
case eCSSKeyword_inset:
return ParseInsetFunction(aValue);
default:
return false;
}
}
bool
CSSParserImpl::ParseReferenceBoxAndBasicShape(
nsCSSValue& aValue,
const KTableEntry aBoxKeywordTable[])
{
nsCSSValue referenceBox;
bool hasBox = ParseEnum(referenceBox, aBoxKeywordTable);
const bool boxCameFirst = hasBox;
nsCSSValue basicShape;
bool basicShapeConsumedTokens = false;
bool hasShape = ParseBasicShape(basicShape, &basicShapeConsumedTokens);
// Parsing wasn't successful if ParseBasicShape consumed tokens but failed
// or if the token was neither a reference box nor a basic shape.
if ((!hasShape && basicShapeConsumedTokens) || (!hasBox && !hasShape)) {
return false;
}
// Check if the second argument is a reference box if the first wasn't.
if (!hasBox) {
hasBox = ParseEnum(referenceBox, aBoxKeywordTable);
}
RefPtr<nsCSSValue::Array> fullValue =
nsCSSValue::Array::Create((hasBox && hasShape) ? 2 : 1);
if (hasBox && hasShape) {
fullValue->Item(boxCameFirst ? 0 : 1) = referenceBox;
fullValue->Item(boxCameFirst ? 1 : 0) = basicShape;
} else if (hasBox) {
fullValue->Item(0) = referenceBox;
} else {
MOZ_ASSERT(hasShape, "should've bailed if we got neither box nor shape");
fullValue->Item(0) = basicShape;
}
aValue.SetArrayValue(fullValue, eCSSUnit_Array);
return true;
}
// Parse a clip-path url to a <clipPath> element or a basic shape.
bool
CSSParserImpl::ParseClipPath(nsCSSValue& aValue)
{
if (ParseSingleTokenVariant(aValue, VARIANT_HUO, nullptr)) {
return true;
}
return ParseReferenceBoxAndBasicShape(
aValue, nsCSSProps::kClipPathGeometryBoxKTable);
}
// none | [ <basic-shape> || <shape-box> ] | <image>
bool
CSSParserImpl::ParseShapeOutside(nsCSSValue& aValue)
{
if (ParseSingleTokenVariant(aValue, VARIANT_HUO, nullptr)) {
// 'inherit', 'initial', 'unset', 'none', and <image> url must be alone.
return true;
}
return ParseReferenceBoxAndBasicShape(
aValue, nsCSSProps::kShapeOutsideShapeBoxKTable);
}
bool CSSParserImpl::ParseTransformOrigin(nsCSSPropertyID aProperty)
{
nsCSSValuePair position;
if (!ParseBoxPositionValues(position, true))
return false;
// Unlike many other uses of pairs, this position should always be stored
// as a pair, even if the values are the same, so it always serializes as
// a pair, and to keep the computation code simple.
if (position.mXValue.GetUnit() == eCSSUnit_Inherit ||
position.mXValue.GetUnit() == eCSSUnit_Initial ||
position.mXValue.GetUnit() == eCSSUnit_Unset) {
MOZ_ASSERT(position.mXValue == position.mYValue,
"inherit/initial/unset only half?");
AppendValue(aProperty, position.mXValue);
} else {
nsCSSValue value;
if (aProperty != eCSSProperty_transform_origin) {
value.SetPairValue(position.mXValue, position.mYValue);
} else {
nsCSSValue depth;
CSSParseResult result =
ParseVariant(depth, VARIANT_LENGTH | VARIANT_CALC, nullptr);
if (result == CSSParseResult::Error) {
return false;
} else if (result == CSSParseResult::NotFound) {
depth.SetFloatValue(0.0f, eCSSUnit_Pixel);
}
value.SetTripletValue(position.mXValue, position.mYValue, depth);
}
AppendValue(aProperty, value);
}
return true;
}
/**
* Reads a drop-shadow value. At the moment the Filter Effects specification
* just expects one shadow item. Should this ever change to a list of shadow
* items, use ParseShadowList instead.
*/
bool
CSSParserImpl::ParseDropShadow(nsCSSValue* aValue)
{
// Use nsCSSValueList to reuse the shadow resolving code in
// nsRuleNode and nsComputedDOMStyle.
nsCSSValue shadow;
nsCSSValueList* cur = shadow.SetListValue();
if (!ParseShadowItem(cur->mValue, false))
return false;
if (!ExpectSymbol(')', true))
return false;
nsCSSValue::Array* dropShadow = aValue->InitFunction(eCSSKeyword_drop_shadow, 1);
// Copy things over.
dropShadow->Item(1) = shadow;
return true;
}
/**
* Reads a single url or filter function from the tokenizer stream, reporting an
* error if something goes wrong.
*/
bool
CSSParserImpl::ParseSingleFilter(nsCSSValue* aValue)
{
if (ParseSingleTokenVariant(*aValue, VARIANT_URL, nullptr)) {
return true;
}
if (!nsLayoutUtils::CSSFiltersEnabled()) {
// With CSS Filters disabled, we should only accept an SVG reference filter.
REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURL);
return false;
}
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEFilterEOF);
return false;
}
if (mToken.mType != eCSSToken_Function) {
REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction);
UngetToken();
return false;
}
nsCSSKeyword functionName = nsCSSKeywords::LookupKeyword(mToken.mIdent);
// Parse drop-shadow independently of the other filter functions
// because of its more complex characteristics.
if (functionName == eCSSKeyword_drop_shadow) {
if (ParseDropShadow(aValue)) {
return true;
} else {
// Unrecognized filter function.
REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction);
SkipUntil(')');
return false;
}
}
// Set up the parsing rules based on the filter function.
uint32_t variantMask = VARIANT_PN;
bool rejectNegativeArgument = true;
bool clampArgumentToOne = false;
switch (functionName) {
case eCSSKeyword_blur:
variantMask = VARIANT_LCALC | VARIANT_NONNEGATIVE_DIMENSION;
// VARIANT_NONNEGATIVE_DIMENSION will already reject negative lengths.
rejectNegativeArgument = false;
break;
case eCSSKeyword_brightness:
case eCSSKeyword_contrast:
case eCSSKeyword_saturate:
break;
case eCSSKeyword_grayscale:
case eCSSKeyword_invert:
case eCSSKeyword_sepia:
case eCSSKeyword_opacity:
clampArgumentToOne = true;
break;
case eCSSKeyword_hue_rotate:
variantMask = VARIANT_ANGLE;
rejectNegativeArgument = false;
break;
default:
// Unrecognized filter function.
REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction);
SkipUntil(')');
return false;
}
// Parse the function.
uint16_t minElems = 1U;
uint16_t maxElems = 1U;
uint32_t allVariants = 0;
if (!ParseFunction(functionName, &variantMask, allVariants,
minElems, maxElems, *aValue)) {
REPORT_UNEXPECTED(PEFilterFunctionArgumentsParsingError);
return false;
}
// Get the first and only argument to the filter function.
MOZ_ASSERT(aValue->GetUnit() == eCSSUnit_Function,
"expected a filter function");
MOZ_ASSERT(aValue->UnitHasArrayValue(),
"filter function should be an array");
MOZ_ASSERT(aValue->GetArrayValue()->Count() == 2,
"filter function should have exactly one argument");
nsCSSValue& arg = aValue->GetArrayValue()->Item(1);
if (rejectNegativeArgument &&
((arg.GetUnit() == eCSSUnit_Percent && arg.GetPercentValue() < 0.0f) ||
(arg.GetUnit() == eCSSUnit_Number && arg.GetFloatValue() < 0.0f))) {
REPORT_UNEXPECTED(PEExpectedNonnegativeNP);
return false;
}
if (clampArgumentToOne) {
if (arg.GetUnit() == eCSSUnit_Number &&
arg.GetFloatValue() > 1.0f) {
arg.SetFloatValue(1.0f, arg.GetUnit());
} else if (arg.GetUnit() == eCSSUnit_Percent &&
arg.GetPercentValue() > 1.0f) {
arg.SetPercentValue(1.0f);
}
}
return true;
}
/**
* Parses a filter property value by continuously reading in urls and/or filter
* functions and constructing a list.
*
* When CSS Filters are enabled, the filter property accepts one or more SVG
* reference filters and/or CSS filter functions.
* e.g. filter: url(#my-filter-1) blur(3px) url(#my-filter-2) grayscale(50%);
*
* When CSS Filters are disabled, the filter property only accepts one SVG
* reference filter.
* e.g. filter: url(#my-filter);
*/
bool
CSSParserImpl::ParseFilter()
{
nsCSSValue value;
// 'inherit', 'initial', 'unset' and 'none' must be alone
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
nullptr)) {
nsCSSValueList* cur = value.SetListValue();
while (cur) {
if (!ParseSingleFilter(&cur->mValue)) {
return false;
}
if (CheckEndProperty()) {
break;
}
if (!nsLayoutUtils::CSSFiltersEnabled()) {
// With CSS Filters disabled, we should only accept one SVG reference
// filter.
REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
return false;
}
cur->mNext = new nsCSSValueList;
cur = cur->mNext;
}
}
AppendValue(eCSSProperty_filter, value);
return true;
}
bool
CSSParserImpl::ParseTransitionProperty()
{
nsCSSValue value;
// 'inherit', 'initial', 'unset' and 'none' must be alone
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
nullptr)) {
// Accept a list of arbitrary identifiers. They should be
// CSS properties, but we want to accept any so that we
// accept properties that we don't know about yet, e.g.
// transition-property: invalid-property, left, opacity;
nsCSSValueList* cur = value.SetListValue();
for (;;) {
if (!ParseSingleTokenVariant(cur->mValue,
VARIANT_IDENTIFIER | VARIANT_ALL,
nullptr)) {
return false;
}
if (cur->mValue.GetUnit() == eCSSUnit_Ident) {
nsDependentString str(cur->mValue.GetStringBufferValue());
// Exclude 'none', 'inherit', 'initial' and 'unset' according to the
// same rules as for 'counter-reset' in CSS 2.1.
if (str.LowerCaseEqualsLiteral("none") ||
str.LowerCaseEqualsLiteral("inherit") ||
str.LowerCaseEqualsLiteral("initial") ||
(str.LowerCaseEqualsLiteral("unset") &&
nsLayoutUtils::UnsetValueEnabled())) {
return false;
}
}
if (!ExpectSymbol(',', true)) {
break;
}
cur->mNext = new nsCSSValueList;
cur = cur->mNext;
}
}
AppendValue(eCSSProperty_transition_property, value);
return true;
}
bool
CSSParserImpl::ParseTransitionTimingFunctionValues(nsCSSValue& aValue)
{
NS_ASSERTION(!mHavePushBack &&
mToken.mType == eCSSToken_Function &&
mToken.mIdent.LowerCaseEqualsLiteral("cubic-bezier"),
"unexpected initial state");
RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(4);
float x1, x2, y1, y2;
if (!ParseTransitionTimingFunctionValueComponent(x1, ',', true) ||
!ParseTransitionTimingFunctionValueComponent(y1, ',', false) ||
!ParseTransitionTimingFunctionValueComponent(x2, ',', true) ||
!ParseTransitionTimingFunctionValueComponent(y2, ')', false)) {
return false;
}
val->Item(0).SetFloatValue(x1, eCSSUnit_Number);
val->Item(1).SetFloatValue(y1, eCSSUnit_Number);
val->Item(2).SetFloatValue(x2, eCSSUnit_Number);
val->Item(3).SetFloatValue(y2, eCSSUnit_Number);
aValue.SetArrayValue(val, eCSSUnit_Cubic_Bezier);
return true;
}
bool
CSSParserImpl::ParseTransitionTimingFunctionValueComponent(float& aComponent,
char aStop,
bool aIsXPoint)
{
if (!GetToken(true)) {
return false;
}
nsCSSToken* tk = &mToken;
if (tk->mType == eCSSToken_Number) {
float num = tk->mNumber;
// Clamp infinity or -infinity values to max float or -max float to avoid
// calculations with infinity.
num = mozilla::clamped(num, -std::numeric_limits<float>::max(),
std::numeric_limits<float>::max());
// X control point should be inside [0, 1] range.
if (aIsXPoint && (num < 0.0 || num > 1.0)) {
return false;
}
aComponent = num;
if (ExpectSymbol(aStop, true)) {
return true;
}
}
return false;
}
bool
CSSParserImpl::ParseTransitionStepTimingFunctionValues(nsCSSValue& aValue)
{
NS_ASSERTION(!mHavePushBack &&
mToken.mType == eCSSToken_Function &&
mToken.mIdent.LowerCaseEqualsLiteral("steps"),
"unexpected initial state");
RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(2);
if (!ParseSingleTokenOneOrLargerVariant(val->Item(0), VARIANT_INTEGER,
nullptr)) {
return false;
}
int32_t type = -1; // indicates an implicit end value
if (ExpectSymbol(',', true)) {
if (!GetToken(true)) {
return false;
}
if (mToken.mType == eCSSToken_Ident) {
if (mToken.mIdent.LowerCaseEqualsLiteral("start")) {
type = NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("end")) {
type = NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_END;
}
}
if (type == -1) {
UngetToken();
return false;
}
}
val->Item(1).SetIntValue(type, eCSSUnit_Enumerated);
if (!ExpectSymbol(')', true)) {
return false;
}
aValue.SetArrayValue(val, eCSSUnit_Steps);
return true;
}
bool
CSSParserImpl::ParseTransitionFramesTimingFunctionValues(nsCSSValue& aValue)
{
NS_ASSERTION(!mHavePushBack &&
mToken.mType == eCSSToken_Function &&
mToken.mIdent.LowerCaseEqualsLiteral("frames"),
"unexpected initial state");
nsCSSKeyword functionName = nsCSSKeywords::LookupKeyword(mToken.mIdent);
MOZ_ASSERT(functionName == eCSSKeyword_frames);
nsCSSValue frameNumber;
if (!ParseSingleTokenOneOrLargerVariant(frameNumber, VARIANT_INTEGER,
nullptr)) {
return false;
}
MOZ_ASSERT(frameNumber.GetIntValue() >= 1,
"Parsing function should've enforced OneOrLarger, per its name");
// The number of frames must be a positive integer greater than one.
if (frameNumber.GetIntValue() == 1) {
return false;
}
if (!ExpectSymbol(')', true)) {
return false;
}
RefPtr<nsCSSValue::Array> val = aValue.InitFunction(functionName, 1);
val->Item(1) = frameNumber;
return true;
}
static nsCSSValueList*
AppendValueToList(nsCSSValue& aContainer,
nsCSSValueList* aTail,
const nsCSSValue& aValue)
{
nsCSSValueList* entry;
if (aContainer.GetUnit() == eCSSUnit_Null) {
MOZ_ASSERT(!aTail, "should not have an entry");
entry = aContainer.SetListValue();
} else {
MOZ_ASSERT(!aTail->mNext, "should not have a next entry");
MOZ_ASSERT(aContainer.GetUnit() == eCSSUnit_List, "not a list");
entry = new nsCSSValueList;
aTail->mNext = entry;
}
entry->mValue = aValue;
return entry;
}
CSSParserImpl::ParseAnimationOrTransitionShorthandResult
CSSParserImpl::ParseAnimationOrTransitionShorthand(
const nsCSSPropertyID* aProperties,
const nsCSSValue* aInitialValues,
nsCSSValue* aValues,
size_t aNumProperties)
{
nsCSSValue tempValue;
// first see if 'inherit', 'initial' or 'unset' is specified. If one is,
// it can be the only thing specified, so don't attempt to parse any
// additional properties
if (ParseSingleTokenVariant(tempValue, VARIANT_INHERIT, nullptr)) {
for (uint32_t i = 0; i < aNumProperties; ++i) {
AppendValue(aProperties[i], tempValue);
}
return eParseAnimationOrTransitionShorthand_Inherit;
}
static const size_t maxNumProperties = 8;
MOZ_ASSERT(aNumProperties <= maxNumProperties,
"can't handle this many properties");
nsCSSValueList *cur[maxNumProperties];
bool parsedProperty[maxNumProperties];
for (size_t i = 0; i < aNumProperties; ++i) {
cur[i] = nullptr;
}
bool atEOP = false; // at end of property?
for (;;) { // loop over comma-separated transitions or animations
// whether a particular subproperty was specified for this
// transition or animation
bool haveAnyProperty = false;
for (size_t i = 0; i < aNumProperties; ++i) {
parsedProperty[i] = false;
}
for (;;) { // loop over values within a transition or animation
bool foundProperty = false;
// check to see if we're at the end of one full transition or
// animation definition (either because we hit a comma or because
// we hit the end of the property definition)
if (ExpectSymbol(',', true))
break;
if (CheckEndProperty()) {
atEOP = true;
break;
}
// else, try to parse the next transition or animation sub-property
for (uint32_t i = 0; !foundProperty && i < aNumProperties; ++i) {
if (!parsedProperty[i]) {
// if we haven't found this property yet, try to parse it
CSSParseResult result =
ParseSingleValueProperty(tempValue, aProperties[i]);
if (result == CSSParseResult::Error) {
return eParseAnimationOrTransitionShorthand_Error;
}
if (result == CSSParseResult::Ok) {
parsedProperty[i] = true;
cur[i] = AppendValueToList(aValues[i], cur[i], tempValue);
foundProperty = true;
haveAnyProperty = true;
break; // out of inner loop; continue looking for next sub-property
}
}
}
if (!foundProperty) {
// We're not at a ',' or at the end of the property, but we couldn't
// parse any of the sub-properties, so the declaration is invalid.
return eParseAnimationOrTransitionShorthand_Error;
}
}
if (!haveAnyProperty) {
// Got an empty item.
return eParseAnimationOrTransitionShorthand_Error;
}
// We hit the end of the property or the end of one transition
// or animation definition, add its components to the list.
for (uint32_t i = 0; i < aNumProperties; ++i) {
// If all of the subproperties were not explicitly specified, fill
// in the missing ones with initial values.
if (!parsedProperty[i]) {
cur[i] = AppendValueToList(aValues[i], cur[i], aInitialValues[i]);
}
}
if (atEOP)
break;
// else we just hit a ',' so continue parsing the next compound transition
}
return eParseAnimationOrTransitionShorthand_Values;
}
bool
CSSParserImpl::ParseTransition()
{
static const nsCSSPropertyID kTransitionProperties[] = {
eCSSProperty_transition_duration,
eCSSProperty_transition_timing_function,
// Must check 'transition-delay' after 'transition-duration', since
// that's our assumption about what the spec means for the shorthand
// syntax (the first time given is the duration, and the second
// given is the delay).
eCSSProperty_transition_delay,
// Must check 'transition-property' after
// 'transition-timing-function' since 'transition-property' accepts
// any keyword.
eCSSProperty_transition_property
};
static const uint32_t numProps = MOZ_ARRAY_LENGTH(kTransitionProperties);
// this is a shorthand property that accepts -property, -delay,
// -duration, and -timing-function with some components missing.
// there can be multiple transitions, separated with commas
nsCSSValue initialValues[numProps];
initialValues[0].SetFloatValue(0.0, eCSSUnit_Seconds);
initialValues[1].SetIntValue(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE,
eCSSUnit_Enumerated);
initialValues[2].SetFloatValue(0.0, eCSSUnit_Seconds);
initialValues[3].SetAllValue();
nsCSSValue values[numProps];
ParseAnimationOrTransitionShorthandResult spres =
ParseAnimationOrTransitionShorthand(kTransitionProperties,
initialValues, values, numProps);
if (spres != eParseAnimationOrTransitionShorthand_Values) {
return spres != eParseAnimationOrTransitionShorthand_Error;
}
// Make two checks on the list for 'transition-property':
// + If there is more than one item, then none of the items can be
// 'none'.
// + None of the items can be 'inherit', 'initial' or 'unset'.
{
MOZ_ASSERT(kTransitionProperties[3] == eCSSProperty_transition_property,
"array index mismatch");
nsCSSValueList *l = values[3].GetListValue();
bool multipleItems = !!l->mNext;
do {
const nsCSSValue& val = l->mValue;
if (val.GetUnit() == eCSSUnit_None) {
if (multipleItems) {
// This is a syntax error.
return false;
}
// Unbox a solitary 'none'.
values[3].SetNoneValue();
break;
}
if (val.GetUnit() == eCSSUnit_Ident) {
nsDependentString str(val.GetStringBufferValue());
if (str.EqualsLiteral("inherit") ||
str.EqualsLiteral("initial") ||
(str.EqualsLiteral("unset") &&
nsLayoutUtils::UnsetValueEnabled())) {
return false;
}
}
} while ((l = l->mNext));
}
// Save all parsed transition sub-properties in mTempData
for (uint32_t i = 0; i < numProps; ++i) {
AppendValue(kTransitionProperties[i], values[i]);
}
return true;
}
bool
CSSParserImpl::ParseAnimation()
{
static const nsCSSPropertyID kAnimationProperties[] = {
eCSSProperty_animation_duration,
eCSSProperty_animation_timing_function,
// Must check 'animation-delay' after 'animation-duration', since
// that's our assumption about what the spec means for the shorthand
// syntax (the first time given is the duration, and the second
// given is the delay).
eCSSProperty_animation_delay,
eCSSProperty_animation_direction,
eCSSProperty_animation_fill_mode,
eCSSProperty_animation_iteration_count,
eCSSProperty_animation_play_state,
// Must check 'animation-name' after 'animation-timing-function',
// 'animation-direction', 'animation-fill-mode',
// 'animation-iteration-count', and 'animation-play-state' since
// 'animation-name' accepts any keyword.
eCSSProperty_animation_name
};
static const uint32_t numProps = MOZ_ARRAY_LENGTH(kAnimationProperties);
// this is a shorthand property that accepts -property, -delay,
// -duration, and -timing-function with some components missing.
// there can be multiple animations, separated with commas
nsCSSValue initialValues[numProps];
initialValues[0].SetFloatValue(0.0, eCSSUnit_Seconds);
initialValues[1].SetIntValue(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE,
eCSSUnit_Enumerated);
initialValues[2].SetFloatValue(0.0, eCSSUnit_Seconds);
initialValues[3].SetIntValue(static_cast<int32_t>(dom::PlaybackDirection::Normal),
eCSSUnit_Enumerated);
initialValues[4].SetIntValue(static_cast<int32_t>(dom::FillMode::None),
eCSSUnit_Enumerated);
initialValues[5].SetFloatValue(1.0f, eCSSUnit_Number);
initialValues[6].SetIntValue(NS_STYLE_ANIMATION_PLAY_STATE_RUNNING, eCSSUnit_Enumerated);
initialValues[7].SetNoneValue();
nsCSSValue values[numProps];
ParseAnimationOrTransitionShorthandResult spres =
ParseAnimationOrTransitionShorthand(kAnimationProperties,
initialValues, values, numProps);
if (spres != eParseAnimationOrTransitionShorthand_Values) {
return spres != eParseAnimationOrTransitionShorthand_Error;
}
// Save all parsed animation sub-properties in mTempData
for (uint32_t i = 0; i < numProps; ++i) {
AppendValue(kAnimationProperties[i], values[i]);
}
return true;
}
bool
CSSParserImpl::ParseShadowItem(nsCSSValue& aValue, bool aIsBoxShadow)
{
// A shadow list item is an array, with entries in this sequence:
enum {
IndexX,
IndexY,
IndexRadius,
IndexSpread, // only for box-shadow
IndexColor,
IndexInset // only for box-shadow
};
RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(6);
if (aIsBoxShadow) {
// Optional inset keyword (ignore errors)
Unused << ParseSingleTokenVariant(val->Item(IndexInset), VARIANT_KEYWORD,
nsCSSProps::kBoxShadowTypeKTable);
}
nsCSSValue xOrColor;
bool haveColor = false;
if (ParseVariant(xOrColor, VARIANT_COLOR | VARIANT_LENGTH | VARIANT_CALC,
nullptr) != CSSParseResult::Ok) {
return false;
}
if (xOrColor.IsLengthUnit() || xOrColor.IsCalcUnit()) {
val->Item(IndexX) = xOrColor;
} else {
// Must be a color (as string or color value)
NS_ASSERTION(xOrColor.GetUnit() == eCSSUnit_Ident ||
xOrColor.GetUnit() == eCSSUnit_EnumColor ||
xOrColor.IsNumericColorUnit(),
"Must be a color value");
val->Item(IndexColor) = xOrColor;
haveColor = true;
// X coordinate mandatory after color
if (ParseVariant(val->Item(IndexX), VARIANT_LENGTH | VARIANT_CALC,
nullptr) != CSSParseResult::Ok) {
return false;
}
}
// Y coordinate; mandatory
if (ParseVariant(val->Item(IndexY), VARIANT_LENGTH | VARIANT_CALC,
nullptr) != CSSParseResult::Ok) {
return false;
}
// Optional radius. Ignore errors except if they pass a negative
// value which we must reject. If we use ParseNonNegativeVariant
// we can't tell the difference between an unspecified radius
// and a negative radius.
CSSParseResult result =
ParseVariant(val->Item(IndexRadius), VARIANT_LENGTH | VARIANT_CALC,
nullptr);
if (result == CSSParseResult::Error) {
return false;
} else if (result == CSSParseResult::Ok) {
if (val->Item(IndexRadius).IsLengthUnit() &&
val->Item(IndexRadius).GetFloatValue() < 0) {
return false;
}
}
if (aIsBoxShadow) {
// Optional spread
if (ParseVariant(val->Item(IndexSpread), VARIANT_LENGTH | VARIANT_CALC,
nullptr) == CSSParseResult::Error) {
return false;
}
}
if (!haveColor) {
// Optional color
if (ParseVariant(val->Item(IndexColor), VARIANT_COLOR, nullptr) ==
CSSParseResult::Error) {
return false;
}
}
if (aIsBoxShadow && val->Item(IndexInset).GetUnit() == eCSSUnit_Null) {
// Optional inset keyword (ignore errors)
Unused << ParseSingleTokenVariant(val->Item(IndexInset), VARIANT_KEYWORD,
nsCSSProps::kBoxShadowTypeKTable);
}
aValue.SetArrayValue(val, eCSSUnit_Array);
return true;
}
bool
CSSParserImpl::ParseShadowList(nsCSSPropertyID aProperty)
{
nsAutoParseCompoundProperty compound(this);
bool isBoxShadow = aProperty == eCSSProperty_box_shadow;
nsCSSValue value;
// 'inherit', 'initial', 'unset' and 'none' must be alone
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
nullptr)) {
nsCSSValueList* cur = value.SetListValue();
for (;;) {
if (!ParseShadowItem(cur->mValue, isBoxShadow)) {
return false;
}
if (!ExpectSymbol(',', true)) {
break;
}
cur->mNext = new nsCSSValueList;
cur = cur->mNext;
}
}
AppendValue(aProperty, value);
return true;
}
int32_t
CSSParserImpl::GetNamespaceIdForPrefix(const nsString& aPrefix)
{
NS_PRECONDITION(!aPrefix.IsEmpty(), "Must have a prefix here");
int32_t nameSpaceID = kNameSpaceID_Unknown;
if (mNameSpaceMap) {
// user-specified identifiers are case-sensitive (bug 416106)
RefPtr<nsAtom> prefix = NS_Atomize(aPrefix);
nameSpaceID = mNameSpaceMap->FindNameSpaceID(prefix);
}
// else no declared namespaces
if (nameSpaceID == kNameSpaceID_Unknown) { // unknown prefix, dump it
REPORT_UNEXPECTED_P(PEUnknownNamespacePrefix, aPrefix);
}
return nameSpaceID;
}
void
CSSParserImpl::SetDefaultNamespaceOnSelector(nsCSSSelector& aSelector)
{
if (mNameSpaceMap) {
aSelector.SetNameSpace(mNameSpaceMap->FindNameSpaceID(nullptr));
} else {
aSelector.SetNameSpace(kNameSpaceID_Unknown); // wildcard
}
}
bool
CSSParserImpl::ParsePaint(nsCSSPropertyID aPropID)
{
nsCSSValue x, y;
if (ParseVariant(x, VARIANT_HC | VARIANT_NONE | VARIANT_URL | VARIANT_KEYWORD,
nsCSSProps::kContextPatternKTable) != CSSParseResult::Ok) {
return false;
}
bool hasFallback = false;
bool canHaveFallback = x.GetUnit() == eCSSUnit_URL ||
x.GetUnit() == eCSSUnit_Enumerated;
if (canHaveFallback) {
CSSParseResult result =
ParseVariant(y, VARIANT_COLOR | VARIANT_NONE, nullptr);
if (result == CSSParseResult::Error) {
return false;
}
hasFallback = (result != CSSParseResult::NotFound);
}
if (hasFallback) {
nsCSSValue val;
val.SetPairValue(x, y);
AppendValue(aPropID, val);
} else {
AppendValue(aPropID, x);
}
return true;
}
bool
CSSParserImpl::ParseDasharray()
{
nsCSSValue value;
// 'inherit', 'initial', 'unset' and 'none' are only allowed on their own
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE |
VARIANT_OPENTYPE_SVG_KEYWORD,
nsCSSProps::kStrokeContextValueKTable)) {
nsCSSValueList *cur = value.SetListValue();
for (;;) {
if (!ParseSingleTokenNonNegativeVariant(cur->mValue, VARIANT_LPN,
nullptr)) {
return false;
}
if (CheckEndProperty()) {
break;
}
// skip optional commas between elements
(void)ExpectSymbol(',', true);
cur->mNext = new nsCSSValueList;
cur = cur->mNext;
}
}
AppendValue(eCSSProperty_stroke_dasharray, value);
return true;
}
bool
CSSParserImpl::ParseMarker()
{
nsCSSValue marker;
if (ParseSingleValueProperty(marker, eCSSProperty_marker_end) ==
CSSParseResult::Ok) {
AppendValue(eCSSProperty_marker_end, marker);
AppendValue(eCSSProperty_marker_mid, marker);
AppendValue(eCSSProperty_marker_start, marker);
return true;
}
return false;
}
bool
CSSParserImpl::ParsePaintOrder()
{
static_assert
((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) > NS_STYLE_PAINT_ORDER_LAST_VALUE,
"bitfield width insufficient for paint-order constants");
static const KTableEntry kPaintOrderKTable[] = {
{ eCSSKeyword_normal, NS_STYLE_PAINT_ORDER_NORMAL },
{ eCSSKeyword_fill, NS_STYLE_PAINT_ORDER_FILL },
{ eCSSKeyword_stroke, NS_STYLE_PAINT_ORDER_STROKE },
{ eCSSKeyword_markers, NS_STYLE_PAINT_ORDER_MARKERS },
{ eCSSKeyword_UNKNOWN, -1 }
};
static_assert(MOZ_ARRAY_LENGTH(kPaintOrderKTable) ==
NS_STYLE_PAINT_ORDER_LAST_VALUE + 2,
"missing paint-order values in kPaintOrderKTable");
nsCSSValue value;
if (!ParseSingleTokenVariant(value, VARIANT_HK, kPaintOrderKTable)) {
return false;
}
uint32_t seen = 0;
uint32_t order = 0;
uint32_t position = 0;
// Ensure that even cast to a signed int32_t when stored in CSSValue,
// we have enough space for the entire paint-order value.
static_assert
(NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE < 32,
"seen and order not big enough");
if (value.GetUnit() == eCSSUnit_Enumerated) {
uint32_t component = static_cast<uint32_t>(value.GetIntValue());
if (component != NS_STYLE_PAINT_ORDER_NORMAL) {
bool parsedOK = true;
for (;;) {
if (seen & (1 << component)) {
// Already seen this component.
UngetToken();
parsedOK = false;
break;
}
seen |= (1 << component);
order |= (component << position);
position += NS_STYLE_PAINT_ORDER_BITWIDTH;
if (!ParseEnum(value, kPaintOrderKTable)) {
break;
}
component = value.GetIntValue();
if (component == NS_STYLE_PAINT_ORDER_NORMAL) {
// Can't have "normal" in the middle of the list of paint components.
UngetToken();
parsedOK = false;
break;
}
}
// Fill in the remaining paint-order components in the order of their
// constant values.
if (parsedOK) {
for (component = 1;
component <= NS_STYLE_PAINT_ORDER_LAST_VALUE;
component++) {
if (!(seen & (1 << component))) {
order |= (component << position);
position += NS_STYLE_PAINT_ORDER_BITWIDTH;
}
}
}
}
static_assert(NS_STYLE_PAINT_ORDER_NORMAL == 0,
"unexpected value for NS_STYLE_PAINT_ORDER_NORMAL");
value.SetIntValue(static_cast<int32_t>(order), eCSSUnit_Enumerated);
}
AppendValue(eCSSProperty_paint_order, value);
return true;
}
bool
CSSParserImpl::BackslashDropped()
{
return mScanner->GetEOFCharacters() &
nsCSSScanner::eEOFCharacters_DropBackslash;
}
void
CSSParserImpl::AppendImpliedEOFCharacters(nsAString& aResult)
{
nsCSSScanner::AppendImpliedEOFCharacters(mScanner->GetEOFCharacters(),
aResult);
}
bool
CSSParserImpl::ParseAll()
{
nsCSSValue value;
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
return false;
}
// It's unlikely we'll want to use 'all' from within a UA style sheet, so
// instead of computing the correct EnabledState value we just expand out
// to all content-visible properties.
CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, eCSSProperty_all,
CSSEnabledState::eForAllContent) {
AppendValue(*p, value);
}
return true;
}
bool
CSSParserImpl::ParseVariableDeclaration(CSSVariableDeclarations::Type* aType,
nsString& aValue)
{
CSSVariableDeclarations::Type type;
nsString variableValue;
bool dropBackslash;
nsString impliedCharacters;
// Record the token stream while parsing a variable value.
if (!mInSupportsCondition) {
mScanner->StartRecording();
}
if (!ParseValueWithVariables(&type, &dropBackslash, impliedCharacters,
nullptr, nullptr)) {
if (!mInSupportsCondition) {
mScanner->StopRecording();
}
return false;
}
if (!mInSupportsCondition) {
if (type == CSSVariableDeclarations::eTokenStream) {
// This was indeed a token stream value, so store it in variableValue.
mScanner->StopRecording(variableValue);
if (dropBackslash) {
MOZ_ASSERT(!variableValue.IsEmpty() &&
variableValue[variableValue.Length() - 1] == '\\');
variableValue.Truncate(variableValue.Length() - 1);
}
variableValue.Append(impliedCharacters);
} else {
// This was either 'inherit' or 'initial'; we don't need the recorded
// input.
mScanner->StopRecording();
}
}
if (mHavePushBack && type == CSSVariableDeclarations::eTokenStream) {
// If we came to the end of a valid variable declaration and a token was
// pushed back, then it would have been ended by '!', ')', ';', ']' or '}'.
// We need to remove it from the recorded variable value.
MOZ_ASSERT(mToken.IsSymbol('!') ||
mToken.IsSymbol(')') ||
mToken.IsSymbol(';') ||
mToken.IsSymbol(']') ||
mToken.IsSymbol('}'));
if (!mInSupportsCondition) {
MOZ_ASSERT(!variableValue.IsEmpty());
MOZ_ASSERT(variableValue[variableValue.Length() - 1] == mToken.mSymbol);
variableValue.Truncate(variableValue.Length() - 1);
}
}
*aType = type;
aValue = variableValue;
return true;
}
bool
CSSParserImpl::ParseScrollSnapType()
{
nsCSSValue value;
if (!ParseSingleTokenVariant(value, VARIANT_HK,
nsCSSProps::kScrollSnapTypeKTable)) {
return false;
}
AppendValue(eCSSProperty_scroll_snap_type_x, value);
AppendValue(eCSSProperty_scroll_snap_type_y, value);
return true;
}
bool
CSSParserImpl::ParseScrollSnapPoints(nsCSSValue& aValue, nsCSSPropertyID aPropID)
{
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NONE,
nullptr)) {
return true;
}
if (!GetToken(true)) {
return false;
}
if (mToken.mType == eCSSToken_Function &&
nsCSSKeywords::LookupKeyword(mToken.mIdent) == eCSSKeyword_repeat) {
nsCSSValue lengthValue;
if (ParseNonNegativeVariant(lengthValue,
VARIANT_LENGTH | VARIANT_PERCENT | VARIANT_CALC,
nullptr) != CSSParseResult::Ok) {
REPORT_UNEXPECTED(PEExpectedNonnegativeNP);
SkipUntil(')');
return false;
}
if (!ExpectSymbol(')', true)) {
REPORT_UNEXPECTED(PEExpectedCloseParen);
SkipUntil(')');
return false;
}
RefPtr<nsCSSValue::Array> functionArray =
aValue.InitFunction(eCSSKeyword_repeat, 1);
functionArray->Item(1) = lengthValue;
return true;
}
UngetToken();
return false;
}
bool
CSSParserImpl::ParseScrollSnapDestination(nsCSSValue& aValue)
{
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
return true;
}
nsCSSValue itemValue;
if (!ParsePositionValue(aValue)) {
REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
return false;
}
return true;
}
// This function is very similar to ParseImageLayerPosition, and ParseImageLayerSize.
bool
CSSParserImpl::ParseScrollSnapCoordinate(nsCSSValue& aValue)
{
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NONE,
nullptr)) {
return true;
}
nsCSSValue itemValue;
if (!ParsePositionValue(itemValue)) {
REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
return false;
}
nsCSSValueList* item = aValue.SetListValue();
for (;;) {
item->mValue = itemValue;
if (!ExpectSymbol(',', true)) {
break;
}
if (!ParsePositionValue(itemValue)) {
REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
return false;
}
item->mNext = new nsCSSValueList;
item = item->mNext;
}
return true;
}
bool
CSSParserImpl::ParseValueWithVariables(CSSVariableDeclarations::Type* aType,
bool* aDropBackslash,
nsString& aImpliedCharacters,
void (*aFunc)(const nsAString&, void*),
void* aData)
{
// A property value is invalid if it contains variable references and also:
//
// * has unbalanced parens, brackets or braces
// * has any BAD_STRING or BAD_URL tokens
// * has any ';' or '!' tokens at the top level of a variable reference's
// fallback
//
// If the property is a custom property (i.e. a variable declaration), then
// it is also invalid if it consists of no tokens, such as:
//
// --invalid:;
//
// Note that is valid for a custom property to have a value that consists
// solely of white space, such as:
//
// --valid: ;
// Stack of closing characters for currently open constructs.
StopSymbolCharStack stack;
// Indexes into ')' characters in |stack| that correspond to "var(". This
// is used to stop parsing when we encounter a '!' or ';' at the top level
// of a variable reference's fallback.
AutoTArray<uint32_t, 16> references;
if (!GetToken(false)) {
// Variable value was empty since we reached EOF.
REPORT_UNEXPECTED_EOF(PEVariableEOF);
return false;
}
if (mToken.mType == eCSSToken_Symbol &&
(mToken.mSymbol == '!' ||
mToken.mSymbol == ')' ||
mToken.mSymbol == ';' ||
mToken.mSymbol == ']' ||
mToken.mSymbol == '}')) {
// Variable value was empty since we reached the end of the construct.
UngetToken();
REPORT_UNEXPECTED_TOKEN(PEVariableEmpty);
return false;
}
if (mToken.mType == eCSSToken_Whitespace) {
if (!GetToken(true)) {
// Variable value was white space only. This is valid.
MOZ_ASSERT(!BackslashDropped());
*aType = CSSVariableDeclarations::eTokenStream;
*aDropBackslash = false;
AppendImpliedEOFCharacters(aImpliedCharacters);
return true;
}
}
// Look for 'initial', 'inherit' or 'unset' as the first non-white space
// token.
CSSVariableDeclarations::Type type = CSSVariableDeclarations::eTokenStream;
if (mToken.mType == eCSSToken_Ident) {
if (mToken.mIdent.LowerCaseEqualsLiteral("initial")) {
type = CSSVariableDeclarations::eInitial;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("inherit")) {
type = CSSVariableDeclarations::eInherit;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("unset")) {
type = CSSVariableDeclarations::eUnset;
}
}
if (type != CSSVariableDeclarations::eTokenStream) {
if (!GetToken(true)) {
// Variable value was 'initial' or 'inherit' followed by EOF.
MOZ_ASSERT(!BackslashDropped());
*aType = type;
*aDropBackslash = false;
AppendImpliedEOFCharacters(aImpliedCharacters);
return true;
}
UngetToken();
if (mToken.mType == eCSSToken_Symbol &&
(mToken.mSymbol == '!' ||
mToken.mSymbol == ')' ||
mToken.mSymbol == ';' ||
mToken.mSymbol == ']' ||
mToken.mSymbol == '}')) {
// Variable value was 'initial' or 'inherit' followed by the end
// of the declaration.
MOZ_ASSERT(!BackslashDropped());
*aType = type;
*aDropBackslash = false;
return true;
}
}
do {
switch (mToken.mType) {
case eCSSToken_Symbol:
if (mToken.mSymbol == '(') {
stack.AppendElement(')');
} else if (mToken.mSymbol == '[') {
stack.AppendElement(']');
} else if (mToken.mSymbol == '{') {
stack.AppendElement('}');
} else if (mToken.mSymbol == ';' ||
mToken.mSymbol == '!') {
if (stack.IsEmpty()) {
UngetToken();
MOZ_ASSERT(!BackslashDropped());
*aType = CSSVariableDeclarations::eTokenStream;
*aDropBackslash = false;
return true;
} else if (!references.IsEmpty() &&
references.LastElement() == stack.Length() - 1) {
REPORT_UNEXPECTED_TOKEN(PEInvalidVariableTokenFallback);
SkipUntilAllOf(stack);
return false;
}
} else if (mToken.mSymbol == ')' ||
mToken.mSymbol == ']' ||
mToken.mSymbol == '}') {
for (;;) {
if (stack.IsEmpty()) {
UngetToken();
MOZ_ASSERT(!BackslashDropped());
*aType = CSSVariableDeclarations::eTokenStream;
*aDropBackslash = false;
return true;
}
char16_t c = stack.LastElement();
stack.TruncateLength(stack.Length() - 1);
if (!references.IsEmpty() &&
references.LastElement() == stack.Length()) {
references.TruncateLength(references.Length() - 1);
}
if (mToken.mSymbol == c) {
break;
}
}
}
break;
case eCSSToken_Function:
if (mToken.mIdent.LowerCaseEqualsLiteral("var")) {
if (!GetToken(true)) {
// EOF directly after "var(".
REPORT_UNEXPECTED_EOF(PEExpectedVariableNameEOF);
return false;
}
if (mToken.mType != eCSSToken_Ident ||
!nsCSSProps::IsCustomPropertyName(mToken.mIdent)) {
// There must be an identifier directly after the "var(" and
// it must be a custom property name.
UngetToken();
REPORT_UNEXPECTED_TOKEN(PEExpectedVariableName);
SkipUntil(')');
SkipUntilAllOf(stack);
return false;
}
if (aFunc) {
MOZ_ASSERT(Substring(mToken.mIdent, 0,
CSS_CUSTOM_NAME_PREFIX_LENGTH).
EqualsLiteral("--"));
// remove '--'
const nsDependentSubstring varName =
Substring(mToken.mIdent, CSS_CUSTOM_NAME_PREFIX_LENGTH);
aFunc(varName, aData);
}
if (!GetToken(true)) {
// EOF right after "var(<ident>".
stack.AppendElement(')');
} else if (mToken.IsSymbol(',')) {
// Variable reference with fallback.
if (!GetToken(false) || mToken.IsSymbol(')')) {
// Comma must be followed by at least one fallback token.
REPORT_UNEXPECTED(PEExpectedVariableFallback);
SkipUntilAllOf(stack);
return false;
}
UngetToken();
references.AppendElement(stack.Length());
stack.AppendElement(')');
} else if (mToken.IsSymbol(')')) {
// Correctly closed variable reference.
} else {
// Malformed variable reference.
REPORT_UNEXPECTED_TOKEN(PEExpectedVariableCommaOrCloseParen);
SkipUntil(')');
SkipUntilAllOf(stack);
return false;
}
} else {
stack.AppendElement(')');
}
break;
case eCSSToken_Bad_String:
SkipUntilAllOf(stack);
return false;
case eCSSToken_Bad_URL:
SkipUntil(')');
SkipUntilAllOf(stack);
return false;
default:
break;
}
} while (GetToken(true));
// Append any implied closing characters.
*aDropBackslash = BackslashDropped();
AppendImpliedEOFCharacters(aImpliedCharacters);
uint32_t i = stack.Length();
while (i--) {
aImpliedCharacters.Append(stack[i]);
}
*aType = type;
return true;
}
bool
CSSParserImpl::IsValueValidForProperty(const nsCSSPropertyID aPropID,
const nsAString& aPropValue)
{
mData.AssertInitialState();
mTempData.AssertInitialState();
nsCSSScanner scanner(aPropValue, 0);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, nullptr);
InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
// We normally would need to pass in a sheet principal to InitScanner,
// because we might parse a URL value. However, we will never use the
// parsed nsCSSValue (and so whether we have a sheet principal or not
// doesn't really matter), so to avoid failing the assertion in
// SetValueToURL, we set mSheetPrincipalRequired to false to declare
// that it's safe to skip the assertion.
AutoRestore<bool> autoRestore(mSheetPrincipalRequired);
mSheetPrincipalRequired = false;
nsAutoSuppressErrors suppressErrors(this);
mSection = eCSSSection_General;
// Check for unknown properties
if (eCSSProperty_UNKNOWN == aPropID) {
ReleaseScanner();
return false;
}
// Check that the property and value parse successfully
bool parsedOK = ParseProperty(aPropID);
// Check for priority
parsedOK = parsedOK && ParsePriority() != ePriority_Error;
// We should now be at EOF
parsedOK = parsedOK && !GetToken(true);
mTempData.ClearProperty(aPropID);
mTempData.AssertInitialState();
mData.AssertInitialState();
CLEAR_ERROR();
ReleaseScanner();
return parsedOK;
}
} // namespace
// Recycling of parser implementation objects
static CSSParserImpl* gFreeList = nullptr;
nsCSSParser::nsCSSParser(mozilla::css::Loader* aLoader,
CSSStyleSheet* aSheet)
{
CSSParserImpl *impl = gFreeList;
if (impl) {
gFreeList = impl->mNextFree;
impl->mNextFree = nullptr;
} else {
impl = new CSSParserImpl();
}
if (aLoader) {
impl->SetChildLoader(aLoader);
impl->SetQuirkMode(aLoader->GetCompatibilityMode() ==
eCompatibility_NavQuirks);
}
if (aSheet) {
impl->SetStyleSheet(aSheet);
}
mImpl = static_cast<void*>(impl);
}
nsCSSParser::~nsCSSParser()
{
CSSParserImpl *impl = static_cast<CSSParserImpl*>(mImpl);
impl->Reset();
impl->mNextFree = gFreeList;
gFreeList = impl;
}
/* static */ void
nsCSSParser::Shutdown()
{
CSSParserImpl *tofree = gFreeList;
CSSParserImpl *next;
while (tofree)
{
next = tofree->mNextFree;
delete tofree;
tofree = next;
}
}
// Wrapper methods
nsresult
nsCSSParser::ParseSheet(const nsAString& aInput,
nsIURI* aSheetURI,
nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
uint32_t aLineNumber,
css::LoaderReusableStyleSheets* aReusableSheets)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseSheet(aInput, aSheetURI, aBaseURI, aSheetPrincipal, aLineNumber,
aReusableSheets);
}
already_AddRefed<css::Declaration>
nsCSSParser::ParseStyleAttribute(const nsAString& aAttributeValue,
nsIURI* aDocURI,
nsIURI* aBaseURI,
nsIPrincipal* aNodePrincipal)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseStyleAttribute(aAttributeValue, aDocURI, aBaseURI, aNodePrincipal);
}
nsresult
nsCSSParser::ParseDeclarations(const nsAString& aBuffer,
nsIURI* aSheetURI,
nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
css::Declaration* aDeclaration,
bool* aChanged)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseDeclarations(aBuffer, aSheetURI, aBaseURI, aSheetPrincipal,
aDeclaration, aChanged);
}
nsresult
nsCSSParser::ParseRule(const nsAString& aRule,
nsIURI* aSheetURI,
nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
css::Rule** aResult)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseRule(aRule, aSheetURI, aBaseURI, aSheetPrincipal, aResult);
}
void
nsCSSParser::ParseProperty(const nsCSSPropertyID aPropID,
const nsAString& aPropValue,
nsIURI* aSheetURI,
nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
css::Declaration* aDeclaration,
bool* aChanged,
bool aIsImportant,
bool aIsSVGMode)
{
static_cast<CSSParserImpl*>(mImpl)->
ParseProperty(aPropID, aPropValue, aSheetURI, aBaseURI,
aSheetPrincipal, aDeclaration, aChanged,
aIsImportant, aIsSVGMode);
}
void
nsCSSParser::ParseLonghandProperty(const nsCSSPropertyID aPropID,
const nsAString& aPropValue,
nsIURI* aSheetURI,
nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
nsCSSValue& aResult)
{
static_cast<CSSParserImpl*>(mImpl)->
ParseLonghandProperty(aPropID, aPropValue, aSheetURI, aBaseURI,
aSheetPrincipal, aResult);
}
bool
nsCSSParser::ParseTransformProperty(const nsAString& aPropValue,
bool aDisallowRelativeValues,
nsCSSValue& aResult)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseTransformProperty(aPropValue, aDisallowRelativeValues, aResult);
}
void
nsCSSParser::ParseVariable(const nsAString& aVariableName,
const nsAString& aPropValue,
nsIURI* aSheetURI,
nsIURI* aBaseURI,
nsIPrincipal* aSheetPrincipal,
css::Declaration* aDeclaration,
bool* aChanged,
bool aIsImportant)
{
static_cast<CSSParserImpl*>(mImpl)->
ParseVariable(aVariableName, aPropValue, aSheetURI, aBaseURI,
aSheetPrincipal, aDeclaration, aChanged, aIsImportant);
}
void
nsCSSParser::ParseMediaList(const nsAString& aBuffer,
nsIURI* aURI,
uint32_t aLineNumber,
nsMediaList* aMediaList,
dom::CallerType aCallerType)
{
static_cast<CSSParserImpl*>(mImpl)->
ParseMediaList(aBuffer, aURI, aLineNumber, aMediaList, aCallerType);
}
bool
nsCSSParser::ParseSourceSizeList(const nsAString& aBuffer,
nsIURI* aURI,
uint32_t aLineNumber,
InfallibleTArray< nsAutoPtr<nsMediaQuery> >& aQueries,
InfallibleTArray<nsCSSValue>& aValues)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseSourceSizeList(aBuffer, aURI, aLineNumber, aQueries, aValues);
}
bool
nsCSSParser::ParseFontFamilyListString(const nsAString& aBuffer,
nsIURI* aURI,
uint32_t aLineNumber,
nsCSSValue& aValue)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseFontFamilyListString(aBuffer, aURI, aLineNumber, aValue);
}
bool
nsCSSParser::ParseColorString(const nsAString& aBuffer,
nsIURI* aURI,
uint32_t aLineNumber,
nsCSSValue& aValue,
bool aSuppressErrors /* false */)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseColorString(aBuffer, aURI, aLineNumber, aValue, aSuppressErrors);
}
bool
nsCSSParser::ParseMarginString(const nsAString& aBuffer,
nsIURI* aURI,
uint32_t aLineNumber,
nsCSSValue& aValue,
bool aSuppressErrors /* false */)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseMarginString(aBuffer, aURI, aLineNumber, aValue, aSuppressErrors);
}
nsresult
nsCSSParser::ParseSelectorString(const nsAString& aSelectorString,
nsIURI* aURI,
uint32_t aLineNumber,
nsCSSSelectorList** aSelectorList)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseSelectorString(aSelectorString, aURI, aLineNumber, aSelectorList);
}
already_AddRefed<nsCSSKeyframeRule>
nsCSSParser::ParseKeyframeRule(const nsAString& aBuffer,
nsIURI* aURI,
uint32_t aLineNumber)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseKeyframeRule(aBuffer, aURI, aLineNumber);
}
bool
nsCSSParser::ParseKeyframeSelectorString(const nsAString& aSelectorString,
nsIURI* aURI,
uint32_t aLineNumber,
InfallibleTArray<float>& aSelectorList)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseKeyframeSelectorString(aSelectorString, aURI, aLineNumber,
aSelectorList);
}
bool
nsCSSParser::EvaluateSupportsDeclaration(const nsAString& aProperty,
const nsAString& aValue,
nsIURI* aDocURL,
nsIURI* aBaseURL,
nsIPrincipal* aDocPrincipal)
{
return static_cast<CSSParserImpl*>(mImpl)->
EvaluateSupportsDeclaration(aProperty, aValue, aDocURL, aBaseURL,
aDocPrincipal);
}
bool
nsCSSParser::EvaluateSupportsCondition(const nsAString& aCondition,
nsIURI* aDocURL,
nsIURI* aBaseURL,
nsIPrincipal* aDocPrincipal,
SupportsParsingSettings aSettings)
{
return static_cast<CSSParserImpl*>(mImpl)->
EvaluateSupportsCondition(aCondition, aDocURL, aBaseURL,
aDocPrincipal, aSettings);
}
bool
nsCSSParser::EnumerateVariableReferences(const nsAString& aPropertyValue,
VariableEnumFunc aFunc,
void* aData)
{
return static_cast<CSSParserImpl*>(mImpl)->
EnumerateVariableReferences(aPropertyValue, aFunc, aData);
}
bool
nsCSSParser::ResolveVariableValue(const nsAString& aPropertyValue,
const CSSVariableValues* aVariables,
nsString& aResult,
nsCSSTokenSerializationType& aFirstToken,
nsCSSTokenSerializationType& aLastToken)
{
return static_cast<CSSParserImpl*>(mImpl)->
ResolveVariableValue(aPropertyValue, aVariables,
aResult, aFirstToken, aLastToken);
}
void
nsCSSParser::ParsePropertyWithVariableReferences(
nsCSSPropertyID aPropertyID,
nsCSSPropertyID aShorthandPropertyID,
const nsAString& aValue,
const CSSVariableValues* aVariables,
nsRuleData* aRuleData,
nsIURI* aDocURL,
nsIURI* aBaseURL,
nsIPrincipal* aDocPrincipal,
CSSStyleSheet* aSheet,
uint32_t aLineNumber,
uint32_t aLineOffset)
{
static_cast<CSSParserImpl*>(mImpl)->
ParsePropertyWithVariableReferences(aPropertyID, aShorthandPropertyID,
aValue, aVariables, aRuleData, aDocURL,
aBaseURL, aDocPrincipal, aSheet,
aLineNumber, aLineOffset);
}
already_AddRefed<nsAtom>
nsCSSParser::ParseCounterStyleName(const nsAString& aBuffer, nsIURI* aURL)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseCounterStyleName(aBuffer, aURL);
}
bool
nsCSSParser::ParseCounterDescriptor(nsCSSCounterDesc aDescID,
const nsAString& aBuffer,
nsIURI* aSheetURL,
nsIURI* aBaseURL,
nsIPrincipal* aSheetPrincipal,
nsCSSValue& aValue)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseCounterDescriptor(aDescID, aBuffer,
aSheetURL, aBaseURL, aSheetPrincipal, aValue);
}
bool
nsCSSParser::ParseFontFaceDescriptor(nsCSSFontDesc aDescID,
const nsAString& aBuffer,
nsIURI* aSheetURL,
nsIURI* aBaseURL,
nsIPrincipal* aSheetPrincipal,
nsCSSValue& aValue)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseFontFaceDescriptor(aDescID, aBuffer,
aSheetURL, aBaseURL, aSheetPrincipal, aValue);
}
bool
nsCSSParser::IsValueValidForProperty(const nsCSSPropertyID aPropID,
const nsAString& aPropValue)
{
return static_cast<CSSParserImpl*>(mImpl)->
IsValueValidForProperty(aPropID, aPropValue);
}