mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 05:41:12 +00:00
0ab64cae78
Command used: find -E ./* -iregex '.*(\.cpp|\.h)' | xargs egrep -l '.*_moz_column.*' | xargs sed -i '' 's/_moz_column/column/g' MozReview-Commit-ID: CJbSfDfhmle --HG-- extra : rebase_source : f3989585c0f5ac98ebbcf7c9a729b4c89c5ddeab
18080 lines
590 KiB
C++
18080 lines
590 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=2 et sw=2 tw=78: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/* parsing of CSS stylesheets, based on a token stream from the CSS scanner */
|
|
|
|
#include "mozilla/ArrayUtils.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/Move.h"
|
|
#include "mozilla/MathAlgorithms.h"
|
|
#include "mozilla/TypedEnumBits.h"
|
|
|
|
#include <algorithm> // for std::stable_sort
|
|
#include <limits> // for std::numeric_limits
|
|
|
|
#include "nsCSSParser.h"
|
|
#include "nsAlgorithm.h"
|
|
#include "nsCSSProps.h"
|
|
#include "nsCSSKeywords.h"
|
|
#include "nsCSSScanner.h"
|
|
#include "mozilla/css/ErrorReporter.h"
|
|
#include "mozilla/css/Loader.h"
|
|
#include "mozilla/css/StyleRule.h"
|
|
#include "mozilla/css/ImportRule.h"
|
|
#include "nsCSSRules.h"
|
|
#include "mozilla/css/NameSpaceRule.h"
|
|
#include "nsTArray.h"
|
|
#include "mozilla/CSSStyleSheet.h"
|
|
#include "mozilla/css/Declaration.h"
|
|
#include "nsStyleConsts.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsString.h"
|
|
#include "nsIAtom.h"
|
|
#include "nsColor.h"
|
|
#include "nsCSSPseudoClasses.h"
|
|
#include "nsCSSPseudoElements.h"
|
|
#include "nsCSSAnonBoxes.h"
|
|
#include "nsNameSpaceManager.h"
|
|
#include "nsXMLNameSpaceMap.h"
|
|
#include "nsError.h"
|
|
#include "nsIMediaList.h"
|
|
#include "nsStyleUtil.h"
|
|
#include "nsIPrincipal.h"
|
|
#include "nsICSSUnprefixingService.h"
|
|
#include "mozilla/Sprintf.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsAutoPtr.h"
|
|
#include "CSSCalc.h"
|
|
#include "nsMediaFeatures.h"
|
|
#include "nsLayoutUtils.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "nsRuleData.h"
|
|
#include "mozilla/CSSVariableValues.h"
|
|
#include "mozilla/dom/AnimationEffectReadOnlyBinding.h"
|
|
#include "mozilla/dom/URL.h"
|
|
#include "gfxFontFamilyList.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::css;
|
|
|
|
typedef nsCSSProps::KTableEntry KTableEntry;
|
|
|
|
// pref-backed bool values (hooked up in nsCSSParser::Startup)
|
|
static bool sOpentypeSVGEnabled;
|
|
static bool sWebkitPrefixedAliasesEnabled;
|
|
static bool sWebkitDevicePixelRatioEnabled;
|
|
static bool sUnprefixingServiceEnabled;
|
|
#ifdef NIGHTLY_BUILD
|
|
static bool sUnprefixingServiceGloballyWhitelisted;
|
|
#endif
|
|
static bool sMozGradientsEnabled;
|
|
static bool sControlCharVisibility;
|
|
|
|
const uint32_t
|
|
nsCSSProps::kParserVariantTable[eCSSProperty_COUNT_no_shorthands] = {
|
|
#define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \
|
|
stylestruct_, stylestructoffset_, animtype_) \
|
|
parsevariant_,
|
|
#define CSS_PROP_LIST_INCLUDE_LOGICAL
|
|
#include "nsCSSPropList.h"
|
|
#undef CSS_PROP_LIST_INCLUDE_LOGICAL
|
|
#undef CSS_PROP
|
|
};
|
|
|
|
// Maximum number of repetitions for the repeat() function
|
|
// in the grid-template-rows and grid-template-columns properties,
|
|
// to limit high memory usage from small stylesheets.
|
|
// Must be a positive integer. Should be large-ish.
|
|
#define GRID_TEMPLATE_MAX_REPETITIONS 10000
|
|
|
|
// End-of-array marker for mask arguments to ParseBitmaskValues
|
|
#define MASK_END_VALUE (-1)
|
|
|
|
enum class CSSParseResult : int32_t {
|
|
// Parsed something successfully:
|
|
Ok,
|
|
// Did not find what we were looking for, but did not consume any token:
|
|
NotFound,
|
|
// Unexpected token or token value, too late for UngetToken() to be enough:
|
|
Error
|
|
};
|
|
|
|
enum class GridTrackSizeFlags {
|
|
eDefaultTrackSize = 0x0,
|
|
eFixedTrackSize = 0x1, // parse a <fixed-size> instead of <track-size>
|
|
};
|
|
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(GridTrackSizeFlags)
|
|
|
|
enum class GridTrackListFlags {
|
|
eDefaultTrackList = 0x0, // parse a <track-list>
|
|
eExplicitTrackList = 0x1, // parse an <explicit-track-list> instead
|
|
};
|
|
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(GridTrackListFlags)
|
|
|
|
namespace {
|
|
|
|
// Rule processing function
|
|
typedef void (* RuleAppendFunc) (css::Rule* aRule, void* aData);
|
|
static void AssignRuleToPointer(css::Rule* aRule, void* aPointer);
|
|
static void AppendRuleToSheet(css::Rule* aRule, void* aParser);
|
|
|
|
struct CSSParserInputState {
|
|
nsCSSScannerPosition mPosition;
|
|
nsCSSToken mToken;
|
|
bool mHavePushBack;
|
|
};
|
|
|
|
static_assert(css::eAuthorSheetFeatures == 0 &&
|
|
css::eUserSheetFeatures == 1 &&
|
|
css::eAgentSheetFeatures == 2,
|
|
"sheet parsing mode constants won't fit "
|
|
"in CSSParserImpl::mParsingMode");
|
|
|
|
// Your basic top-down recursive descent style parser
|
|
// The exposed methods and members of this class are precisely those
|
|
// needed by nsCSSParser, far below.
|
|
class CSSParserImpl {
|
|
public:
|
|
CSSParserImpl();
|
|
~CSSParserImpl();
|
|
|
|
nsresult SetStyleSheet(CSSStyleSheet* aSheet);
|
|
|
|
nsIDocument* GetDocument();
|
|
|
|
nsresult SetQuirkMode(bool aQuirkMode);
|
|
|
|
nsresult SetChildLoader(mozilla::css::Loader* aChildLoader);
|
|
|
|
// Clears everything set by the above Set*() functions.
|
|
void Reset();
|
|
|
|
nsresult ParseSheet(const nsAString& aInput,
|
|
nsIURI* aSheetURI,
|
|
nsIURI* aBaseURI,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
uint32_t aLineNumber,
|
|
css::LoaderReusableStyleSheets* aReusableSheets);
|
|
|
|
already_AddRefed<css::Declaration>
|
|
ParseStyleAttribute(const nsAString& aAttributeValue,
|
|
nsIURI* aDocURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aNodePrincipal);
|
|
|
|
nsresult ParseDeclarations(const nsAString& aBuffer,
|
|
nsIURI* aSheetURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
css::Declaration* aDeclaration,
|
|
bool* aChanged);
|
|
|
|
nsresult ParseRule(const nsAString& aRule,
|
|
nsIURI* aSheetURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
css::Rule** aResult);
|
|
|
|
void ParseProperty(const nsCSSPropertyID aPropID,
|
|
const nsAString& aPropValue,
|
|
nsIURI* aSheetURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
css::Declaration* aDeclaration,
|
|
bool* aChanged,
|
|
bool aIsImportant,
|
|
bool aIsSVGMode);
|
|
void ParseLonghandProperty(const nsCSSPropertyID aPropID,
|
|
const nsAString& aPropValue,
|
|
nsIURI* aSheetURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
nsCSSValue& aValue);
|
|
|
|
bool ParseTransformProperty(const nsAString& aPropValue,
|
|
bool aDisallowRelativeValues,
|
|
nsCSSValue& aResult);
|
|
|
|
void ParseMediaList(const nsSubstring& aBuffer,
|
|
nsIURI* aURL, // for error reporting
|
|
uint32_t aLineNumber, // for error reporting
|
|
nsMediaList* aMediaList,
|
|
bool aHTMLMode);
|
|
|
|
bool ParseSourceSizeList(const nsAString& aBuffer,
|
|
nsIURI* aURI, // for error reporting
|
|
uint32_t aLineNumber, // for error reporting
|
|
InfallibleTArray< nsAutoPtr<nsMediaQuery> >& aQueries,
|
|
InfallibleTArray<nsCSSValue>& aValues,
|
|
bool aHTMLMode);
|
|
|
|
void ParseVariable(const nsAString& aVariableName,
|
|
const nsAString& aPropValue,
|
|
nsIURI* aSheetURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
css::Declaration* aDeclaration,
|
|
bool* aChanged,
|
|
bool aIsImportant);
|
|
|
|
bool ParseFontFamilyListString(const nsSubstring& aBuffer,
|
|
nsIURI* aURL, // for error reporting
|
|
uint32_t aLineNumber, // for error reporting
|
|
nsCSSValue& aValue);
|
|
|
|
bool ParseColorString(const nsSubstring& aBuffer,
|
|
nsIURI* aURL, // for error reporting
|
|
uint32_t aLineNumber, // for error reporting
|
|
nsCSSValue& aValue,
|
|
bool aSuppressErrors /* false */);
|
|
|
|
nsresult ParseSelectorString(const nsSubstring& aSelectorString,
|
|
nsIURI* aURL, // for error reporting
|
|
uint32_t aLineNumber, // for error reporting
|
|
nsCSSSelectorList **aSelectorList);
|
|
|
|
already_AddRefed<nsCSSKeyframeRule>
|
|
ParseKeyframeRule(const nsSubstring& aBuffer,
|
|
nsIURI* aURL,
|
|
uint32_t aLineNumber);
|
|
|
|
bool ParseKeyframeSelectorString(const nsSubstring& aSelectorString,
|
|
nsIURI* aURL, // for error reporting
|
|
uint32_t aLineNumber, // for error reporting
|
|
InfallibleTArray<float>& aSelectorList);
|
|
|
|
bool EvaluateSupportsDeclaration(const nsAString& aProperty,
|
|
const nsAString& aValue,
|
|
nsIURI* aDocURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aDocPrincipal);
|
|
|
|
bool EvaluateSupportsCondition(const nsAString& aCondition,
|
|
nsIURI* aDocURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aDocPrincipal);
|
|
|
|
bool ParseCounterStyleName(const nsAString& aBuffer,
|
|
nsIURI* aURL,
|
|
nsAString& aName);
|
|
|
|
bool ParseCounterDescriptor(nsCSSCounterDesc aDescID,
|
|
const nsAString& aBuffer,
|
|
nsIURI* aSheetURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
nsCSSValue& aValue);
|
|
|
|
bool ParseFontFaceDescriptor(nsCSSFontDesc aDescID,
|
|
const nsAString& aBuffer,
|
|
nsIURI* aSheetURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
nsCSSValue& aValue);
|
|
|
|
bool IsValueValidForProperty(const nsCSSPropertyID aPropID,
|
|
const nsAString& aPropValue);
|
|
|
|
typedef nsCSSParser::VariableEnumFunc VariableEnumFunc;
|
|
|
|
/**
|
|
* Parses a CSS token stream value and invokes a callback function for each
|
|
* variable reference that is encountered.
|
|
*
|
|
* @param aPropertyValue The CSS token stream value.
|
|
* @param aFunc The callback function to invoke; its parameters are the
|
|
* variable name found and the aData argument passed in to this function.
|
|
* @param aData User data to pass in to the callback.
|
|
* @return Whether aPropertyValue could be parsed as a valid CSS token stream
|
|
* value (e.g., without syntactic errors in variable references).
|
|
*/
|
|
bool EnumerateVariableReferences(const nsAString& aPropertyValue,
|
|
VariableEnumFunc aFunc,
|
|
void* aData);
|
|
|
|
/**
|
|
* Parses aPropertyValue as a CSS token stream value and resolves any
|
|
* variable references using the variables in aVariables.
|
|
*
|
|
* @param aPropertyValue The CSS token stream value.
|
|
* @param aVariables The set of variable values to use when resolving variable
|
|
* references.
|
|
* @param aResult Out parameter that gets the resolved value.
|
|
* @param aFirstToken Out parameter that gets the type of the first token in
|
|
* aResult.
|
|
* @param aLastToken Out parameter that gets the type of the last token in
|
|
* aResult.
|
|
* @return Whether aResult could be parsed successfully and variable reference
|
|
* substitution succeeded.
|
|
*/
|
|
bool ResolveVariableValue(const nsAString& aPropertyValue,
|
|
const CSSVariableValues* aVariables,
|
|
nsString& aResult,
|
|
nsCSSTokenSerializationType& aFirstToken,
|
|
nsCSSTokenSerializationType& aLastToken);
|
|
|
|
/**
|
|
* Parses a string as a CSS token stream value for particular property,
|
|
* resolving any variable references. The parsed property value is stored
|
|
* in the specified nsRuleData object. If aShorthandPropertyID has a value
|
|
* other than eCSSProperty_UNKNOWN, this is the property that will be parsed;
|
|
* otherwise, aPropertyID will be parsed. Either way, only aPropertyID,
|
|
* a longhand property, will be copied over to the rule data.
|
|
*
|
|
* If the property cannot be parsed, it will be treated as if 'initial' or
|
|
* 'inherit' were specified, for non-inherited and inherited properties
|
|
* respectively.
|
|
*
|
|
* @param aPropertyID The ID of the longhand property whose value is to be
|
|
* copied to the rule data.
|
|
* @param aShorthandPropertyID The ID of the shorthand property to be parsed.
|
|
* If a longhand property is to be parsed, aPropertyID is that property,
|
|
* and aShorthandPropertyID must be eCSSProperty_UNKNOWN.
|
|
* @param aValue The CSS token stream value.
|
|
* @param aVariables The set of variable values to use when resolving variable
|
|
* references.
|
|
* @param aRuleData The rule data object into which parsed property value for
|
|
* aPropertyID will be stored.
|
|
*/
|
|
void ParsePropertyWithVariableReferences(nsCSSPropertyID aPropertyID,
|
|
nsCSSPropertyID aShorthandPropertyID,
|
|
const nsAString& aValue,
|
|
const CSSVariableValues* aVariables,
|
|
nsRuleData* aRuleData,
|
|
nsIURI* aDocURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aDocPrincipal,
|
|
CSSStyleSheet* aSheet,
|
|
uint32_t aLineNumber,
|
|
uint32_t aLineOffset);
|
|
|
|
bool AgentRulesEnabled() const {
|
|
return mParsingMode == css::eAgentSheetFeatures;
|
|
}
|
|
bool ChromeRulesEnabled() const {
|
|
return mIsChrome;
|
|
}
|
|
bool UserRulesEnabled() const {
|
|
return mParsingMode == css::eAgentSheetFeatures ||
|
|
mParsingMode == css::eUserSheetFeatures;
|
|
}
|
|
|
|
CSSEnabledState EnabledState() const {
|
|
static_assert(int(CSSEnabledState::eForAllContent) == 0,
|
|
"CSSEnabledState::eForAllContent should be zero for "
|
|
"this bitfield to work");
|
|
CSSEnabledState enabledState = CSSEnabledState::eForAllContent;
|
|
if (AgentRulesEnabled()) {
|
|
enabledState |= CSSEnabledState::eInUASheets;
|
|
}
|
|
if (mIsChrome) {
|
|
enabledState |= CSSEnabledState::eInChrome;
|
|
}
|
|
return enabledState;
|
|
}
|
|
|
|
nsCSSPropertyID LookupEnabledProperty(const nsAString& aProperty) {
|
|
return nsCSSProps::LookupProperty(aProperty, EnabledState());
|
|
}
|
|
|
|
protected:
|
|
class nsAutoParseCompoundProperty;
|
|
friend class nsAutoParseCompoundProperty;
|
|
|
|
class nsAutoFailingSupportsRule;
|
|
friend class nsAutoFailingSupportsRule;
|
|
|
|
class nsAutoSuppressErrors;
|
|
friend class nsAutoSuppressErrors;
|
|
|
|
void AppendRule(css::Rule* aRule);
|
|
friend void AppendRuleToSheet(css::Rule*, void*); // calls AppendRule
|
|
|
|
/**
|
|
* This helper class automatically calls SetParsingCompoundProperty in its
|
|
* constructor and takes care of resetting it to false in its destructor.
|
|
*/
|
|
class nsAutoParseCompoundProperty {
|
|
public:
|
|
explicit nsAutoParseCompoundProperty(CSSParserImpl* aParser) : mParser(aParser)
|
|
{
|
|
NS_ASSERTION(!aParser->IsParsingCompoundProperty(),
|
|
"already parsing compound property");
|
|
NS_ASSERTION(aParser, "Null parser?");
|
|
aParser->SetParsingCompoundProperty(true);
|
|
}
|
|
|
|
~nsAutoParseCompoundProperty()
|
|
{
|
|
mParser->SetParsingCompoundProperty(false);
|
|
}
|
|
private:
|
|
CSSParserImpl* mParser;
|
|
};
|
|
|
|
/**
|
|
* This helper class conditionally sets mInFailingSupportsRule to
|
|
* true if aCondition = false, and resets it to its original value in its
|
|
* destructor. If we are already somewhere within a failing @supports
|
|
* rule, passing in aCondition = true does not change mInFailingSupportsRule.
|
|
*/
|
|
class nsAutoFailingSupportsRule {
|
|
public:
|
|
nsAutoFailingSupportsRule(CSSParserImpl* aParser,
|
|
bool aCondition)
|
|
: mParser(aParser),
|
|
mOriginalValue(aParser->mInFailingSupportsRule)
|
|
{
|
|
if (!aCondition) {
|
|
mParser->mInFailingSupportsRule = true;
|
|
}
|
|
}
|
|
|
|
~nsAutoFailingSupportsRule()
|
|
{
|
|
mParser->mInFailingSupportsRule = mOriginalValue;
|
|
}
|
|
|
|
private:
|
|
CSSParserImpl* mParser;
|
|
bool mOriginalValue;
|
|
};
|
|
|
|
/**
|
|
* Auto class to set aParser->mSuppressErrors to the specified value
|
|
* and restore it to its original value later.
|
|
*/
|
|
class nsAutoSuppressErrors {
|
|
public:
|
|
explicit nsAutoSuppressErrors(CSSParserImpl* aParser,
|
|
bool aSuppressErrors = true)
|
|
: mParser(aParser),
|
|
mOriginalValue(aParser->mSuppressErrors)
|
|
{
|
|
mParser->mSuppressErrors = aSuppressErrors;
|
|
}
|
|
|
|
~nsAutoSuppressErrors()
|
|
{
|
|
mParser->mSuppressErrors = mOriginalValue;
|
|
}
|
|
|
|
private:
|
|
CSSParserImpl* mParser;
|
|
bool mOriginalValue;
|
|
};
|
|
|
|
/**
|
|
* RAII class to set aParser->mInSupportsCondition to true and restore it
|
|
* to false later.
|
|
*/
|
|
class MOZ_RAII nsAutoInSupportsCondition
|
|
{
|
|
public:
|
|
explicit nsAutoInSupportsCondition(CSSParserImpl* aParser)
|
|
: mParser(aParser)
|
|
{
|
|
MOZ_ASSERT(!aParser->mInSupportsCondition,
|
|
"nsAutoInSupportsCondition is not designed to be used "
|
|
"re-entrantly");
|
|
mParser->mInSupportsCondition = true;
|
|
}
|
|
|
|
~nsAutoInSupportsCondition()
|
|
{
|
|
mParser->mInSupportsCondition = false;
|
|
}
|
|
|
|
private:
|
|
CSSParserImpl* const mParser;
|
|
};
|
|
|
|
// the caller must hold on to aString until parsing is done
|
|
void InitScanner(nsCSSScanner& aScanner,
|
|
css::ErrorReporter& aReporter,
|
|
nsIURI* aSheetURI, nsIURI* aBaseURI,
|
|
nsIPrincipal* aSheetPrincipal);
|
|
void ReleaseScanner(void);
|
|
|
|
/**
|
|
* This is a RAII class which behaves like an "AutoRestore<>" for our parser
|
|
* input state. When instantiated, this class saves the current parser input
|
|
* state (in a CSSParserInputState object), and it restores the parser to
|
|
* that state when destructed, unless "DoNotRestore()" has been called.
|
|
*/
|
|
class MOZ_RAII nsAutoCSSParserInputStateRestorer {
|
|
public:
|
|
explicit nsAutoCSSParserInputStateRestorer(CSSParserImpl* aParser
|
|
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
|
: mParser(aParser),
|
|
mShouldRestore(true)
|
|
{
|
|
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
|
mParser->SaveInputState(mSavedState);
|
|
}
|
|
|
|
void DoNotRestore()
|
|
{
|
|
mShouldRestore = false;
|
|
}
|
|
|
|
~nsAutoCSSParserInputStateRestorer()
|
|
{
|
|
if (mShouldRestore) {
|
|
mParser->RestoreSavedInputState(mSavedState);
|
|
}
|
|
}
|
|
|
|
private:
|
|
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
|
CSSParserImpl* mParser;
|
|
CSSParserInputState mSavedState;
|
|
bool mShouldRestore;
|
|
};
|
|
|
|
/**
|
|
* This is a RAII class which creates a temporary nsCSSScanner for the given
|
|
* string, and reconfigures aParser to use *that* scanner instead of its
|
|
* existing scanner, until we go out of scope. (This allows us to rewrite
|
|
* a portion of a stylesheet using a temporary string, and switch to parsing
|
|
* that rewritten section, and then resume parsing the original stylesheet.)
|
|
*
|
|
* aParser must have a non-null nsCSSScanner (which we'll be temporarily
|
|
* replacing) and ErrorReporter (which this class will co-opt for the
|
|
* temporary parser). While we're in scope, we also suppress error reporting,
|
|
* so it doesn't really matter which reporter we use. We suppress reporting
|
|
* because this class is only used with CSS that is synthesized & didn't
|
|
* come directly from an author, and it would be confusing if we reported
|
|
* syntax errors for CSS that an author didn't provide.
|
|
*
|
|
* XXXdholbert we could also change this & report errors, if needed. Might
|
|
* want to customize the error reporting somehow though.
|
|
*/
|
|
class MOZ_RAII nsAutoScannerChanger {
|
|
public:
|
|
nsAutoScannerChanger(CSSParserImpl* aParser,
|
|
const nsAString& aStringToScan
|
|
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
|
: mParser(aParser),
|
|
mOriginalScanner(aParser->mScanner),
|
|
mStringScanner(aStringToScan, 0),
|
|
mParserStateRestorer(aParser),
|
|
mErrorSuppresser(aParser)
|
|
{
|
|
MOZ_ASSERT(mOriginalScanner,
|
|
"Shouldn't use nsAutoScannerChanger unless we already "
|
|
"have a scanner");
|
|
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
|
|
|
// Set & setup the new scanner:
|
|
mParser->mScanner = &mStringScanner;
|
|
mStringScanner.SetErrorReporter(mParser->mReporter);
|
|
|
|
// We might've had push-back on our original scanner (and if we did,
|
|
// that fact is saved via mParserStateRestorer). But we don't have
|
|
// push-back in mStringScanner, so clear that flag.
|
|
mParser->mHavePushBack = false;
|
|
}
|
|
|
|
~nsAutoScannerChanger()
|
|
{
|
|
// Restore original scanner. All other cleanup is done by RAII members.
|
|
mParser->mScanner = mOriginalScanner;
|
|
}
|
|
|
|
private:
|
|
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
|
CSSParserImpl* mParser;
|
|
nsCSSScanner *mOriginalScanner;
|
|
nsCSSScanner mStringScanner;
|
|
nsAutoCSSParserInputStateRestorer mParserStateRestorer;
|
|
nsAutoSuppressErrors mErrorSuppresser;
|
|
};
|
|
|
|
|
|
bool IsSVGMode() const {
|
|
return mScanner->IsSVGMode();
|
|
}
|
|
|
|
/**
|
|
* Saves the current input state, which includes any currently pushed
|
|
* back token, and the current position of the scanner.
|
|
*/
|
|
void SaveInputState(CSSParserInputState& aState);
|
|
|
|
/**
|
|
* Restores the saved input state by pushing back any saved pushback
|
|
* token and calling RestoreSavedPosition on the scanner.
|
|
*/
|
|
void RestoreSavedInputState(const CSSParserInputState& aState);
|
|
|
|
bool GetToken(bool aSkipWS);
|
|
void UngetToken();
|
|
bool GetNextTokenLocation(bool aSkipWS, uint32_t *linenum, uint32_t *colnum);
|
|
void AssertNextTokenAt(uint32_t aLine, uint32_t aCol)
|
|
{
|
|
// Beware that this method will call GetToken/UngetToken (in
|
|
// GetNextTokenLocation) in DEBUG builds, but not in non-DEBUG builds.
|
|
DebugOnly<uint32_t> lineAfter, colAfter;
|
|
MOZ_ASSERT(GetNextTokenLocation(true, &lineAfter, &colAfter) &&
|
|
lineAfter == aLine && colAfter == aCol,
|
|
"shouldn't have consumed any tokens");
|
|
}
|
|
|
|
bool ExpectSymbol(char16_t aSymbol, bool aSkipWS);
|
|
bool ExpectEndProperty();
|
|
bool CheckEndProperty();
|
|
nsSubstring* NextIdent();
|
|
|
|
// returns true when the stop symbol is found, and false for EOF
|
|
bool SkipUntil(char16_t aStopSymbol);
|
|
void SkipUntilOneOf(const char16_t* aStopSymbolChars);
|
|
// For each character in aStopSymbolChars from the end of the array
|
|
// to the start, calls SkipUntil with that character.
|
|
typedef AutoTArray<char16_t, 16> StopSymbolCharStack;
|
|
void SkipUntilAllOf(const StopSymbolCharStack& aStopSymbolChars);
|
|
// returns true if the stop symbol or EOF is found, and false for an
|
|
// unexpected ')', ']' or '}'; this not safe to call outside variable
|
|
// resolution, as it doesn't handle mismatched content
|
|
bool SkipBalancedContentUntil(char16_t aStopSymbol);
|
|
|
|
void SkipRuleSet(bool aInsideBraces);
|
|
bool SkipAtRule(bool aInsideBlock);
|
|
bool SkipDeclaration(bool aCheckForBraces);
|
|
|
|
void PushGroup(css::GroupRule* aRule);
|
|
void PopGroup();
|
|
|
|
bool ParseRuleSet(RuleAppendFunc aAppendFunc, void* aProcessData,
|
|
bool aInsideBraces = false);
|
|
bool ParseAtRule(RuleAppendFunc aAppendFunc, void* aProcessData,
|
|
bool aInAtRule);
|
|
bool ParseCharsetRule(RuleAppendFunc aAppendFunc, void* aProcessData);
|
|
bool ParseImportRule(RuleAppendFunc aAppendFunc, void* aProcessData);
|
|
bool ParseURLOrString(nsString& aURL);
|
|
bool GatherMedia(nsMediaList* aMedia, bool aInAtRule);
|
|
|
|
enum eMediaQueryType { eMediaQueryNormal,
|
|
// Parsing an at rule
|
|
eMediaQueryAtRule,
|
|
// Attempt to consume a single media-condition and
|
|
// stop. Note that the spec defines "expression and/or
|
|
// expression" as one condition but "expression,
|
|
// expression" as two.
|
|
eMediaQuerySingleCondition };
|
|
bool ParseMediaQuery(eMediaQueryType aMode, nsMediaQuery **aQuery,
|
|
bool *aHitStop);
|
|
bool ParseMediaQueryExpression(nsMediaQuery* aQuery);
|
|
void ProcessImport(const nsString& aURLSpec,
|
|
nsMediaList* aMedia,
|
|
RuleAppendFunc aAppendFunc,
|
|
void* aProcessData,
|
|
uint32_t aLineNumber,
|
|
uint32_t aColumnNumber);
|
|
bool ParseGroupRule(css::GroupRule* aRule, RuleAppendFunc aAppendFunc,
|
|
void* aProcessData);
|
|
bool ParseMediaRule(RuleAppendFunc aAppendFunc, void* aProcessData);
|
|
bool ParseMozDocumentRule(RuleAppendFunc aAppendFunc, void* aProcessData);
|
|
bool ParseNameSpaceRule(RuleAppendFunc aAppendFunc, void* aProcessData);
|
|
void ProcessNameSpace(const nsString& aPrefix,
|
|
const nsString& aURLSpec, RuleAppendFunc aAppendFunc,
|
|
void* aProcessData,
|
|
uint32_t aLineNumber, uint32_t aColumnNumber);
|
|
|
|
bool ParseFontFaceRule(RuleAppendFunc aAppendFunc, void* aProcessData);
|
|
bool ParseFontFeatureValuesRule(RuleAppendFunc aAppendFunc,
|
|
void* aProcessData);
|
|
bool ParseFontFeatureValueSet(nsCSSFontFeatureValuesRule *aRule);
|
|
bool ParseFontDescriptor(nsCSSFontFaceRule* aRule);
|
|
bool ParseFontDescriptorValue(nsCSSFontDesc aDescID,
|
|
nsCSSValue& aValue);
|
|
|
|
bool ParsePageRule(RuleAppendFunc aAppendFunc, void* aProcessData);
|
|
bool ParseKeyframesRule(RuleAppendFunc aAppendFunc, void* aProcessData);
|
|
already_AddRefed<nsCSSKeyframeRule> ParseKeyframeRule();
|
|
bool ParseKeyframeSelectorList(InfallibleTArray<float>& aSelectorList);
|
|
|
|
bool ParseSupportsRule(RuleAppendFunc aAppendFunc, void* aProcessData);
|
|
bool ParseSupportsCondition(bool& aConditionMet);
|
|
bool ParseSupportsConditionNegation(bool& aConditionMet);
|
|
bool ParseSupportsConditionInParens(bool& aConditionMet);
|
|
bool ParseSupportsMozBoolPrefName(bool& aConditionMet);
|
|
bool ParseSupportsConditionInParensInsideParens(bool& aConditionMet);
|
|
bool ParseSupportsConditionTerms(bool& aConditionMet);
|
|
enum SupportsConditionTermOperator { eAnd, eOr };
|
|
bool ParseSupportsConditionTermsAfterOperator(
|
|
bool& aConditionMet,
|
|
SupportsConditionTermOperator aOperator);
|
|
|
|
bool ParseCounterStyleRule(RuleAppendFunc aAppendFunc, void* aProcessData);
|
|
bool ParseCounterStyleName(nsAString& aName, bool aForDefinition);
|
|
bool ParseCounterStyleNameValue(nsCSSValue& aValue);
|
|
bool ParseCounterDescriptor(nsCSSCounterStyleRule *aRule);
|
|
bool ParseCounterDescriptorValue(nsCSSCounterDesc aDescID,
|
|
nsCSSValue& aValue);
|
|
bool ParseCounterRange(nsCSSValuePair& aPair);
|
|
|
|
/**
|
|
* Parses the current input stream for a CSS token stream value and resolves
|
|
* any variable references using the variables in aVariables.
|
|
*
|
|
* @param aVariables The set of variable values to use when resolving variable
|
|
* references.
|
|
* @param aResult Out parameter that, if the function returns true, will be
|
|
* replaced with the resolved value.
|
|
* @return Whether aResult could be parsed successfully and variable reference
|
|
* substitution succeeded.
|
|
*/
|
|
bool ResolveValueWithVariableReferences(
|
|
const CSSVariableValues* aVariables,
|
|
nsString& aResult,
|
|
nsCSSTokenSerializationType& aResultFirstToken,
|
|
nsCSSTokenSerializationType& aResultLastToken);
|
|
// Helper function for ResolveValueWithVariableReferences.
|
|
bool ResolveValueWithVariableReferencesRec(
|
|
nsString& aResult,
|
|
nsCSSTokenSerializationType& aResultFirstToken,
|
|
nsCSSTokenSerializationType& aResultLastToken,
|
|
const CSSVariableValues* aVariables);
|
|
|
|
enum nsSelectorParsingStatus {
|
|
// we have parsed a selector and we saw a token that cannot be
|
|
// part of a selector:
|
|
eSelectorParsingStatus_Done,
|
|
// we should continue parsing the selector:
|
|
eSelectorParsingStatus_Continue,
|
|
// we saw an unexpected token or token value,
|
|
// or we saw end-of-file with an unfinished selector:
|
|
eSelectorParsingStatus_Error
|
|
};
|
|
nsSelectorParsingStatus ParseIDSelector(int32_t& aDataMask,
|
|
nsCSSSelector& aSelector);
|
|
|
|
nsSelectorParsingStatus ParseClassSelector(int32_t& aDataMask,
|
|
nsCSSSelector& aSelector);
|
|
|
|
// aPseudoElement and aPseudoElementArgs are the location where
|
|
// pseudo-elements (as opposed to pseudo-classes) are stored;
|
|
// pseudo-classes are stored on aSelector. aPseudoElement and
|
|
// aPseudoElementArgs must be non-null iff !aIsNegated.
|
|
nsSelectorParsingStatus ParsePseudoSelector(int32_t& aDataMask,
|
|
nsCSSSelector& aSelector,
|
|
bool aIsNegated,
|
|
nsIAtom** aPseudoElement,
|
|
nsAtomList** aPseudoElementArgs,
|
|
CSSPseudoElementType* aPseudoElementType);
|
|
|
|
nsSelectorParsingStatus ParseAttributeSelector(int32_t& aDataMask,
|
|
nsCSSSelector& aSelector);
|
|
|
|
nsSelectorParsingStatus ParseTypeOrUniversalSelector(int32_t& aDataMask,
|
|
nsCSSSelector& aSelector,
|
|
bool aIsNegated);
|
|
|
|
nsSelectorParsingStatus ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector,
|
|
CSSPseudoClassType aType);
|
|
|
|
nsSelectorParsingStatus ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
|
|
CSSPseudoClassType aType);
|
|
|
|
nsSelectorParsingStatus ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
|
|
CSSPseudoClassType aType);
|
|
|
|
nsSelectorParsingStatus ParseNegatedSimpleSelector(int32_t& aDataMask,
|
|
nsCSSSelector& aSelector);
|
|
|
|
// If aStopChar is non-zero, the selector list is done when we hit
|
|
// aStopChar. Otherwise, it's done when we hit EOF.
|
|
bool ParseSelectorList(nsCSSSelectorList*& aListHead,
|
|
char16_t aStopChar);
|
|
bool ParseSelectorGroup(nsCSSSelectorList*& aListHead);
|
|
bool ParseSelector(nsCSSSelectorList* aList, char16_t aPrevCombinator);
|
|
|
|
enum {
|
|
eParseDeclaration_InBraces = 1 << 0,
|
|
eParseDeclaration_AllowImportant = 1 << 1,
|
|
// The declaration we're parsing was generated by the CSSUnprefixingService:
|
|
eParseDeclaration_FromUnprefixingSvc = 1 << 2
|
|
};
|
|
enum nsCSSContextType {
|
|
eCSSContext_General,
|
|
eCSSContext_Page
|
|
};
|
|
|
|
already_AddRefed<css::Declaration>
|
|
ParseDeclarationBlock(uint32_t aFlags,
|
|
nsCSSContextType aContext = eCSSContext_General);
|
|
bool ParseDeclaration(css::Declaration* aDeclaration,
|
|
uint32_t aFlags,
|
|
bool aMustCallValueAppended,
|
|
bool* aChanged,
|
|
nsCSSContextType aContext = eCSSContext_General);
|
|
|
|
// A "prefix-aware" wrapper for nsCSSKeywords::LookupKeyword().
|
|
// Use this instead of LookupKeyword() if you might be parsing an unprefixed
|
|
// property (like "display") for which we emulate a vendor-prefixed value
|
|
// (like "-webkit-box").
|
|
nsCSSKeyword LookupKeywordPrefixAware(nsAString& aKeywordStr,
|
|
const KTableEntry aKeywordTable[]);
|
|
|
|
bool ShouldUseUnprefixingService() const;
|
|
bool ParsePropertyWithUnprefixingService(const nsAString& aPropertyName,
|
|
css::Declaration* aDeclaration,
|
|
uint32_t aFlags,
|
|
bool aMustCallValueAppended,
|
|
bool* aChanged,
|
|
nsCSSContextType aContext);
|
|
// When we detect a webkit-prefixed gradient expression, this function can be
|
|
// used to parse its body into outparam |aValue|, with the help of the
|
|
// CSSUnprefixingService.
|
|
// Only call if ShouldUseUnprefixingService() returns true.
|
|
bool ParseWebkitPrefixedGradientWithService(nsAString& aPrefixedFuncName,
|
|
nsCSSValue& aValue);
|
|
|
|
bool ParseProperty(nsCSSPropertyID aPropID);
|
|
bool ParsePropertyByFunction(nsCSSPropertyID aPropID);
|
|
CSSParseResult ParseSingleValueProperty(nsCSSValue& aValue,
|
|
nsCSSPropertyID aPropID);
|
|
bool ParseSingleValuePropertyByFunction(nsCSSValue& aValue,
|
|
nsCSSPropertyID aPropID);
|
|
|
|
// This is similar to ParseSingleValueProperty but only works for
|
|
// properties that are parsed with ParseBoxProperties or
|
|
// ParseGroupedBoxProperty.
|
|
//
|
|
// Only works with variants with the following flags:
|
|
// A, C, H, K, L, N, P, CALC.
|
|
CSSParseResult ParseBoxProperty(nsCSSValue& aValue,
|
|
nsCSSPropertyID aPropID);
|
|
|
|
enum PriorityParsingStatus {
|
|
ePriority_None,
|
|
ePriority_Important,
|
|
ePriority_Error
|
|
};
|
|
PriorityParsingStatus ParsePriority();
|
|
|
|
#ifdef MOZ_XUL
|
|
bool ParseTreePseudoElement(nsAtomList **aPseudoElementArgs);
|
|
#endif
|
|
|
|
// Property specific parsing routines
|
|
bool ParseImageLayers(const nsCSSPropertyID aTable[]);
|
|
|
|
struct ImageLayersShorthandParseState {
|
|
nsCSSValue& mColor;
|
|
nsCSSValueList* mImage;
|
|
nsCSSValuePairList* mRepeat;
|
|
nsCSSValueList* mAttachment; // A property for background layer only
|
|
nsCSSValueList* mClip;
|
|
nsCSSValueList* mOrigin;
|
|
nsCSSValueList* mPositionX;
|
|
nsCSSValueList* mPositionY;
|
|
nsCSSValuePairList* mSize;
|
|
nsCSSValueList* mComposite; // A property for mask layer only
|
|
nsCSSValueList* mMode; // A property for mask layer only
|
|
ImageLayersShorthandParseState(
|
|
nsCSSValue& aColor, nsCSSValueList* aImage, nsCSSValuePairList* aRepeat,
|
|
nsCSSValueList* aAttachment, nsCSSValueList* aClip,
|
|
nsCSSValueList* aOrigin,
|
|
nsCSSValueList* aPositionX, nsCSSValueList* aPositionY,
|
|
nsCSSValuePairList* aSize, nsCSSValueList* aComposite,
|
|
nsCSSValueList* aMode) :
|
|
mColor(aColor), mImage(aImage), mRepeat(aRepeat),
|
|
mAttachment(aAttachment), mClip(aClip), mOrigin(aOrigin),
|
|
mPositionX(aPositionX), mPositionY(aPositionY),
|
|
mSize(aSize), mComposite(aComposite),
|
|
mMode(aMode) {};
|
|
};
|
|
|
|
bool IsFunctionTokenValidForImageLayerImage(const nsCSSToken& aToken) const;
|
|
bool ParseImageLayersItem(ImageLayersShorthandParseState& aState,
|
|
const nsCSSPropertyID aTable[]);
|
|
|
|
bool ParseValueList(nsCSSPropertyID aPropID); // a single value prop-id
|
|
bool ParseImageLayerRepeat(nsCSSPropertyID aPropID);
|
|
bool ParseImageLayerRepeatValues(nsCSSValuePair& aValue);
|
|
bool ParseImageLayerPosition(const nsCSSPropertyID aTable[]);
|
|
bool ParseImageLayerPositionCoord(nsCSSPropertyID aPropID, bool aIsHorizontal);
|
|
|
|
// ParseBoxPositionValues parses the CSS 2.1 background-position syntax,
|
|
// which is still used by some properties. See ParsePositionValue
|
|
// for the css3-background syntax.
|
|
bool ParseBoxPositionValues(nsCSSValuePair& aOut, bool aAcceptsInherit,
|
|
bool aAllowExplicitCenter = true); // deprecated
|
|
|
|
// ParsePositionValue parses a CSS <position> value, which is used by
|
|
// the 'background-position' property.
|
|
bool ParsePositionValue(nsCSSValue& aOut);
|
|
bool ParsePositionValueSeparateCoords(nsCSSValue& aOutX, nsCSSValue& aOutY);
|
|
|
|
bool ParseImageLayerPositionCoordItem(nsCSSValue& aOut, bool aIsHorizontal);
|
|
bool ParseImageLayerSize(nsCSSPropertyID aPropID);
|
|
bool ParseImageLayerSizeValues(nsCSSValuePair& aOut);
|
|
bool ParseBorderColor();
|
|
bool ParseBorderColors(nsCSSPropertyID aProperty);
|
|
void SetBorderImageInitialValues();
|
|
bool ParseBorderImageRepeat(bool aAcceptsInherit);
|
|
// If ParseBorderImageSlice returns false, aConsumedTokens indicates
|
|
// whether or not any tokens were consumed (in other words, was the property
|
|
// in error or just not present). If ParseBorderImageSlice returns true
|
|
// aConsumedTokens is always true.
|
|
bool ParseBorderImageSlice(bool aAcceptsInherit, bool* aConsumedTokens);
|
|
bool ParseBorderImageWidth(bool aAcceptsInherit);
|
|
bool ParseBorderImageOutset(bool aAcceptsInherit);
|
|
bool ParseBorderImage();
|
|
bool ParseBorderSpacing();
|
|
bool ParseBorderSide(const nsCSSPropertyID aPropIDs[],
|
|
bool aSetAllSides);
|
|
bool ParseBorderStyle();
|
|
bool ParseBorderWidth();
|
|
|
|
bool ParseCalc(nsCSSValue &aValue, uint32_t aVariantMask);
|
|
bool ParseCalcAdditiveExpression(nsCSSValue& aValue,
|
|
uint32_t& aVariantMask);
|
|
bool ParseCalcMultiplicativeExpression(nsCSSValue& aValue,
|
|
uint32_t& aVariantMask,
|
|
bool *aHadFinalWS);
|
|
bool ParseCalcTerm(nsCSSValue& aValue, uint32_t& aVariantMask);
|
|
bool RequireWhitespace();
|
|
|
|
// For "flex" shorthand property, defined in CSS Flexbox spec
|
|
bool ParseFlex();
|
|
// For "flex-flow" shorthand property, defined in CSS Flexbox spec
|
|
bool ParseFlexFlow();
|
|
|
|
// CSS Grid
|
|
bool ParseGridAutoFlow();
|
|
|
|
// Parse a <line-names> expression.
|
|
// If successful, either leave aValue untouched,
|
|
// to indicate that we parsed the empty list,
|
|
// or set it to a eCSSUnit_List of eCSSUnit_Ident.
|
|
//
|
|
// To parse an optional <line-names> (ie. if not finding an open bracket
|
|
// is considered the same as an empty list),
|
|
// treat CSSParseResult::NotFound the same as CSSParseResult::Ok.
|
|
//
|
|
// If aValue is already a eCSSUnit_List, append to that list.
|
|
CSSParseResult ParseGridLineNames(nsCSSValue& aValue);
|
|
bool ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr);
|
|
bool ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue);
|
|
|
|
CSSParseResult ParseGridTrackBreadth(nsCSSValue& aValue);
|
|
// eFixedTrackSize in aFlags makes it parse a <fixed-size>.
|
|
CSSParseResult ParseGridTrackSize(nsCSSValue& aValue,
|
|
GridTrackSizeFlags aFlags = GridTrackSizeFlags::eDefaultTrackSize);
|
|
|
|
bool ParseGridAutoColumnsRows(nsCSSPropertyID aPropID);
|
|
bool ParseGridTrackListRepeat(nsCSSValueList** aTailPtr);
|
|
bool ParseGridTrackRepeatIntro(bool aForSubgrid,
|
|
int32_t* aRepetitions,
|
|
Maybe<int32_t>* aRepeatAutoEnum);
|
|
|
|
// Assuming a [ <line-names>? ] has already been parsed,
|
|
// parse the rest of a <track-list>.
|
|
//
|
|
// This exists because [ <line-names>? ] is ambiguous in the 'grid-template'
|
|
// shorthand: it can be either the start of a <track-list> (in
|
|
// a <'grid-template-rows'>) or of the intertwined syntax that sets both
|
|
// grid-template-rows and grid-template-areas.
|
|
//
|
|
// On success, |aValue| will be a list of odd length >= 3,
|
|
// starting with a <line-names> (which is itself a list)
|
|
// and alternating between that and <track-size>.
|
|
bool ParseGridTrackListWithFirstLineNames(nsCSSValue& aValue,
|
|
const nsCSSValue& aFirstLineNames,
|
|
GridTrackListFlags aFlags = GridTrackListFlags::eDefaultTrackList);
|
|
|
|
bool ParseGridTrackList(nsCSSPropertyID aPropID,
|
|
GridTrackListFlags aFlags = GridTrackListFlags::eDefaultTrackList);
|
|
bool ParseGridTemplateColumnsRows(nsCSSPropertyID aPropID);
|
|
|
|
// |aAreaIndices| is a lookup table to help us parse faster,
|
|
// mapping area names to indices in |aResult.mNamedAreas|.
|
|
bool ParseGridTemplateAreasLine(const nsAutoString& aInput,
|
|
css::GridTemplateAreasValue* aResult,
|
|
nsDataHashtable<nsStringHashKey, uint32_t>& aAreaIndices);
|
|
bool ParseGridTemplateAreas();
|
|
bool ParseGridTemplateColumnsOrAutoFlow(bool aForGridShorthand);
|
|
bool ParseGridTemplate(bool aForGridShorthand = false);
|
|
bool ParseGridTemplateAfterString(const nsCSSValue& aFirstLineNames);
|
|
bool ParseGrid();
|
|
CSSParseResult ParseGridShorthandAutoProps(int32_t aAutoFlowAxis);
|
|
bool ParseGridLine(nsCSSValue& aValue);
|
|
bool ParseGridColumnRowStartEnd(nsCSSPropertyID aPropID);
|
|
bool ParseGridColumnRow(nsCSSPropertyID aStartPropID,
|
|
nsCSSPropertyID aEndPropID);
|
|
bool ParseGridArea();
|
|
bool ParseGridGap();
|
|
|
|
bool ParseInitialLetter();
|
|
|
|
// parsing 'align/justify-items/self' from the css-align spec
|
|
bool ParseAlignJustifyPosition(nsCSSValue& aResult,
|
|
const KTableEntry aTable[]);
|
|
bool ParseJustifyItems();
|
|
bool ParseAlignItems();
|
|
bool ParseAlignJustifySelf(nsCSSPropertyID aPropID);
|
|
// parsing 'align/justify-content' from the css-align spec
|
|
bool ParseAlignJustifyContent(nsCSSPropertyID aPropID);
|
|
|
|
// for 'clip' and '-moz-image-region'
|
|
bool ParseRect(nsCSSPropertyID aPropID);
|
|
bool ParseColumns();
|
|
bool ParseContain(nsCSSValue& aValue);
|
|
bool ParseContent();
|
|
bool ParseCounterData(nsCSSPropertyID aPropID);
|
|
bool ParseCursor();
|
|
bool ParseFont();
|
|
bool ParseFontSynthesis(nsCSSValue& aValue);
|
|
bool ParseSingleAlternate(int32_t& aWhichFeature, nsCSSValue& aValue);
|
|
bool ParseFontVariantAlternates(nsCSSValue& aValue);
|
|
bool MergeBitmaskValue(int32_t aNewValue, const int32_t aMasks[],
|
|
int32_t& aMergedValue);
|
|
bool ParseBitmaskValues(nsCSSValue& aValue,
|
|
const KTableEntry aKeywordTable[],
|
|
const int32_t aMasks[]);
|
|
bool ParseFontVariantEastAsian(nsCSSValue& aValue);
|
|
bool ParseFontVariantLigatures(nsCSSValue& aValue);
|
|
bool ParseFontVariantNumeric(nsCSSValue& aValue);
|
|
bool ParseFontVariant();
|
|
bool ParseFontWeight(nsCSSValue& aValue);
|
|
bool ParseOneFamily(nsAString& aFamily, bool& aOneKeyword, bool& aQuoted);
|
|
bool ParseFamily(nsCSSValue& aValue);
|
|
bool ParseFontFeatureSettings(nsCSSValue& aValue);
|
|
bool ParseFontSrc(nsCSSValue& aValue);
|
|
bool ParseFontSrcFormat(InfallibleTArray<nsCSSValue>& values);
|
|
bool ParseFontRanges(nsCSSValue& aValue);
|
|
bool ParseListStyle();
|
|
bool ParseListStyleType(nsCSSValue& aValue);
|
|
bool ParseMargin();
|
|
bool ParseClipPath(nsCSSValue& aValue);
|
|
bool ParseTransform(bool aIsPrefixed, bool aDisallowRelativeValues = false);
|
|
bool ParseObjectPosition();
|
|
bool ParseOutline();
|
|
bool ParseOverflow();
|
|
bool ParsePadding();
|
|
bool ParseQuotes();
|
|
bool ParseTextAlign(nsCSSValue& aValue,
|
|
const KTableEntry aTable[]);
|
|
bool ParseTextAlign(nsCSSValue& aValue);
|
|
bool ParseTextAlignLast(nsCSSValue& aValue);
|
|
bool ParseTextDecoration();
|
|
bool ParseTextDecorationLine(nsCSSValue& aValue);
|
|
bool ParseTextEmphasis();
|
|
bool ParseTextEmphasisPosition(nsCSSValue& aValue);
|
|
bool ParseTextEmphasisStyle(nsCSSValue& aValue);
|
|
bool ParseTextCombineUpright(nsCSSValue& aValue);
|
|
bool ParseTextOverflow(nsCSSValue& aValue);
|
|
bool ParseTouchAction(nsCSSValue& aValue);
|
|
|
|
bool ParseShadowItem(nsCSSValue& aValue, bool aIsBoxShadow);
|
|
bool ParseShadowList(nsCSSPropertyID aProperty);
|
|
bool ParseShapeOutside(nsCSSValue& aValue);
|
|
bool ParseTransitionProperty();
|
|
bool ParseTransitionTimingFunctionValues(nsCSSValue& aValue);
|
|
bool ParseTransitionTimingFunctionValueComponent(float& aComponent,
|
|
char aStop,
|
|
bool aIsXPoint);
|
|
bool ParseTransitionStepTimingFunctionValues(nsCSSValue& aValue);
|
|
enum ParseAnimationOrTransitionShorthandResult {
|
|
eParseAnimationOrTransitionShorthand_Values,
|
|
eParseAnimationOrTransitionShorthand_Inherit,
|
|
eParseAnimationOrTransitionShorthand_Error
|
|
};
|
|
ParseAnimationOrTransitionShorthandResult
|
|
ParseAnimationOrTransitionShorthand(const nsCSSPropertyID* aProperties,
|
|
const nsCSSValue* aInitialValues,
|
|
nsCSSValue* aValues,
|
|
size_t aNumProperties);
|
|
bool ParseTransition();
|
|
bool ParseAnimation();
|
|
bool ParseWillChange();
|
|
|
|
bool ParsePaint(nsCSSPropertyID aPropID);
|
|
bool ParseDasharray();
|
|
bool ParseMarker();
|
|
bool ParsePaintOrder();
|
|
bool ParseAll();
|
|
bool ParseScrollSnapType();
|
|
bool ParseScrollSnapPoints(nsCSSValue& aValue, nsCSSPropertyID aPropID);
|
|
bool ParseScrollSnapDestination(nsCSSValue& aValue);
|
|
bool ParseScrollSnapCoordinate(nsCSSValue& aValue);
|
|
bool ParseWebkitTextStroke();
|
|
|
|
/**
|
|
* Parses a variable value from a custom property declaration.
|
|
*
|
|
* @param aType Out parameter into which will be stored the type of variable
|
|
* value, indicating whether the parsed value was a token stream or one of
|
|
* the CSS-wide keywords.
|
|
* @param aValue Out parameter into which will be stored the token stream
|
|
* as a string, if the parsed custom property did take a token stream.
|
|
* @return Whether parsing succeeded.
|
|
*/
|
|
bool ParseVariableDeclaration(CSSVariableDeclarations::Type* aType,
|
|
nsString& aValue);
|
|
|
|
/**
|
|
* Parses a CSS variable value. This could be 'initial', 'inherit', 'unset'
|
|
* or a token stream, which may or may not include variable references.
|
|
*
|
|
* @param aType Out parameter into which the type of the variable value
|
|
* will be stored.
|
|
* @param aDropBackslash Out parameter indicating whether during variable
|
|
* value parsing there was a trailing backslash before EOF that must
|
|
* be dropped when serializing the variable value.
|
|
* @param aImpliedCharacters Out parameter appended to which will be any
|
|
* characters that were implied when encountering EOF and which
|
|
* must be included at the end of the serialized variable value.
|
|
* @param aFunc A callback function to invoke when a variable reference
|
|
* is encountered. May be null. Arguments are the variable name
|
|
* and the aData argument passed in to this function.
|
|
* @param User data to pass in to the callback.
|
|
* @return Whether parsing succeeded.
|
|
*/
|
|
bool ParseValueWithVariables(CSSVariableDeclarations::Type* aType,
|
|
bool* aDropBackslash,
|
|
nsString& aImpliedCharacters,
|
|
void (*aFunc)(const nsAString&, void*),
|
|
void* aData);
|
|
|
|
/**
|
|
* Returns whether the scanner dropped a backslash just before EOF.
|
|
*/
|
|
bool BackslashDropped();
|
|
|
|
/**
|
|
* Calls AppendImpliedEOFCharacters on mScanner.
|
|
*/
|
|
void AppendImpliedEOFCharacters(nsAString& aResult);
|
|
|
|
// Reused utility parsing routines
|
|
void AppendValue(nsCSSPropertyID aPropID, const nsCSSValue& aValue);
|
|
bool ParseBoxProperties(const nsCSSPropertyID aPropIDs[]);
|
|
bool ParseGroupedBoxProperty(int32_t aVariantMask,
|
|
nsCSSValue& aValue);
|
|
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);
|
|
bool ParseNumberColorComponent(uint8_t& aComponent, char aStop);
|
|
bool ParsePercentageColorComponent(float& aComponent, char aStop);
|
|
// ParseHSLColor parses everything starting with the opening '('
|
|
// up through and including the aStop char.
|
|
bool ParseHSLColor(float& aHue, float& aSaturation, float& aLightness,
|
|
char aStop);
|
|
// ParseColorOpacity will enforce that the color ends with a ')'
|
|
// after the opacity
|
|
bool ParseColorOpacity(uint8_t& aOpacity);
|
|
bool ParseColorOpacity(float& aOpacity);
|
|
bool ParseEnum(nsCSSValue& aValue,
|
|
const KTableEntry aKeywordTable[]);
|
|
|
|
// Variant parsing methods
|
|
CSSParseResult ParseVariant(nsCSSValue& aValue,
|
|
uint32_t aVariantMask,
|
|
const KTableEntry aKeywordTable[]);
|
|
CSSParseResult ParseVariantWithRestrictions(nsCSSValue& aValue,
|
|
int32_t aVariantMask,
|
|
const KTableEntry aKeywordTable[],
|
|
uint32_t aRestrictions);
|
|
CSSParseResult ParseNonNegativeVariant(nsCSSValue& aValue,
|
|
int32_t aVariantMask,
|
|
const KTableEntry aKeywordTable[]);
|
|
CSSParseResult ParseOneOrLargerVariant(nsCSSValue& aValue,
|
|
int32_t aVariantMask,
|
|
const KTableEntry aKeywordTable[]);
|
|
|
|
// Variant parsing methods that are guaranteed to UngetToken any token
|
|
// consumed on failure
|
|
bool ParseSingleTokenVariant(nsCSSValue& aValue,
|
|
int32_t aVariantMask,
|
|
const KTableEntry aKeywordTable[])
|
|
{
|
|
MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS),
|
|
"use ParseVariant for variants in VARIANT_MULTIPLE_TOKENS");
|
|
CSSParseResult result = ParseVariant(aValue, aVariantMask, aKeywordTable);
|
|
MOZ_ASSERT(result != CSSParseResult::Error);
|
|
return result == CSSParseResult::Ok;
|
|
}
|
|
bool ParseSingleTokenVariantWithRestrictions(
|
|
nsCSSValue& aValue,
|
|
int32_t aVariantMask,
|
|
const KTableEntry aKeywordTable[],
|
|
uint32_t aRestrictions)
|
|
{
|
|
MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS),
|
|
"use ParseVariantWithRestrictions for variants in "
|
|
"VARIANT_MULTIPLE_TOKENS");
|
|
CSSParseResult result =
|
|
ParseVariantWithRestrictions(aValue, aVariantMask, aKeywordTable,
|
|
aRestrictions);
|
|
MOZ_ASSERT(result != CSSParseResult::Error);
|
|
return result == CSSParseResult::Ok;
|
|
}
|
|
bool ParseSingleTokenNonNegativeVariant(nsCSSValue& aValue,
|
|
int32_t aVariantMask,
|
|
const KTableEntry aKeywordTable[])
|
|
{
|
|
MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS),
|
|
"use ParseNonNegativeVariant for variants in "
|
|
"VARIANT_MULTIPLE_TOKENS");
|
|
CSSParseResult result =
|
|
ParseNonNegativeVariant(aValue, aVariantMask, aKeywordTable);
|
|
MOZ_ASSERT(result != CSSParseResult::Error);
|
|
return result == CSSParseResult::Ok;
|
|
}
|
|
bool ParseSingleTokenOneOrLargerVariant(nsCSSValue& aValue,
|
|
int32_t aVariantMask,
|
|
const KTableEntry aKeywordTable[])
|
|
{
|
|
MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS),
|
|
"use ParseOneOrLargerVariant for variants in "
|
|
"VARIANT_MULTIPLE_TOKENS");
|
|
CSSParseResult result =
|
|
ParseOneOrLargerVariant(aValue, aVariantMask, aKeywordTable);
|
|
MOZ_ASSERT(result != CSSParseResult::Error);
|
|
return result == CSSParseResult::Ok;
|
|
}
|
|
|
|
// Helpers for some common ParseSingleTokenNonNegativeVariant calls.
|
|
bool ParseNonNegativeInteger(nsCSSValue& aValue)
|
|
{
|
|
return ParseSingleTokenNonNegativeVariant(aValue, VARIANT_INTEGER, nullptr);
|
|
}
|
|
bool ParseNonNegativeNumber(nsCSSValue& aValue)
|
|
{
|
|
return ParseSingleTokenNonNegativeVariant(aValue, VARIANT_NUMBER, nullptr);
|
|
}
|
|
|
|
// Helpers for some common ParseSingleTokenOneOrLargerVariant calls.
|
|
bool ParseOneOrLargerInteger(nsCSSValue& aValue)
|
|
{
|
|
return ParseSingleTokenOneOrLargerVariant(aValue, VARIANT_INTEGER, nullptr);
|
|
}
|
|
bool ParseOneOrLargerNumber(nsCSSValue& aValue)
|
|
{
|
|
return ParseSingleTokenOneOrLargerVariant(aValue, VARIANT_NUMBER, nullptr);
|
|
}
|
|
|
|
// http://dev.w3.org/csswg/css-values/#custom-idents
|
|
// Parse an identifier that is none of:
|
|
// * a CSS-wide keyword
|
|
// * "default"
|
|
// * a keyword in |aExcludedKeywords|
|
|
// * a keyword in |aPropertyKTable|
|
|
//
|
|
// |aExcludedKeywords| is an array of nsCSSKeyword
|
|
// that ends with a eCSSKeyword_UNKNOWN marker.
|
|
//
|
|
// |aPropertyKTable| can be used if some of the keywords to exclude
|
|
// also appear in an existing nsCSSProps::KTableEntry,
|
|
// to avoid duplicating them.
|
|
bool ParseCustomIdent(nsCSSValue& aValue,
|
|
const nsAutoString& aIdentValue,
|
|
const nsCSSKeyword aExcludedKeywords[] = nullptr,
|
|
const nsCSSProps::KTableEntry aPropertyKTable[] = nullptr);
|
|
bool ParseCounter(nsCSSValue& aValue);
|
|
bool ParseAttr(nsCSSValue& aValue);
|
|
bool ParseSymbols(nsCSSValue& aValue);
|
|
bool SetValueToURL(nsCSSValue& aValue, const nsString& aURL);
|
|
bool TranslateDimension(nsCSSValue& aValue, uint32_t aVariantMask,
|
|
float aNumber, const nsString& aUnit);
|
|
bool ParseImageOrientation(nsCSSValue& aAngle);
|
|
bool ParseImageRect(nsCSSValue& aImage);
|
|
bool ParseElement(nsCSSValue& aValue);
|
|
bool ParseColorStop(nsCSSValueGradient* aGradient);
|
|
|
|
enum GradientParsingFlags {
|
|
eGradient_Repeating = 1 << 0, // repeating-{linear|radial}-gradient
|
|
eGradient_MozLegacy = 1 << 1, // -moz-{linear|radial}-gradient
|
|
eGradient_WebkitLegacy = 1 << 2, // -webkit-{linear|radial}-gradient
|
|
|
|
// Mask to catch both "legacy" flags:
|
|
eGradient_AnyLegacy = eGradient_MozLegacy | eGradient_WebkitLegacy
|
|
};
|
|
bool ParseLinearGradient(nsCSSValue& aValue, uint8_t aFlags);
|
|
bool ParseRadialGradient(nsCSSValue& aValue, uint8_t aFlags);
|
|
bool IsLegacyGradientLine(const nsCSSTokenType& aType,
|
|
const nsString& aId);
|
|
bool ParseGradientColorStops(nsCSSValueGradient* aGradient,
|
|
nsCSSValue& aValue);
|
|
|
|
// For the ancient "-webkit-gradient(linear|radial, ...)" syntax:
|
|
bool ParseWebkitGradientPointComponent(nsCSSValue& aComponent,
|
|
bool aIsHorizontal);
|
|
bool ParseWebkitGradientPoint(nsCSSValuePair& aPoint);
|
|
bool ParseWebkitGradientRadius(float& aRadius);
|
|
bool ParseWebkitGradientColorStop(nsCSSValueGradient* aGradient);
|
|
bool ParseWebkitGradientColorStops(nsCSSValueGradient* aGradient);
|
|
void FinalizeLinearWebkitGradient(nsCSSValueGradient* aGradient,
|
|
const nsCSSValuePair& aStartPoint,
|
|
const nsCSSValuePair& aSecondPoint);
|
|
void FinalizeRadialWebkitGradient(nsCSSValueGradient* aGradient,
|
|
const nsCSSValuePair& aFirstCenter,
|
|
const nsCSSValuePair& aSecondCenter,
|
|
const float aFirstRadius,
|
|
const float aSecondRadius);
|
|
bool ParseWebkitGradient(nsCSSValue& aValue);
|
|
|
|
void SetParsingCompoundProperty(bool aBool) {
|
|
mParsingCompoundProperty = aBool;
|
|
}
|
|
bool IsParsingCompoundProperty(void) const {
|
|
return mParsingCompoundProperty;
|
|
}
|
|
|
|
/* Functions for basic shapes */
|
|
bool ParseReferenceBoxAndBasicShape(nsCSSValue& aValue,
|
|
const KTableEntry aBoxKeywordTable[]);
|
|
bool ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens);
|
|
bool ParsePolygonFunction(nsCSSValue& aValue);
|
|
bool ParseCircleOrEllipseFunction(nsCSSKeyword, nsCSSValue& aValue);
|
|
bool ParseInsetFunction(nsCSSValue& aValue);
|
|
// We parse position values differently for basic-shape, by expanding defaults
|
|
// and replacing keywords with percentages
|
|
bool ParsePositionValueForBasicShape(nsCSSValue& aOut);
|
|
|
|
|
|
/* Functions for transform Parsing */
|
|
bool ParseSingleTransform(bool aIsPrefixed, bool aDisallowRelativeValues,
|
|
nsCSSValue& aValue);
|
|
bool ParseFunction(nsCSSKeyword aFunction, const uint32_t aAllowedTypes[],
|
|
uint32_t aVariantMaskAll, uint16_t aMinElems,
|
|
uint16_t aMaxElems, nsCSSValue &aValue);
|
|
bool ParseFunctionInternals(const uint32_t aVariantMask[],
|
|
uint32_t aVariantMaskAll,
|
|
uint16_t aMinElems,
|
|
uint16_t aMaxElems,
|
|
InfallibleTArray<nsCSSValue>& aOutput);
|
|
|
|
/* Functions for transform-origin/perspective-origin Parsing */
|
|
bool ParseTransformOrigin(bool aPerspective);
|
|
|
|
/* Functions for filter parsing */
|
|
bool ParseFilter();
|
|
bool ParseSingleFilter(nsCSSValue* aValue);
|
|
bool ParseDropShadow(nsCSSValue* aValue);
|
|
|
|
/* Find and return the namespace ID associated with aPrefix.
|
|
If aPrefix has not been declared in an @namespace rule, returns
|
|
kNameSpaceID_Unknown. */
|
|
int32_t GetNamespaceIdForPrefix(const nsString& aPrefix);
|
|
|
|
/* Find the correct default namespace, and set it on aSelector. */
|
|
void SetDefaultNamespaceOnSelector(nsCSSSelector& aSelector);
|
|
|
|
// Current token. The value is valid after calling GetToken and invalidated
|
|
// by UngetToken.
|
|
nsCSSToken mToken;
|
|
|
|
// Our scanner.
|
|
nsCSSScanner* mScanner;
|
|
|
|
// Our error reporter.
|
|
css::ErrorReporter* mReporter;
|
|
|
|
// The URI to be used as a base for relative URIs.
|
|
nsCOMPtr<nsIURI> mBaseURI;
|
|
|
|
// The URI to be used as an HTTP "Referer" and for error reporting.
|
|
nsCOMPtr<nsIURI> mSheetURI;
|
|
|
|
// The principal of the sheet involved
|
|
nsCOMPtr<nsIPrincipal> mSheetPrincipal;
|
|
|
|
// The sheet we're parsing into
|
|
RefPtr<CSSStyleSheet> mSheet;
|
|
|
|
// Used for @import rules
|
|
css::Loader* mChildLoader; // not ref counted, it owns us
|
|
|
|
// Any sheets we may reuse when parsing an @import.
|
|
css::LoaderReusableStyleSheets* mReusableSheets;
|
|
|
|
// Sheet section we're in. This is used to enforce correct ordering of the
|
|
// various rule types (eg the fact that a @charset rule must come before
|
|
// anything else). Note that there are checks of similar things in various
|
|
// places in CSSStyleSheet.cpp (e.g in insertRule, RebuildChildList).
|
|
enum nsCSSSection {
|
|
eCSSSection_Charset,
|
|
eCSSSection_Import,
|
|
eCSSSection_NameSpace,
|
|
eCSSSection_General
|
|
};
|
|
nsCSSSection mSection;
|
|
|
|
nsXMLNameSpaceMap *mNameSpaceMap; // weak, mSheet owns it
|
|
|
|
// After an UngetToken is done this flag is true. The next call to
|
|
// GetToken clears the flag.
|
|
bool mHavePushBack : 1;
|
|
|
|
// True if we are in quirks mode; false in standards or almost standards mode
|
|
bool mNavQuirkMode : 1;
|
|
|
|
// True when the hashless color quirk applies.
|
|
bool mHashlessColorQuirk : 1;
|
|
|
|
// True when the unitless length quirk applies.
|
|
bool mUnitlessLengthQuirk : 1;
|
|
|
|
// Controls access to nonstandard style constructs that are not safe
|
|
// for use on the public Web but necessary in UA sheets and/or
|
|
// useful in user sheets. The only values stored in this field are
|
|
// 0, 1, and 2; three bits are allocated to avoid issues should the
|
|
// enum type be signed.
|
|
css::SheetParsingMode mParsingMode : 3;
|
|
|
|
// True if we are in parsing rules for the chrome.
|
|
bool mIsChrome : 1;
|
|
|
|
// True if viewport units should be allowed.
|
|
bool mViewportUnitsEnabled : 1;
|
|
|
|
// True for parsing media lists for HTML attributes, where we have to
|
|
// ignore CSS comments.
|
|
bool mHTMLMediaMode : 1;
|
|
|
|
// This flag is set when parsing a non-box shorthand; it's used to not apply
|
|
// some quirks during shorthand parsing
|
|
bool mParsingCompoundProperty : 1;
|
|
|
|
// True if we are in the middle of parsing an @supports condition.
|
|
// This is used to avoid recording the input stream when variable references
|
|
// are encountered in a property declaration in the @supports condition.
|
|
bool mInSupportsCondition : 1;
|
|
|
|
// True if we are somewhere within a @supports rule whose condition is
|
|
// false.
|
|
bool mInFailingSupportsRule : 1;
|
|
|
|
// True if we will suppress all parse errors (except unexpected EOFs).
|
|
// This is used to prevent errors for declarations inside a failing
|
|
// @supports rule.
|
|
bool mSuppressErrors : 1;
|
|
|
|
// True if any parsing of URL values requires a sheet principal to have
|
|
// been passed in the nsCSSScanner constructor. This is usually the case.
|
|
// It can be set to false, for example, when we create an nsCSSParser solely
|
|
// to parse a property value to test it for syntactic correctness. When
|
|
// false, an assertion that mSheetPrincipal is non-null is skipped. Should
|
|
// not be set to false if any nsCSSValues created during parsing can escape
|
|
// out of the parser.
|
|
bool mSheetPrincipalRequired;
|
|
|
|
// This enum helps us track whether we've unprefixed "display: -webkit-box"
|
|
// (treating it as "display: flex") in an earlier declaration within a series
|
|
// of declarations. (This only impacts behavior when the function
|
|
// "ShouldUseUnprefixingService()" returns true, and that should only happen
|
|
// for a short whitelist of origins.)
|
|
enum WebkitBoxUnprefixState : uint8_t {
|
|
eNotParsingDecls, // We are *not* currently parsing a sequence of
|
|
// CSS declarations. (default state)
|
|
|
|
// The next two enum values indicate that we *are* currently parsing a
|
|
// sequence of declarations (in ParseDeclarations or ParseDeclarationBlock)
|
|
// and...
|
|
eHaveNotUnprefixed, // ...we have not unprefixed 'display:-webkit-box' in
|
|
// this sequence of CSS declarations.
|
|
eHaveUnprefixed // ...we *have* unprefixed 'display:-webkit-box' earlier in
|
|
// this sequence of CSS declarations.
|
|
};
|
|
WebkitBoxUnprefixState mWebkitBoxUnprefixState;
|
|
|
|
// Stack of rule groups; used for @media and such.
|
|
InfallibleTArray<RefPtr<css::GroupRule> > mGroupStack;
|
|
|
|
// During the parsing of a property (which may be a shorthand), the data
|
|
// are stored in |mTempData|. (It is needed to ensure that parser
|
|
// errors cause the data to be ignored, and to ensure that a
|
|
// non-'!important' declaration does not override an '!important'
|
|
// one.)
|
|
nsCSSExpandedDataBlock mTempData;
|
|
|
|
// All data from successfully parsed properties are placed into |mData|.
|
|
nsCSSExpandedDataBlock mData;
|
|
|
|
public:
|
|
// Used from nsCSSParser constructors and destructors
|
|
CSSParserImpl* mNextFree;
|
|
};
|
|
|
|
static void AssignRuleToPointer(css::Rule* aRule, void* aPointer)
|
|
{
|
|
css::Rule **pointer = static_cast<css::Rule**>(aPointer);
|
|
NS_ADDREF(*pointer = aRule);
|
|
}
|
|
|
|
static void AppendRuleToSheet(css::Rule* aRule, void* aParser)
|
|
{
|
|
CSSParserImpl* parser = (CSSParserImpl*) aParser;
|
|
parser->AppendRule(aRule);
|
|
}
|
|
|
|
#define REPORT_UNEXPECTED(msg_) \
|
|
{ if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_); }
|
|
|
|
#define REPORT_UNEXPECTED_P(msg_, param_) \
|
|
{ if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, param_); }
|
|
|
|
#define REPORT_UNEXPECTED_P_V(msg_, param_, value_) \
|
|
{ if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, param_, value_); }
|
|
|
|
#define REPORT_UNEXPECTED_TOKEN(msg_) \
|
|
{ if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, mToken); }
|
|
|
|
#define REPORT_UNEXPECTED_TOKEN_CHAR(msg_, ch_) \
|
|
{ if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, mToken, ch_); }
|
|
|
|
#define REPORT_UNEXPECTED_EOF(lf_) \
|
|
mReporter->ReportUnexpectedEOF(#lf_)
|
|
|
|
#define REPORT_UNEXPECTED_EOF_CHAR(ch_) \
|
|
mReporter->ReportUnexpectedEOF(ch_)
|
|
|
|
#define OUTPUT_ERROR() \
|
|
mReporter->OutputError()
|
|
|
|
#define OUTPUT_ERROR_WITH_POSITION(linenum_, lineoff_) \
|
|
mReporter->OutputError(linenum_, lineoff_)
|
|
|
|
#define CLEAR_ERROR() \
|
|
mReporter->ClearError()
|
|
|
|
CSSParserImpl::CSSParserImpl()
|
|
: mToken(),
|
|
mScanner(nullptr),
|
|
mReporter(nullptr),
|
|
mChildLoader(nullptr),
|
|
mReusableSheets(nullptr),
|
|
mSection(eCSSSection_Charset),
|
|
mNameSpaceMap(nullptr),
|
|
mHavePushBack(false),
|
|
mNavQuirkMode(false),
|
|
mHashlessColorQuirk(false),
|
|
mUnitlessLengthQuirk(false),
|
|
mParsingMode(css::eAuthorSheetFeatures),
|
|
mIsChrome(false),
|
|
mViewportUnitsEnabled(true),
|
|
mHTMLMediaMode(false),
|
|
mParsingCompoundProperty(false),
|
|
mInSupportsCondition(false),
|
|
mInFailingSupportsRule(false),
|
|
mSuppressErrors(false),
|
|
mSheetPrincipalRequired(true),
|
|
mWebkitBoxUnprefixState(eNotParsingDecls),
|
|
mNextFree(nullptr)
|
|
{
|
|
}
|
|
|
|
CSSParserImpl::~CSSParserImpl()
|
|
{
|
|
mData.AssertInitialState();
|
|
mTempData.AssertInitialState();
|
|
}
|
|
|
|
nsresult
|
|
CSSParserImpl::SetStyleSheet(CSSStyleSheet* aSheet)
|
|
{
|
|
if (aSheet != mSheet) {
|
|
// Switch to using the new sheet, if any
|
|
mGroupStack.Clear();
|
|
mSheet = aSheet;
|
|
if (mSheet) {
|
|
mNameSpaceMap = mSheet->GetNameSpaceMap();
|
|
} else {
|
|
mNameSpaceMap = nullptr;
|
|
}
|
|
} else if (mSheet) {
|
|
mNameSpaceMap = mSheet->GetNameSpaceMap();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsIDocument*
|
|
CSSParserImpl::GetDocument()
|
|
{
|
|
if (!mSheet) {
|
|
return nullptr;
|
|
}
|
|
return mSheet->GetDocument();
|
|
}
|
|
|
|
nsresult
|
|
CSSParserImpl::SetQuirkMode(bool aQuirkMode)
|
|
{
|
|
mNavQuirkMode = aQuirkMode;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CSSParserImpl::SetChildLoader(mozilla::css::Loader* aChildLoader)
|
|
{
|
|
mChildLoader = aChildLoader; // not ref counted, it owns us
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::Reset()
|
|
{
|
|
NS_ASSERTION(!mScanner, "resetting with scanner active");
|
|
SetStyleSheet(nullptr);
|
|
SetQuirkMode(false);
|
|
SetChildLoader(nullptr);
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::InitScanner(nsCSSScanner& aScanner,
|
|
css::ErrorReporter& aReporter,
|
|
nsIURI* aSheetURI, nsIURI* aBaseURI,
|
|
nsIPrincipal* aSheetPrincipal)
|
|
{
|
|
NS_PRECONDITION(!mHTMLMediaMode, "Bad initial state");
|
|
NS_PRECONDITION(!mParsingCompoundProperty, "Bad initial state");
|
|
NS_PRECONDITION(!mScanner, "already have scanner");
|
|
|
|
mScanner = &aScanner;
|
|
mReporter = &aReporter;
|
|
mScanner->SetErrorReporter(mReporter);
|
|
|
|
mBaseURI = aBaseURI;
|
|
mSheetURI = aSheetURI;
|
|
mSheetPrincipal = aSheetPrincipal;
|
|
mHavePushBack = false;
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::ReleaseScanner()
|
|
{
|
|
mScanner = nullptr;
|
|
mReporter = nullptr;
|
|
mBaseURI = nullptr;
|
|
mSheetURI = nullptr;
|
|
mSheetPrincipal = nullptr;
|
|
}
|
|
|
|
nsresult
|
|
CSSParserImpl::ParseSheet(const nsAString& aInput,
|
|
nsIURI* aSheetURI,
|
|
nsIURI* aBaseURI,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
uint32_t aLineNumber,
|
|
css::LoaderReusableStyleSheets* aReusableSheets)
|
|
{
|
|
NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
|
|
NS_PRECONDITION(aBaseURI, "need base URI");
|
|
NS_PRECONDITION(aSheetURI, "need sheet URI");
|
|
NS_PRECONDITION(mSheet, "Must have sheet to parse into");
|
|
NS_ENSURE_STATE(mSheet);
|
|
|
|
#ifdef DEBUG
|
|
nsIURI* uri = mSheet->GetSheetURI();
|
|
bool equal;
|
|
NS_ASSERTION(NS_SUCCEEDED(aSheetURI->Equals(uri, &equal)) && equal,
|
|
"Sheet URI does not match passed URI");
|
|
NS_ASSERTION(NS_SUCCEEDED(mSheet->Principal()->Equals(aSheetPrincipal,
|
|
&equal)) &&
|
|
equal,
|
|
"Sheet principal does not match passed principal");
|
|
#endif
|
|
|
|
nsCSSScanner scanner(aInput, aLineNumber);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
|
|
InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
|
|
|
|
int32_t ruleCount = mSheet->StyleRuleCount();
|
|
if (0 < ruleCount) {
|
|
const css::Rule* lastRule = mSheet->GetStyleRuleAt(ruleCount - 1);
|
|
if (lastRule) {
|
|
switch (lastRule->GetType()) {
|
|
case css::Rule::CHARSET_RULE:
|
|
case css::Rule::IMPORT_RULE:
|
|
mSection = eCSSSection_Import;
|
|
break;
|
|
case css::Rule::NAMESPACE_RULE:
|
|
mSection = eCSSSection_NameSpace;
|
|
break;
|
|
default:
|
|
mSection = eCSSSection_General;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
mSection = eCSSSection_Charset; // sheet is empty, any rules are fair
|
|
}
|
|
|
|
mParsingMode = mSheet->ParsingMode();
|
|
mIsChrome = dom::IsChromeURI(aSheetURI);
|
|
mReusableSheets = aReusableSheets;
|
|
|
|
nsCSSToken* tk = &mToken;
|
|
for (;;) {
|
|
// Get next non-whitespace token
|
|
if (!GetToken(true)) {
|
|
OUTPUT_ERROR();
|
|
break;
|
|
}
|
|
if (eCSSToken_HTMLComment == tk->mType) {
|
|
continue; // legal here only
|
|
}
|
|
if (eCSSToken_AtKeyword == tk->mType) {
|
|
ParseAtRule(AppendRuleToSheet, this, false);
|
|
continue;
|
|
}
|
|
UngetToken();
|
|
if (ParseRuleSet(AppendRuleToSheet, this)) {
|
|
mSection = eCSSSection_General;
|
|
}
|
|
}
|
|
ReleaseScanner();
|
|
|
|
mParsingMode = css::eAuthorSheetFeatures;
|
|
mIsChrome = false;
|
|
mReusableSheets = nullptr;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* Determines whether the identifier contained in the given string is a
|
|
* vendor-specific identifier, as described in CSS 2.1 section 4.1.2.1.
|
|
*/
|
|
static bool
|
|
NonMozillaVendorIdentifier(const nsAString& ident)
|
|
{
|
|
return (ident.First() == char16_t('-') &&
|
|
!StringBeginsWith(ident, NS_LITERAL_STRING("-moz-"))) ||
|
|
ident.First() == char16_t('_');
|
|
|
|
}
|
|
|
|
already_AddRefed<css::Declaration>
|
|
CSSParserImpl::ParseStyleAttribute(const nsAString& aAttributeValue,
|
|
nsIURI* aDocURI,
|
|
nsIURI* aBaseURI,
|
|
nsIPrincipal* aNodePrincipal)
|
|
{
|
|
NS_PRECONDITION(aNodePrincipal, "Must have principal here!");
|
|
NS_PRECONDITION(aBaseURI, "need base URI");
|
|
|
|
// XXX line number?
|
|
nsCSSScanner scanner(aAttributeValue, 0);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURI);
|
|
InitScanner(scanner, reporter, aDocURI, aBaseURI, aNodePrincipal);
|
|
|
|
mSection = eCSSSection_General;
|
|
|
|
uint32_t parseFlags = eParseDeclaration_AllowImportant;
|
|
|
|
RefPtr<css::Declaration> declaration = ParseDeclarationBlock(parseFlags);
|
|
|
|
ReleaseScanner();
|
|
|
|
return declaration.forget();
|
|
}
|
|
|
|
nsresult
|
|
CSSParserImpl::ParseDeclarations(const nsAString& aBuffer,
|
|
nsIURI* aSheetURI,
|
|
nsIURI* aBaseURI,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
css::Declaration* aDeclaration,
|
|
bool* aChanged)
|
|
{
|
|
*aChanged = false;
|
|
|
|
NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
|
|
|
|
nsCSSScanner scanner(aBuffer, 0);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
|
|
InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
|
|
|
|
MOZ_ASSERT(mWebkitBoxUnprefixState == eNotParsingDecls,
|
|
"Someone forgot to clear mWebkitBoxUnprefixState!");
|
|
AutoRestore<WebkitBoxUnprefixState> autoRestore(mWebkitBoxUnprefixState);
|
|
mWebkitBoxUnprefixState = eHaveNotUnprefixed;
|
|
|
|
mSection = eCSSSection_General;
|
|
|
|
mData.AssertInitialState();
|
|
aDeclaration->ClearData();
|
|
// We could check if it was already empty, but...
|
|
*aChanged = true;
|
|
|
|
for (;;) {
|
|
// If we cleared the old decl, then we want to be calling
|
|
// ValueAppended as we parse.
|
|
if (!ParseDeclaration(aDeclaration, eParseDeclaration_AllowImportant,
|
|
true, aChanged)) {
|
|
if (!SkipDeclaration(false)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
aDeclaration->CompressFrom(&mData);
|
|
ReleaseScanner();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CSSParserImpl::ParseRule(const nsAString& aRule,
|
|
nsIURI* aSheetURI,
|
|
nsIURI* aBaseURI,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
css::Rule** aResult)
|
|
{
|
|
NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
|
|
NS_PRECONDITION(aBaseURI, "need base URI");
|
|
|
|
*aResult = nullptr;
|
|
|
|
nsCSSScanner scanner(aRule, 0);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
|
|
InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
|
|
|
|
mSection = eCSSSection_Charset; // callers are responsible for rejecting invalid rules.
|
|
|
|
nsCSSToken* tk = &mToken;
|
|
// Get first non-whitespace token
|
|
nsresult rv = NS_OK;
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED(PEParseRuleWSOnly);
|
|
OUTPUT_ERROR();
|
|
rv = NS_ERROR_DOM_SYNTAX_ERR;
|
|
} else {
|
|
if (eCSSToken_AtKeyword == tk->mType) {
|
|
// FIXME: perhaps aInsideBlock should be true when we are?
|
|
ParseAtRule(AssignRuleToPointer, aResult, false);
|
|
} else {
|
|
UngetToken();
|
|
ParseRuleSet(AssignRuleToPointer, aResult);
|
|
}
|
|
|
|
if (*aResult && GetToken(true)) {
|
|
// garbage after rule
|
|
REPORT_UNEXPECTED_TOKEN(PERuleTrailing);
|
|
NS_RELEASE(*aResult);
|
|
}
|
|
|
|
if (!*aResult) {
|
|
rv = NS_ERROR_DOM_SYNTAX_ERR;
|
|
OUTPUT_ERROR();
|
|
}
|
|
}
|
|
|
|
ReleaseScanner();
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::ParseLonghandProperty(const nsCSSPropertyID aPropID,
|
|
const nsAString& aPropValue,
|
|
nsIURI* aSheetURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
nsCSSValue& aValue)
|
|
{
|
|
MOZ_ASSERT(aPropID < eCSSProperty_COUNT_no_shorthands,
|
|
"ParseLonghandProperty must only take a longhand property");
|
|
|
|
RefPtr<css::Declaration> declaration = new css::Declaration;
|
|
declaration->InitializeEmpty();
|
|
|
|
bool changed;
|
|
ParseProperty(aPropID, aPropValue, aSheetURL, aBaseURL, aSheetPrincipal,
|
|
declaration, &changed,
|
|
/* aIsImportant */ false,
|
|
/* aIsSVGMode */ false);
|
|
|
|
if (changed) {
|
|
aValue = *declaration->GetNormalBlock()->ValueFor(aPropID);
|
|
} else {
|
|
aValue.Reset();
|
|
}
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseTransformProperty(const nsAString& aPropValue,
|
|
bool aDisallowRelativeValues,
|
|
nsCSSValue& aValue)
|
|
{
|
|
RefPtr<css::Declaration> declaration = new css::Declaration();
|
|
declaration->InitializeEmpty();
|
|
|
|
mData.AssertInitialState();
|
|
mTempData.AssertInitialState();
|
|
|
|
nsCSSScanner scanner(aPropValue, 0);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, nullptr);
|
|
InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
|
|
|
|
bool parsedOK = ParseTransform(false, aDisallowRelativeValues);
|
|
// We should now be at EOF
|
|
if (parsedOK && GetToken(true)) {
|
|
parsedOK = false;
|
|
}
|
|
|
|
bool changed = false;
|
|
if (parsedOK) {
|
|
declaration->ExpandTo(&mData);
|
|
changed = mData.TransferFromBlock(mTempData, eCSSProperty_transform,
|
|
EnabledState(), false,
|
|
true, false, declaration,
|
|
GetDocument());
|
|
declaration->CompressFrom(&mData);
|
|
}
|
|
|
|
if (changed) {
|
|
aValue = *declaration->GetNormalBlock()->ValueFor(eCSSProperty_transform);
|
|
} else {
|
|
aValue.Reset();
|
|
}
|
|
|
|
ReleaseScanner();
|
|
|
|
return parsedOK;
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::ParseProperty(const nsCSSPropertyID aPropID,
|
|
const nsAString& aPropValue,
|
|
nsIURI* aSheetURI,
|
|
nsIURI* aBaseURI,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
css::Declaration* aDeclaration,
|
|
bool* aChanged,
|
|
bool aIsImportant,
|
|
bool aIsSVGMode)
|
|
{
|
|
NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
|
|
NS_PRECONDITION(aBaseURI, "need base URI");
|
|
NS_PRECONDITION(aDeclaration, "Need declaration to parse into!");
|
|
MOZ_ASSERT(aPropID != eCSSPropertyExtra_variable);
|
|
|
|
mData.AssertInitialState();
|
|
mTempData.AssertInitialState();
|
|
aDeclaration->AssertMutable();
|
|
|
|
nsCSSScanner scanner(aPropValue, 0);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
|
|
InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
|
|
mSection = eCSSSection_General;
|
|
scanner.SetSVGMode(aIsSVGMode);
|
|
|
|
*aChanged = false;
|
|
|
|
// Check for unknown or preffed off properties
|
|
if (eCSSProperty_UNKNOWN == aPropID ||
|
|
!nsCSSProps::IsEnabled(aPropID, EnabledState())) {
|
|
NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropID));
|
|
REPORT_UNEXPECTED_P(PEUnknownProperty, propName);
|
|
REPORT_UNEXPECTED(PEDeclDropped);
|
|
OUTPUT_ERROR();
|
|
ReleaseScanner();
|
|
return;
|
|
}
|
|
|
|
bool parsedOK = ParseProperty(aPropID);
|
|
// We should now be at EOF
|
|
if (parsedOK && GetToken(true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
|
|
parsedOK = false;
|
|
}
|
|
|
|
if (!parsedOK) {
|
|
NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropID));
|
|
REPORT_UNEXPECTED_P(PEValueParsingError, propName);
|
|
REPORT_UNEXPECTED(PEDeclDropped);
|
|
OUTPUT_ERROR();
|
|
mTempData.ClearProperty(aPropID);
|
|
} else {
|
|
|
|
// We know we don't need to force a ValueAppended call for the new
|
|
// value. So if we are not processing a shorthand, and there's
|
|
// already a value for this property in the declaration at the
|
|
// same importance level, then we can just copy our parsed value
|
|
// directly into the declaration without going through the whole
|
|
// expand/compress thing.
|
|
if (!aDeclaration->TryReplaceValue(aPropID, aIsImportant, mTempData,
|
|
aChanged)) {
|
|
// Do it the slow way
|
|
aDeclaration->ExpandTo(&mData);
|
|
*aChanged = mData.TransferFromBlock(mTempData, aPropID,
|
|
EnabledState(), aIsImportant,
|
|
true, false, aDeclaration,
|
|
GetDocument());
|
|
aDeclaration->CompressFrom(&mData);
|
|
}
|
|
CLEAR_ERROR();
|
|
}
|
|
|
|
mTempData.AssertInitialState();
|
|
|
|
ReleaseScanner();
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::ParseVariable(const nsAString& aVariableName,
|
|
const nsAString& aPropValue,
|
|
nsIURI* aSheetURI,
|
|
nsIURI* aBaseURI,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
css::Declaration* aDeclaration,
|
|
bool* aChanged,
|
|
bool aIsImportant)
|
|
{
|
|
NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
|
|
NS_PRECONDITION(aBaseURI, "need base URI");
|
|
NS_PRECONDITION(aDeclaration, "Need declaration to parse into!");
|
|
NS_PRECONDITION(nsLayoutUtils::CSSVariablesEnabled(),
|
|
"expected Variables to be enabled");
|
|
|
|
mData.AssertInitialState();
|
|
mTempData.AssertInitialState();
|
|
aDeclaration->AssertMutable();
|
|
|
|
nsCSSScanner scanner(aPropValue, 0);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
|
|
InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
|
|
mSection = eCSSSection_General;
|
|
|
|
*aChanged = false;
|
|
|
|
CSSVariableDeclarations::Type variableType;
|
|
nsString variableValue;
|
|
|
|
bool parsedOK = ParseVariableDeclaration(&variableType, variableValue);
|
|
|
|
// We should now be at EOF
|
|
if (parsedOK && GetToken(true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
|
|
parsedOK = false;
|
|
}
|
|
|
|
if (!parsedOK) {
|
|
REPORT_UNEXPECTED_P(PEValueParsingError, NS_LITERAL_STRING("--") +
|
|
aVariableName);
|
|
REPORT_UNEXPECTED(PEDeclDropped);
|
|
OUTPUT_ERROR();
|
|
} else {
|
|
CLEAR_ERROR();
|
|
aDeclaration->AddVariableDeclaration(aVariableName, variableType,
|
|
variableValue, aIsImportant, true);
|
|
*aChanged = true;
|
|
}
|
|
|
|
mTempData.AssertInitialState();
|
|
|
|
ReleaseScanner();
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::ParseMediaList(const nsSubstring& aBuffer,
|
|
nsIURI* aURI, // for error reporting
|
|
uint32_t aLineNumber, // for error reporting
|
|
nsMediaList* aMediaList,
|
|
bool aHTMLMode)
|
|
{
|
|
// XXX Are there cases where the caller wants to keep what it already
|
|
// has in case of parser error? If GatherMedia ever changes to return
|
|
// a value other than true, we probably should avoid modifying aMediaList.
|
|
aMediaList->Clear();
|
|
|
|
// fake base URI since media lists don't have URIs in them
|
|
nsCSSScanner scanner(aBuffer, aLineNumber);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
|
|
InitScanner(scanner, reporter, aURI, aURI, nullptr);
|
|
|
|
mHTMLMediaMode = aHTMLMode;
|
|
|
|
// XXXldb We need to make the scanner not skip CSS comments! (Or
|
|
// should we?)
|
|
|
|
// For aHTMLMode, we used to follow the parsing rules in
|
|
// http://www.w3.org/TR/1999/REC-html401-19991224/types.html#type-media-descriptors
|
|
// which wouldn't work for media queries since they remove all but the
|
|
// first word. However, they're changed in
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/section-document.html#media2
|
|
// (as of 2008-05-29) which says that the media attribute just points
|
|
// to a media query. (The main substative difference is the relative
|
|
// precedence of commas and paretheses.)
|
|
|
|
DebugOnly<bool> parsedOK = GatherMedia(aMediaList, false);
|
|
NS_ASSERTION(parsedOK, "GatherMedia returned false; we probably want to avoid "
|
|
"trashing aMediaList");
|
|
|
|
CLEAR_ERROR();
|
|
ReleaseScanner();
|
|
mHTMLMediaMode = false;
|
|
}
|
|
|
|
// <source-size-list> = <source-size>#?
|
|
// <source-size> = <media-condition>? <length>
|
|
bool
|
|
CSSParserImpl::ParseSourceSizeList(const nsAString& aBuffer,
|
|
nsIURI* aURI, // for error reporting
|
|
uint32_t aLineNumber, // for error reporting
|
|
InfallibleTArray< nsAutoPtr<nsMediaQuery> >& aQueries,
|
|
InfallibleTArray<nsCSSValue>& aValues,
|
|
bool aHTMLMode)
|
|
{
|
|
aQueries.Clear();
|
|
aValues.Clear();
|
|
|
|
// fake base URI since media value lists don't have URIs in them
|
|
nsCSSScanner scanner(aBuffer, aLineNumber);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
|
|
InitScanner(scanner, reporter, aURI, aURI, nullptr);
|
|
|
|
// See ParseMediaList comment about HTML mode
|
|
mHTMLMediaMode = aHTMLMode;
|
|
|
|
// https://html.spec.whatwg.org/multipage/embedded-content.html#parse-a-sizes-attribute
|
|
bool hitEnd = false;
|
|
do {
|
|
bool hitError = false;
|
|
// Parse single <media-condition> <source-size-value>
|
|
do {
|
|
nsAutoPtr<nsMediaQuery> query;
|
|
nsCSSValue value;
|
|
|
|
bool hitStop;
|
|
if (!ParseMediaQuery(eMediaQuerySingleCondition, getter_Transfers(query),
|
|
&hitStop)) {
|
|
NS_ASSERTION(!hitStop, "should return true when hit stop");
|
|
hitError = true;
|
|
break;
|
|
}
|
|
|
|
if (!query) {
|
|
REPORT_UNEXPECTED_EOF(PEParseSourceSizeListEOF);
|
|
NS_ASSERTION(hitStop,
|
|
"should return hitStop or an error if returning no query");
|
|
hitError = true;
|
|
break;
|
|
}
|
|
|
|
if (hitStop) {
|
|
// Empty conditions (e.g. just a bare value) should be treated as always
|
|
// matching (a query with no expressions fails to match, so a negated one
|
|
// always matches.)
|
|
query->SetNegated();
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/embedded-content.html#source-size-value
|
|
// Percentages are not allowed in a <source-size-value>, to avoid
|
|
// confusion about what it would be relative to.
|
|
if (ParseNonNegativeVariant(value, VARIANT_LCALC, nullptr) !=
|
|
CSSParseResult::Ok) {
|
|
hitError = true;
|
|
break;
|
|
}
|
|
|
|
if (GetToken(true)) {
|
|
if (!mToken.IsSymbol(',')) {
|
|
REPORT_UNEXPECTED_TOKEN(PEParseSourceSizeListNotComma);
|
|
hitError = true;
|
|
break;
|
|
}
|
|
} else {
|
|
hitEnd = true;
|
|
}
|
|
|
|
aQueries.AppendElement(query.forget());
|
|
aValues.AppendElement(value);
|
|
} while(0);
|
|
|
|
if (hitError) {
|
|
OUTPUT_ERROR();
|
|
|
|
// Per spec, we just skip the current entry if there was a parse error.
|
|
// Jumps to next entry of <source-size-list> which is a comma-separated list.
|
|
if (!SkipUntil(',')) {
|
|
hitEnd = true;
|
|
}
|
|
}
|
|
} while (!hitEnd);
|
|
|
|
CLEAR_ERROR();
|
|
ReleaseScanner();
|
|
mHTMLMediaMode = false;
|
|
|
|
return !aQueries.IsEmpty();
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseColorString(const nsSubstring& aBuffer,
|
|
nsIURI* aURI, // for error reporting
|
|
uint32_t aLineNumber, // for error reporting
|
|
nsCSSValue& aValue,
|
|
bool aSuppressErrors /* false */)
|
|
{
|
|
nsCSSScanner scanner(aBuffer, aLineNumber);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
|
|
InitScanner(scanner, reporter, aURI, aURI, nullptr);
|
|
|
|
nsAutoSuppressErrors suppressErrors(this, aSuppressErrors);
|
|
|
|
// Parse a color, and check that there's nothing else after it.
|
|
bool colorParsed = ParseColor(aValue) == CSSParseResult::Ok &&
|
|
!GetToken(true);
|
|
|
|
if (aSuppressErrors) {
|
|
CLEAR_ERROR();
|
|
} else {
|
|
OUTPUT_ERROR();
|
|
}
|
|
|
|
ReleaseScanner();
|
|
return colorParsed;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseFontFamilyListString(const nsSubstring& aBuffer,
|
|
nsIURI* aURI, // for error reporting
|
|
uint32_t aLineNumber, // for error reporting
|
|
nsCSSValue& aValue)
|
|
{
|
|
nsCSSScanner scanner(aBuffer, aLineNumber);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
|
|
InitScanner(scanner, reporter, aURI, aURI, nullptr);
|
|
|
|
// Parse a font family list, and check that there's nothing else after it.
|
|
bool familyParsed = ParseFamily(aValue) && !GetToken(true);
|
|
OUTPUT_ERROR();
|
|
ReleaseScanner();
|
|
return familyParsed;
|
|
}
|
|
|
|
nsresult
|
|
CSSParserImpl::ParseSelectorString(const nsSubstring& aSelectorString,
|
|
nsIURI* aURI, // for error reporting
|
|
uint32_t aLineNumber, // for error reporting
|
|
nsCSSSelectorList **aSelectorList)
|
|
{
|
|
nsCSSScanner scanner(aSelectorString, aLineNumber);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
|
|
InitScanner(scanner, reporter, aURI, aURI, nullptr);
|
|
|
|
bool success = ParseSelectorList(*aSelectorList, char16_t(0));
|
|
|
|
// We deliberately do not call OUTPUT_ERROR here, because all our
|
|
// callers map a failure return to a JS exception, and if that JS
|
|
// exception is caught, people don't want to see parser diagnostics;
|
|
// see e.g. http://bugs.jquery.com/ticket/7535
|
|
// It would be nice to be able to save the parser diagnostics into
|
|
// the exception, so that if it _isn't_ caught we can report them
|
|
// along with the usual uncaught-exception message, but we don't
|
|
// have any way to do that at present; see bug 631621.
|
|
CLEAR_ERROR();
|
|
ReleaseScanner();
|
|
|
|
if (success) {
|
|
NS_ASSERTION(*aSelectorList, "Should have list!");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_ASSERTION(!*aSelectorList, "Shouldn't have list!");
|
|
|
|
return NS_ERROR_DOM_SYNTAX_ERR;
|
|
}
|
|
|
|
|
|
already_AddRefed<nsCSSKeyframeRule>
|
|
CSSParserImpl::ParseKeyframeRule(const nsSubstring& aBuffer,
|
|
nsIURI* aURI,
|
|
uint32_t aLineNumber)
|
|
{
|
|
nsCSSScanner scanner(aBuffer, aLineNumber);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
|
|
InitScanner(scanner, reporter, aURI, aURI, nullptr);
|
|
|
|
RefPtr<nsCSSKeyframeRule> result = ParseKeyframeRule();
|
|
if (GetToken(true)) {
|
|
// extra garbage at the end
|
|
result = nullptr;
|
|
}
|
|
|
|
OUTPUT_ERROR();
|
|
ReleaseScanner();
|
|
|
|
return result.forget();
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseKeyframeSelectorString(const nsSubstring& aSelectorString,
|
|
nsIURI* aURI, // for error reporting
|
|
uint32_t aLineNumber, // for error reporting
|
|
InfallibleTArray<float>& aSelectorList)
|
|
{
|
|
MOZ_ASSERT(aSelectorList.IsEmpty(), "given list should start empty");
|
|
|
|
nsCSSScanner scanner(aSelectorString, aLineNumber);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
|
|
InitScanner(scanner, reporter, aURI, aURI, nullptr);
|
|
|
|
bool success = ParseKeyframeSelectorList(aSelectorList) &&
|
|
// must consume entire input string
|
|
!GetToken(true);
|
|
|
|
OUTPUT_ERROR();
|
|
ReleaseScanner();
|
|
|
|
if (success) {
|
|
NS_ASSERTION(!aSelectorList.IsEmpty(), "should not be empty");
|
|
} else {
|
|
aSelectorList.Clear();
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::EvaluateSupportsDeclaration(const nsAString& aProperty,
|
|
const nsAString& aValue,
|
|
nsIURI* aDocURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aDocPrincipal)
|
|
{
|
|
nsCSSPropertyID propID = LookupEnabledProperty(aProperty);
|
|
if (propID == eCSSProperty_UNKNOWN) {
|
|
return false;
|
|
}
|
|
|
|
nsCSSScanner scanner(aValue, 0);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL);
|
|
InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
|
|
nsAutoSuppressErrors suppressErrors(this);
|
|
|
|
bool parsedOK;
|
|
|
|
if (propID == eCSSPropertyExtra_variable) {
|
|
MOZ_ASSERT(Substring(aProperty, 0,
|
|
CSS_CUSTOM_NAME_PREFIX_LENGTH).EqualsLiteral("--"));
|
|
const nsDependentSubstring varName =
|
|
Substring(aProperty, CSS_CUSTOM_NAME_PREFIX_LENGTH); // remove '--'
|
|
CSSVariableDeclarations::Type variableType;
|
|
nsString variableValue;
|
|
parsedOK = ParseVariableDeclaration(&variableType, variableValue) &&
|
|
!GetToken(true);
|
|
} else {
|
|
parsedOK = ParseProperty(propID) && !GetToken(true);
|
|
|
|
mTempData.ClearProperty(propID);
|
|
mTempData.AssertInitialState();
|
|
}
|
|
|
|
CLEAR_ERROR();
|
|
ReleaseScanner();
|
|
|
|
return parsedOK;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::EvaluateSupportsCondition(const nsAString& aDeclaration,
|
|
nsIURI* aDocURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aDocPrincipal)
|
|
{
|
|
nsCSSScanner scanner(aDeclaration, 0);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL);
|
|
InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
|
|
nsAutoSuppressErrors suppressErrors(this);
|
|
|
|
bool conditionMet;
|
|
bool parsedOK = ParseSupportsCondition(conditionMet) && !GetToken(true);
|
|
|
|
CLEAR_ERROR();
|
|
ReleaseScanner();
|
|
|
|
return parsedOK && conditionMet;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::EnumerateVariableReferences(const nsAString& aPropertyValue,
|
|
VariableEnumFunc aFunc,
|
|
void* aData)
|
|
{
|
|
nsCSSScanner scanner(aPropertyValue, 0);
|
|
css::ErrorReporter reporter(scanner, nullptr, nullptr, nullptr);
|
|
InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
|
|
nsAutoSuppressErrors suppressErrors(this);
|
|
|
|
CSSVariableDeclarations::Type type;
|
|
bool dropBackslash;
|
|
nsString impliedCharacters;
|
|
bool result = ParseValueWithVariables(&type, &dropBackslash,
|
|
impliedCharacters, aFunc, aData) &&
|
|
!GetToken(true);
|
|
|
|
ReleaseScanner();
|
|
|
|
return result;
|
|
}
|
|
|
|
static bool
|
|
SeparatorRequiredBetweenTokens(nsCSSTokenSerializationType aToken1,
|
|
nsCSSTokenSerializationType aToken2)
|
|
{
|
|
// The two lines marked with (*) do not correspond to entries in
|
|
// the table in the css-syntax spec but which we need to handle,
|
|
// as we treat them as whole tokens.
|
|
switch (aToken1) {
|
|
case eCSSTokenSerialization_Ident:
|
|
return aToken2 == eCSSTokenSerialization_Ident ||
|
|
aToken2 == eCSSTokenSerialization_Function ||
|
|
aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
|
|
aToken2 == eCSSTokenSerialization_Symbol_Minus ||
|
|
aToken2 == eCSSTokenSerialization_Number ||
|
|
aToken2 == eCSSTokenSerialization_Percentage ||
|
|
aToken2 == eCSSTokenSerialization_Dimension ||
|
|
aToken2 == eCSSTokenSerialization_URange ||
|
|
aToken2 == eCSSTokenSerialization_CDC ||
|
|
aToken2 == eCSSTokenSerialization_Symbol_OpenParen;
|
|
case eCSSTokenSerialization_AtKeyword_or_Hash:
|
|
case eCSSTokenSerialization_Dimension:
|
|
return aToken2 == eCSSTokenSerialization_Ident ||
|
|
aToken2 == eCSSTokenSerialization_Function ||
|
|
aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
|
|
aToken2 == eCSSTokenSerialization_Symbol_Minus ||
|
|
aToken2 == eCSSTokenSerialization_Number ||
|
|
aToken2 == eCSSTokenSerialization_Percentage ||
|
|
aToken2 == eCSSTokenSerialization_Dimension ||
|
|
aToken2 == eCSSTokenSerialization_URange ||
|
|
aToken2 == eCSSTokenSerialization_CDC;
|
|
case eCSSTokenSerialization_Symbol_Hash:
|
|
case eCSSTokenSerialization_Symbol_Minus:
|
|
return aToken2 == eCSSTokenSerialization_Ident ||
|
|
aToken2 == eCSSTokenSerialization_Function ||
|
|
aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
|
|
aToken2 == eCSSTokenSerialization_Symbol_Minus ||
|
|
aToken2 == eCSSTokenSerialization_Number ||
|
|
aToken2 == eCSSTokenSerialization_Percentage ||
|
|
aToken2 == eCSSTokenSerialization_Dimension ||
|
|
aToken2 == eCSSTokenSerialization_URange;
|
|
case eCSSTokenSerialization_Number:
|
|
return aToken2 == eCSSTokenSerialization_Ident ||
|
|
aToken2 == eCSSTokenSerialization_Function ||
|
|
aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
|
|
aToken2 == eCSSTokenSerialization_Number ||
|
|
aToken2 == eCSSTokenSerialization_Percentage ||
|
|
aToken2 == eCSSTokenSerialization_Dimension ||
|
|
aToken2 == eCSSTokenSerialization_URange;
|
|
case eCSSTokenSerialization_Symbol_At:
|
|
return aToken2 == eCSSTokenSerialization_Ident ||
|
|
aToken2 == eCSSTokenSerialization_Function ||
|
|
aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
|
|
aToken2 == eCSSTokenSerialization_Symbol_Minus ||
|
|
aToken2 == eCSSTokenSerialization_URange;
|
|
case eCSSTokenSerialization_URange:
|
|
return aToken2 == eCSSTokenSerialization_Ident ||
|
|
aToken2 == eCSSTokenSerialization_Function ||
|
|
aToken2 == eCSSTokenSerialization_Number ||
|
|
aToken2 == eCSSTokenSerialization_Percentage ||
|
|
aToken2 == eCSSTokenSerialization_Dimension ||
|
|
aToken2 == eCSSTokenSerialization_Symbol_Question;
|
|
case eCSSTokenSerialization_Symbol_Dot_or_Plus:
|
|
return aToken2 == eCSSTokenSerialization_Number ||
|
|
aToken2 == eCSSTokenSerialization_Percentage ||
|
|
aToken2 == eCSSTokenSerialization_Dimension;
|
|
case eCSSTokenSerialization_Symbol_Assorted:
|
|
case eCSSTokenSerialization_Symbol_Asterisk:
|
|
return aToken2 == eCSSTokenSerialization_Symbol_Equals;
|
|
case eCSSTokenSerialization_Symbol_Bar:
|
|
return aToken2 == eCSSTokenSerialization_Symbol_Equals ||
|
|
aToken2 == eCSSTokenSerialization_Symbol_Bar ||
|
|
aToken2 == eCSSTokenSerialization_DashMatch; // (*)
|
|
case eCSSTokenSerialization_Symbol_Slash:
|
|
return aToken2 == eCSSTokenSerialization_Symbol_Asterisk ||
|
|
aToken2 == eCSSTokenSerialization_ContainsMatch; // (*)
|
|
default:
|
|
MOZ_ASSERT(aToken1 == eCSSTokenSerialization_Nothing ||
|
|
aToken1 == eCSSTokenSerialization_Whitespace ||
|
|
aToken1 == eCSSTokenSerialization_Percentage ||
|
|
aToken1 == eCSSTokenSerialization_URL_or_BadURL ||
|
|
aToken1 == eCSSTokenSerialization_Function ||
|
|
aToken1 == eCSSTokenSerialization_CDC ||
|
|
aToken1 == eCSSTokenSerialization_Symbol_OpenParen ||
|
|
aToken1 == eCSSTokenSerialization_Symbol_Question ||
|
|
aToken1 == eCSSTokenSerialization_Symbol_Assorted ||
|
|
aToken1 == eCSSTokenSerialization_Symbol_Asterisk ||
|
|
aToken1 == eCSSTokenSerialization_Symbol_Equals ||
|
|
aToken1 == eCSSTokenSerialization_Symbol_Bar ||
|
|
aToken1 == eCSSTokenSerialization_Symbol_Slash ||
|
|
aToken1 == eCSSTokenSerialization_Other,
|
|
"unexpected nsCSSTokenSerializationType value");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Appends aValue to aResult, possibly inserting an empty CSS
|
|
* comment between the two to ensure that tokens from both strings
|
|
* remain separated.
|
|
*/
|
|
static void
|
|
AppendTokens(nsAString& aResult,
|
|
nsCSSTokenSerializationType& aResultFirstToken,
|
|
nsCSSTokenSerializationType& aResultLastToken,
|
|
nsCSSTokenSerializationType aValueFirstToken,
|
|
nsCSSTokenSerializationType aValueLastToken,
|
|
const nsAString& aValue)
|
|
{
|
|
if (SeparatorRequiredBetweenTokens(aResultLastToken, aValueFirstToken)) {
|
|
aResult.AppendLiteral("/**/");
|
|
}
|
|
aResult.Append(aValue);
|
|
if (aResultFirstToken == eCSSTokenSerialization_Nothing) {
|
|
aResultFirstToken = aValueFirstToken;
|
|
}
|
|
if (aValueLastToken != eCSSTokenSerialization_Nothing) {
|
|
aResultLastToken = aValueLastToken;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stops the given scanner recording, and appends the recorded result
|
|
* to aResult, possibly inserting an empty CSS comment between the two to
|
|
* ensure that tokens from both strings remain separated.
|
|
*/
|
|
static void
|
|
StopRecordingAndAppendTokens(nsString& aResult,
|
|
nsCSSTokenSerializationType& aResultFirstToken,
|
|
nsCSSTokenSerializationType& aResultLastToken,
|
|
nsCSSTokenSerializationType aValueFirstToken,
|
|
nsCSSTokenSerializationType aValueLastToken,
|
|
nsCSSScanner* aScanner)
|
|
{
|
|
if (SeparatorRequiredBetweenTokens(aResultLastToken, aValueFirstToken)) {
|
|
aResult.AppendLiteral("/**/");
|
|
}
|
|
aScanner->StopRecording(aResult);
|
|
if (aResultFirstToken == eCSSTokenSerialization_Nothing) {
|
|
aResultFirstToken = aValueFirstToken;
|
|
}
|
|
if (aValueLastToken != eCSSTokenSerialization_Nothing) {
|
|
aResultLastToken = aValueLastToken;
|
|
}
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ResolveValueWithVariableReferencesRec(
|
|
nsString& aResult,
|
|
nsCSSTokenSerializationType& aResultFirstToken,
|
|
nsCSSTokenSerializationType& aResultLastToken,
|
|
const CSSVariableValues* aVariables)
|
|
{
|
|
// This function assumes we are already recording, and will leave the scanner
|
|
// recording when it returns.
|
|
MOZ_ASSERT(mScanner->IsRecording());
|
|
MOZ_ASSERT(aResult.IsEmpty());
|
|
|
|
// Stack of closing characters for currently open constructs.
|
|
AutoTArray<char16_t, 16> stack;
|
|
|
|
// The resolved value for this ResolveValueWithVariableReferencesRec call.
|
|
nsString value;
|
|
|
|
// The length of the scanner's recording before the currently parsed token.
|
|
// This is used so that when we encounter a "var(" token, we can strip
|
|
// it off the end of the recording, regardless of how long the token was.
|
|
// (With escapes, it could be longer than four characters.)
|
|
uint32_t lengthBeforeVar = 0;
|
|
|
|
// Tracking the type of token that appears at the start and end of |value|
|
|
// and that appears at the start and end of the scanner recording. These are
|
|
// used to determine whether we need to insert "/**/" when pasting token
|
|
// streams together.
|
|
nsCSSTokenSerializationType valueFirstToken = eCSSTokenSerialization_Nothing,
|
|
valueLastToken = eCSSTokenSerialization_Nothing,
|
|
recFirstToken = eCSSTokenSerialization_Nothing,
|
|
recLastToken = eCSSTokenSerialization_Nothing;
|
|
|
|
#define UPDATE_RECORDING_TOKENS(type) \
|
|
if (recFirstToken == eCSSTokenSerialization_Nothing) { \
|
|
recFirstToken = type; \
|
|
} \
|
|
recLastToken = type;
|
|
|
|
while (GetToken(false)) {
|
|
switch (mToken.mType) {
|
|
case eCSSToken_Symbol: {
|
|
nsCSSTokenSerializationType type = eCSSTokenSerialization_Other;
|
|
if (mToken.mSymbol == '(') {
|
|
stack.AppendElement(')');
|
|
type = eCSSTokenSerialization_Symbol_OpenParen;
|
|
} else if (mToken.mSymbol == '[') {
|
|
stack.AppendElement(']');
|
|
} else if (mToken.mSymbol == '{') {
|
|
stack.AppendElement('}');
|
|
} else if (mToken.mSymbol == ';') {
|
|
if (stack.IsEmpty()) {
|
|
// A ';' that is at the top level of the value or at the top level
|
|
// of a variable reference's fallback is invalid.
|
|
return false;
|
|
}
|
|
} else if (mToken.mSymbol == '!') {
|
|
if (stack.IsEmpty()) {
|
|
// An '!' that is at the top level of the value or at the top level
|
|
// of a variable reference's fallback is invalid.
|
|
return false;
|
|
}
|
|
} else if (mToken.mSymbol == ')' &&
|
|
stack.IsEmpty()) {
|
|
// We're closing a "var(".
|
|
nsString finalTokens;
|
|
mScanner->StopRecording(finalTokens);
|
|
MOZ_ASSERT(finalTokens[finalTokens.Length() - 1] == ')');
|
|
finalTokens.Truncate(finalTokens.Length() - 1);
|
|
aResult.Append(value);
|
|
|
|
AppendTokens(aResult, valueFirstToken, valueLastToken,
|
|
recFirstToken, recLastToken, finalTokens);
|
|
|
|
mScanner->StartRecording();
|
|
UngetToken();
|
|
aResultFirstToken = valueFirstToken;
|
|
aResultLastToken = valueLastToken;
|
|
return true;
|
|
} else if (mToken.mSymbol == ')' ||
|
|
mToken.mSymbol == ']' ||
|
|
mToken.mSymbol == '}') {
|
|
if (stack.IsEmpty() ||
|
|
stack.LastElement() != mToken.mSymbol) {
|
|
// A mismatched closing bracket is invalid.
|
|
return false;
|
|
}
|
|
stack.TruncateLength(stack.Length() - 1);
|
|
} else if (mToken.mSymbol == '#') {
|
|
type = eCSSTokenSerialization_Symbol_Hash;
|
|
} else if (mToken.mSymbol == '@') {
|
|
type = eCSSTokenSerialization_Symbol_At;
|
|
} else if (mToken.mSymbol == '.' ||
|
|
mToken.mSymbol == '+') {
|
|
type = eCSSTokenSerialization_Symbol_Dot_or_Plus;
|
|
} else if (mToken.mSymbol == '-') {
|
|
type = eCSSTokenSerialization_Symbol_Minus;
|
|
} else if (mToken.mSymbol == '?') {
|
|
type = eCSSTokenSerialization_Symbol_Question;
|
|
} else if (mToken.mSymbol == '$' ||
|
|
mToken.mSymbol == '^' ||
|
|
mToken.mSymbol == '~') {
|
|
type = eCSSTokenSerialization_Symbol_Assorted;
|
|
} else if (mToken.mSymbol == '=') {
|
|
type = eCSSTokenSerialization_Symbol_Equals;
|
|
} else if (mToken.mSymbol == '|') {
|
|
type = eCSSTokenSerialization_Symbol_Bar;
|
|
} else if (mToken.mSymbol == '/') {
|
|
type = eCSSTokenSerialization_Symbol_Slash;
|
|
} else if (mToken.mSymbol == '*') {
|
|
type = eCSSTokenSerialization_Symbol_Asterisk;
|
|
}
|
|
UPDATE_RECORDING_TOKENS(type);
|
|
break;
|
|
}
|
|
|
|
case eCSSToken_Function:
|
|
if (mToken.mIdent.LowerCaseEqualsLiteral("var")) {
|
|
// Save the tokens before the "var(" to our resolved value.
|
|
nsString recording;
|
|
mScanner->StopRecording(recording);
|
|
recording.Truncate(lengthBeforeVar);
|
|
AppendTokens(value, valueFirstToken, valueLastToken,
|
|
recFirstToken, recLastToken, recording);
|
|
recFirstToken = eCSSTokenSerialization_Nothing;
|
|
recLastToken = eCSSTokenSerialization_Nothing;
|
|
|
|
if (!GetToken(true) ||
|
|
mToken.mType != eCSSToken_Ident ||
|
|
!nsCSSProps::IsCustomPropertyName(mToken.mIdent)) {
|
|
// "var(" must be followed by an identifier, and it must be a
|
|
// custom property name.
|
|
return false;
|
|
}
|
|
|
|
// Turn the custom property name into a variable name by removing the
|
|
// '--' prefix.
|
|
MOZ_ASSERT(Substring(mToken.mIdent, 0,
|
|
CSS_CUSTOM_NAME_PREFIX_LENGTH).
|
|
EqualsLiteral("--"));
|
|
nsDependentString variableName(mToken.mIdent,
|
|
CSS_CUSTOM_NAME_PREFIX_LENGTH);
|
|
|
|
// Get the value of the identified variable. Note that we
|
|
// check if the variable value is the empty string, as that means
|
|
// that the variable was invalid at computed value time due to
|
|
// unresolveable variable references or cycles.
|
|
nsString variableValue;
|
|
nsCSSTokenSerializationType varFirstToken, varLastToken;
|
|
bool valid = aVariables->Get(variableName, variableValue,
|
|
varFirstToken, varLastToken) &&
|
|
!variableValue.IsEmpty();
|
|
|
|
if (!GetToken(true) ||
|
|
mToken.IsSymbol(')')) {
|
|
mScanner->StartRecording();
|
|
if (!valid) {
|
|
// Invalid variable with no fallback.
|
|
return false;
|
|
}
|
|
// Valid variable with no fallback.
|
|
AppendTokens(value, valueFirstToken, valueLastToken,
|
|
varFirstToken, varLastToken, variableValue);
|
|
} else if (mToken.IsSymbol(',')) {
|
|
mScanner->StartRecording();
|
|
if (!GetToken(false) ||
|
|
mToken.IsSymbol(')')) {
|
|
// Comma must be followed by at least one fallback token.
|
|
return false;
|
|
}
|
|
UngetToken();
|
|
if (valid) {
|
|
// Valid variable with ignored fallback.
|
|
mScanner->StopRecording();
|
|
AppendTokens(value, valueFirstToken, valueLastToken,
|
|
varFirstToken, varLastToken, variableValue);
|
|
bool ok = SkipBalancedContentUntil(')');
|
|
mScanner->StartRecording();
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
} else {
|
|
nsString fallback;
|
|
if (!ResolveValueWithVariableReferencesRec(fallback,
|
|
varFirstToken,
|
|
varLastToken,
|
|
aVariables)) {
|
|
// Fallback value had invalid tokens or an invalid variable reference
|
|
// that itself had no fallback.
|
|
return false;
|
|
}
|
|
AppendTokens(value, valueFirstToken, valueLastToken,
|
|
varFirstToken, varLastToken, fallback);
|
|
// Now we're either at the pushed back ')' that finished the
|
|
// fallback or at EOF.
|
|
DebugOnly<bool> gotToken = GetToken(false);
|
|
MOZ_ASSERT(!gotToken || mToken.IsSymbol(')'));
|
|
}
|
|
} else {
|
|
// Expected ',' or ')' after the variable name.
|
|
mScanner->StartRecording();
|
|
return false;
|
|
}
|
|
} else {
|
|
stack.AppendElement(')');
|
|
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Function);
|
|
}
|
|
break;
|
|
|
|
case eCSSToken_Bad_String:
|
|
case eCSSToken_Bad_URL:
|
|
return false;
|
|
|
|
case eCSSToken_Whitespace:
|
|
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Whitespace);
|
|
break;
|
|
|
|
case eCSSToken_AtKeyword:
|
|
case eCSSToken_Hash:
|
|
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_AtKeyword_or_Hash);
|
|
break;
|
|
|
|
case eCSSToken_Number:
|
|
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Number);
|
|
break;
|
|
|
|
case eCSSToken_Dimension:
|
|
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Dimension);
|
|
break;
|
|
|
|
case eCSSToken_Ident:
|
|
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Ident);
|
|
break;
|
|
|
|
case eCSSToken_Percentage:
|
|
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Percentage);
|
|
break;
|
|
|
|
case eCSSToken_URange:
|
|
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_URange);
|
|
break;
|
|
|
|
case eCSSToken_URL:
|
|
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_URL_or_BadURL);
|
|
break;
|
|
|
|
case eCSSToken_HTMLComment:
|
|
if (mToken.mIdent[0] == '-') {
|
|
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_CDC);
|
|
} else {
|
|
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Other);
|
|
}
|
|
break;
|
|
|
|
case eCSSToken_Dashmatch:
|
|
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_DashMatch);
|
|
break;
|
|
|
|
case eCSSToken_Containsmatch:
|
|
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_ContainsMatch);
|
|
break;
|
|
|
|
default:
|
|
MOZ_FALLTHROUGH_ASSERT("unexpected token type");
|
|
case eCSSToken_ID:
|
|
case eCSSToken_String:
|
|
case eCSSToken_Includes:
|
|
case eCSSToken_Beginsmatch:
|
|
case eCSSToken_Endsmatch:
|
|
UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Other);
|
|
break;
|
|
}
|
|
|
|
lengthBeforeVar = mScanner->RecordingLength();
|
|
}
|
|
|
|
#undef UPDATE_RECORDING_TOKENS
|
|
|
|
aResult.Append(value);
|
|
StopRecordingAndAppendTokens(aResult, valueFirstToken, valueLastToken,
|
|
recFirstToken, recLastToken, mScanner);
|
|
|
|
// Append any implicitly closed brackets.
|
|
if (!stack.IsEmpty()) {
|
|
do {
|
|
aResult.Append(stack.LastElement());
|
|
stack.TruncateLength(stack.Length() - 1);
|
|
} while (!stack.IsEmpty());
|
|
valueLastToken = eCSSTokenSerialization_Other;
|
|
}
|
|
|
|
mScanner->StartRecording();
|
|
aResultFirstToken = valueFirstToken;
|
|
aResultLastToken = valueLastToken;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ResolveValueWithVariableReferences(
|
|
const CSSVariableValues* aVariables,
|
|
nsString& aResult,
|
|
nsCSSTokenSerializationType& aFirstToken,
|
|
nsCSSTokenSerializationType& aLastToken)
|
|
{
|
|
aResult.Truncate(0);
|
|
|
|
// Start recording before we read the first token.
|
|
mScanner->StartRecording();
|
|
|
|
if (!GetToken(false)) {
|
|
// Value was empty since we reached EOF.
|
|
mScanner->StopRecording();
|
|
return false;
|
|
}
|
|
|
|
UngetToken();
|
|
|
|
nsString value;
|
|
nsCSSTokenSerializationType firstToken, lastToken;
|
|
bool ok = ResolveValueWithVariableReferencesRec(value, firstToken, lastToken, aVariables) &&
|
|
!GetToken(true);
|
|
|
|
mScanner->StopRecording();
|
|
|
|
if (ok) {
|
|
aResult = value;
|
|
aFirstToken = firstToken;
|
|
aLastToken = lastToken;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ResolveVariableValue(const nsAString& aPropertyValue,
|
|
const CSSVariableValues* aVariables,
|
|
nsString& aResult,
|
|
nsCSSTokenSerializationType& aFirstToken,
|
|
nsCSSTokenSerializationType& aLastToken)
|
|
{
|
|
nsCSSScanner scanner(aPropertyValue, 0);
|
|
|
|
// At this point, we know that aPropertyValue is syntactically correct
|
|
// for a token stream that has variable references. We also won't be
|
|
// interpreting any of the stream as we parse it, apart from expanding
|
|
// var() references, so we don't need a base URL etc. or any useful
|
|
// error reporting.
|
|
css::ErrorReporter reporter(scanner, nullptr, nullptr, nullptr);
|
|
InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
|
|
|
|
bool valid = ResolveValueWithVariableReferences(aVariables, aResult,
|
|
aFirstToken, aLastToken);
|
|
|
|
ReleaseScanner();
|
|
return valid;
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::ParsePropertyWithVariableReferences(
|
|
nsCSSPropertyID aPropertyID,
|
|
nsCSSPropertyID aShorthandPropertyID,
|
|
const nsAString& aValue,
|
|
const CSSVariableValues* aVariables,
|
|
nsRuleData* aRuleData,
|
|
nsIURI* aDocURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aDocPrincipal,
|
|
CSSStyleSheet* aSheet,
|
|
uint32_t aLineNumber,
|
|
uint32_t aLineOffset)
|
|
{
|
|
mTempData.AssertInitialState();
|
|
|
|
bool valid;
|
|
nsString expandedValue;
|
|
|
|
// Resolve any variable references in the property value.
|
|
{
|
|
nsCSSScanner scanner(aValue, 0);
|
|
css::ErrorReporter reporter(scanner, aSheet, mChildLoader, aDocURL);
|
|
InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
|
|
|
|
nsCSSTokenSerializationType firstToken, lastToken;
|
|
valid = ResolveValueWithVariableReferences(aVariables, expandedValue,
|
|
firstToken, lastToken);
|
|
if (!valid) {
|
|
NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropertyID));
|
|
REPORT_UNEXPECTED(PEInvalidVariableReference);
|
|
REPORT_UNEXPECTED_P(PEValueParsingError, propName);
|
|
if (nsCSSProps::IsInherited(aPropertyID)) {
|
|
REPORT_UNEXPECTED(PEValueWithVariablesFallbackInherit);
|
|
} else {
|
|
REPORT_UNEXPECTED(PEValueWithVariablesFallbackInitial);
|
|
}
|
|
OUTPUT_ERROR_WITH_POSITION(aLineNumber, aLineOffset);
|
|
}
|
|
ReleaseScanner();
|
|
}
|
|
|
|
nsCSSPropertyID propertyToParse =
|
|
aShorthandPropertyID != eCSSProperty_UNKNOWN ? aShorthandPropertyID :
|
|
aPropertyID;
|
|
|
|
// Parse the property with that resolved value.
|
|
if (valid) {
|
|
nsCSSScanner scanner(expandedValue, 0);
|
|
css::ErrorReporter reporter(scanner, aSheet, mChildLoader, aDocURL);
|
|
InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
|
|
valid = ParseProperty(propertyToParse);
|
|
if (valid && GetToken(true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
|
|
valid = false;
|
|
}
|
|
if (!valid) {
|
|
NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(
|
|
propertyToParse));
|
|
REPORT_UNEXPECTED_P_V(PEValueWithVariablesParsingErrorInValue,
|
|
propName, expandedValue);
|
|
if (nsCSSProps::IsInherited(aPropertyID)) {
|
|
REPORT_UNEXPECTED(PEValueWithVariablesFallbackInherit);
|
|
} else {
|
|
REPORT_UNEXPECTED(PEValueWithVariablesFallbackInitial);
|
|
}
|
|
OUTPUT_ERROR_WITH_POSITION(aLineNumber, aLineOffset);
|
|
}
|
|
ReleaseScanner();
|
|
}
|
|
|
|
// If the property could not be parsed with the resolved value, then we
|
|
// treat it as if the value were 'initial' or 'inherit', depending on whether
|
|
// the property is an inherited property.
|
|
if (!valid) {
|
|
nsCSSValue defaultValue;
|
|
if (nsCSSProps::IsInherited(aPropertyID)) {
|
|
defaultValue.SetInheritValue();
|
|
} else {
|
|
defaultValue.SetInitialValue();
|
|
}
|
|
mTempData.AddLonghandProperty(aPropertyID, defaultValue);
|
|
}
|
|
|
|
// Copy the property value into the rule data.
|
|
mTempData.MapRuleInfoInto(aPropertyID, aRuleData);
|
|
|
|
mTempData.ClearProperty(propertyToParse);
|
|
mTempData.AssertInitialState();
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseCounterStyleName(const nsAString& aBuffer,
|
|
nsIURI* aURL,
|
|
nsAString& aName)
|
|
{
|
|
nsCSSScanner scanner(aBuffer, 0);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURL);
|
|
InitScanner(scanner, reporter, aURL, aURL, nullptr);
|
|
|
|
bool success = ParseCounterStyleName(aName, true) && !GetToken(true);
|
|
|
|
OUTPUT_ERROR();
|
|
ReleaseScanner();
|
|
|
|
return success;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseCounterDescriptor(nsCSSCounterDesc aDescID,
|
|
const nsAString& aBuffer,
|
|
nsIURI* aSheetURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
nsCSSValue& aValue)
|
|
{
|
|
nsCSSScanner scanner(aBuffer, 0);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURL);
|
|
InitScanner(scanner, reporter, aSheetURL, aBaseURL, aSheetPrincipal);
|
|
|
|
bool success = ParseCounterDescriptorValue(aDescID, aValue) &&
|
|
!GetToken(true);
|
|
|
|
OUTPUT_ERROR();
|
|
ReleaseScanner();
|
|
|
|
return success;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseFontFaceDescriptor(nsCSSFontDesc aDescID,
|
|
const nsAString& aBuffer,
|
|
nsIURI* aSheetURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
nsCSSValue& aValue)
|
|
{
|
|
nsCSSScanner scanner(aBuffer, 0);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURL);
|
|
InitScanner(scanner, reporter, aSheetURL, aBaseURL, aSheetPrincipal);
|
|
|
|
bool success = ParseFontDescriptorValue(aDescID, aValue) &&
|
|
!GetToken(true);
|
|
|
|
OUTPUT_ERROR();
|
|
ReleaseScanner();
|
|
|
|
return success;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
bool
|
|
CSSParserImpl::GetToken(bool aSkipWS)
|
|
{
|
|
if (mHavePushBack) {
|
|
mHavePushBack = false;
|
|
if (!aSkipWS || mToken.mType != eCSSToken_Whitespace) {
|
|
return true;
|
|
}
|
|
}
|
|
return mScanner->Next(mToken, aSkipWS ?
|
|
eCSSScannerExclude_WhitespaceAndComments :
|
|
eCSSScannerExclude_Comments);
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::UngetToken()
|
|
{
|
|
NS_PRECONDITION(!mHavePushBack, "double pushback");
|
|
mHavePushBack = true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::GetNextTokenLocation(bool aSkipWS, uint32_t *linenum, uint32_t *colnum)
|
|
{
|
|
// Peek at next token so that mScanner updates line and column vals
|
|
if (!GetToken(aSkipWS)) {
|
|
return false;
|
|
}
|
|
UngetToken();
|
|
// The scanner uses one-indexing for line numbers but zero-indexing
|
|
// for column numbers.
|
|
*linenum = mScanner->GetLineNumber();
|
|
*colnum = 1 + mScanner->GetColumnNumber();
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ExpectSymbol(char16_t aSymbol,
|
|
bool aSkipWS)
|
|
{
|
|
if (!GetToken(aSkipWS)) {
|
|
// CSS2.1 specifies that all "open constructs" are to be closed at
|
|
// EOF. It simplifies higher layers if we claim to have found an
|
|
// ), ], }, or ; if we encounter EOF while looking for one of them.
|
|
// Do still issue a diagnostic, to aid debugging.
|
|
if (aSymbol == ')' || aSymbol == ']' ||
|
|
aSymbol == '}' || aSymbol == ';') {
|
|
REPORT_UNEXPECTED_EOF_CHAR(aSymbol);
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
if (mToken.IsSymbol(aSymbol)) {
|
|
return true;
|
|
}
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
// Checks to see if we're at the end of a property. If an error occurs during
|
|
// the check, does not signal a parse error.
|
|
bool
|
|
CSSParserImpl::CheckEndProperty()
|
|
{
|
|
if (!GetToken(true)) {
|
|
return true; // properties may end with eof
|
|
}
|
|
if ((eCSSToken_Symbol == mToken.mType) &&
|
|
((';' == mToken.mSymbol) ||
|
|
('!' == mToken.mSymbol) ||
|
|
('}' == mToken.mSymbol) ||
|
|
(')' == mToken.mSymbol))) {
|
|
// XXX need to verify that ! is only followed by "important [;|}]
|
|
// XXX this requires a multi-token pushback buffer
|
|
UngetToken();
|
|
return true;
|
|
}
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
// Checks if we're at the end of a property, raising an error if we're not.
|
|
bool
|
|
CSSParserImpl::ExpectEndProperty()
|
|
{
|
|
if (CheckEndProperty())
|
|
return true;
|
|
|
|
// If we're here, we read something incorrect, so we should report it.
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
|
|
return false;
|
|
}
|
|
|
|
// Parses the priority suffix on a property, which at present may be
|
|
// either '!important' or nothing.
|
|
CSSParserImpl::PriorityParsingStatus
|
|
CSSParserImpl::ParsePriority()
|
|
{
|
|
if (!GetToken(true)) {
|
|
return ePriority_None; // properties may end with EOF
|
|
}
|
|
if (!mToken.IsSymbol('!')) {
|
|
UngetToken();
|
|
return ePriority_None; // dunno what it is, but it's not a priority
|
|
}
|
|
|
|
if (!GetToken(true)) {
|
|
// EOF is not ok after !
|
|
REPORT_UNEXPECTED_EOF(PEImportantEOF);
|
|
return ePriority_Error;
|
|
}
|
|
|
|
if (mToken.mType != eCSSToken_Ident ||
|
|
!mToken.mIdent.LowerCaseEqualsLiteral("important")) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedImportant);
|
|
UngetToken();
|
|
return ePriority_Error;
|
|
}
|
|
|
|
return ePriority_Important;
|
|
}
|
|
|
|
nsSubstring*
|
|
CSSParserImpl::NextIdent()
|
|
{
|
|
// XXX Error reporting?
|
|
if (!GetToken(true)) {
|
|
return nullptr;
|
|
}
|
|
if (eCSSToken_Ident != mToken.mType) {
|
|
UngetToken();
|
|
return nullptr;
|
|
}
|
|
return &mToken.mIdent;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::SkipAtRule(bool aInsideBlock)
|
|
{
|
|
for (;;) {
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PESkipAtRuleEOF2);
|
|
return false;
|
|
}
|
|
if (eCSSToken_Symbol == mToken.mType) {
|
|
char16_t symbol = mToken.mSymbol;
|
|
if (symbol == ';') {
|
|
break;
|
|
}
|
|
if (aInsideBlock && symbol == '}') {
|
|
// The closing } doesn't belong to us.
|
|
UngetToken();
|
|
break;
|
|
}
|
|
if (symbol == '{') {
|
|
SkipUntil('}');
|
|
break;
|
|
} else if (symbol == '(') {
|
|
SkipUntil(')');
|
|
} else if (symbol == '[') {
|
|
SkipUntil(']');
|
|
}
|
|
} else if (eCSSToken_Function == mToken.mType ||
|
|
eCSSToken_Bad_URL == mToken.mType) {
|
|
SkipUntil(')');
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseAtRule(RuleAppendFunc aAppendFunc,
|
|
void* aData,
|
|
bool aInAtRule)
|
|
{
|
|
|
|
nsCSSSection newSection;
|
|
bool (CSSParserImpl::*parseFunc)(RuleAppendFunc, void*);
|
|
|
|
if ((mSection <= eCSSSection_Charset) &&
|
|
(mToken.mIdent.LowerCaseEqualsLiteral("charset"))) {
|
|
parseFunc = &CSSParserImpl::ParseCharsetRule;
|
|
newSection = eCSSSection_Import; // only one charset allowed
|
|
|
|
} else if ((mSection <= eCSSSection_Import) &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("import")) {
|
|
parseFunc = &CSSParserImpl::ParseImportRule;
|
|
newSection = eCSSSection_Import;
|
|
|
|
} else if ((mSection <= eCSSSection_NameSpace) &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("namespace")) {
|
|
parseFunc = &CSSParserImpl::ParseNameSpaceRule;
|
|
newSection = eCSSSection_NameSpace;
|
|
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("media")) {
|
|
parseFunc = &CSSParserImpl::ParseMediaRule;
|
|
newSection = eCSSSection_General;
|
|
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("-moz-document")) {
|
|
parseFunc = &CSSParserImpl::ParseMozDocumentRule;
|
|
newSection = eCSSSection_General;
|
|
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("font-face")) {
|
|
parseFunc = &CSSParserImpl::ParseFontFaceRule;
|
|
newSection = eCSSSection_General;
|
|
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("font-feature-values")) {
|
|
parseFunc = &CSSParserImpl::ParseFontFeatureValuesRule;
|
|
newSection = eCSSSection_General;
|
|
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("page")) {
|
|
parseFunc = &CSSParserImpl::ParsePageRule;
|
|
newSection = eCSSSection_General;
|
|
|
|
} else if ((nsCSSProps::IsEnabled(eCSSPropertyAlias_MozAnimation,
|
|
EnabledState()) &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("-moz-keyframes")) ||
|
|
(nsCSSProps::IsEnabled(eCSSPropertyAlias_WebkitAnimation) &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("-webkit-keyframes")) ||
|
|
mToken.mIdent.LowerCaseEqualsLiteral("keyframes")) {
|
|
parseFunc = &CSSParserImpl::ParseKeyframesRule;
|
|
newSection = eCSSSection_General;
|
|
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("supports")) {
|
|
parseFunc = &CSSParserImpl::ParseSupportsRule;
|
|
newSection = eCSSSection_General;
|
|
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("counter-style")) {
|
|
parseFunc = &CSSParserImpl::ParseCounterStyleRule;
|
|
newSection = eCSSSection_General;
|
|
|
|
} else {
|
|
if (!NonMozillaVendorIdentifier(mToken.mIdent)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEUnknownAtRule);
|
|
OUTPUT_ERROR();
|
|
}
|
|
// Skip over unsupported at rule, don't advance section
|
|
return SkipAtRule(aInAtRule);
|
|
}
|
|
|
|
// Inside of @-rules, only the rules that can occur anywhere
|
|
// are allowed.
|
|
bool unnestable = aInAtRule && newSection != eCSSSection_General;
|
|
if (unnestable) {
|
|
REPORT_UNEXPECTED_TOKEN(PEGroupRuleNestedAtRule);
|
|
}
|
|
|
|
if (unnestable || !(this->*parseFunc)(aAppendFunc, aData)) {
|
|
// Skip over invalid at rule, don't advance section
|
|
OUTPUT_ERROR();
|
|
return SkipAtRule(aInAtRule);
|
|
}
|
|
|
|
// Nested @-rules don't affect the top-level rule chain requirement
|
|
if (!aInAtRule) {
|
|
mSection = newSection;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseCharsetRule(RuleAppendFunc aAppendFunc,
|
|
void* aData)
|
|
{
|
|
uint32_t linenum, colnum;
|
|
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
|
|
!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PECharsetRuleEOF);
|
|
return false;
|
|
}
|
|
|
|
if (eCSSToken_String != mToken.mType) {
|
|
UngetToken();
|
|
REPORT_UNEXPECTED_TOKEN(PECharsetRuleNotString);
|
|
return false;
|
|
}
|
|
|
|
nsAutoString charset = mToken.mIdent;
|
|
|
|
if (!ExpectSymbol(';', true)) {
|
|
return false;
|
|
}
|
|
|
|
// It's intentional that we don't create a rule object for @charset rules.
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseURLOrString(nsString& aURL)
|
|
{
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
if (eCSSToken_String == mToken.mType || eCSSToken_URL == mToken.mType) {
|
|
aURL = mToken.mIdent;
|
|
return true;
|
|
}
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseMediaQuery(eMediaQueryType aQueryType,
|
|
nsMediaQuery **aQuery,
|
|
bool *aHitStop)
|
|
{
|
|
*aQuery = nullptr;
|
|
*aHitStop = false;
|
|
bool inAtRule = aQueryType == eMediaQueryAtRule;
|
|
// Attempt to parse a single condition and stop
|
|
bool singleCondition = aQueryType == eMediaQuerySingleCondition;
|
|
|
|
// "If the comma-separated list is the empty list it is assumed to
|
|
// specify the media query 'all'." (css3-mediaqueries, section
|
|
// "Media Queries")
|
|
if (!GetToken(true)) {
|
|
*aHitStop = true;
|
|
// expected termination by EOF
|
|
if (!inAtRule)
|
|
return true;
|
|
|
|
// unexpected termination by EOF
|
|
REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
|
|
return true;
|
|
}
|
|
|
|
if (eCSSToken_Symbol == mToken.mType && inAtRule &&
|
|
(mToken.mSymbol == ';' || mToken.mSymbol == '{' || mToken.mSymbol == '}' )) {
|
|
*aHitStop = true;
|
|
UngetToken();
|
|
return true;
|
|
}
|
|
UngetToken();
|
|
|
|
nsMediaQuery* query = new nsMediaQuery;
|
|
*aQuery = query;
|
|
|
|
if (ExpectSymbol('(', true)) {
|
|
// we got an expression without a media type
|
|
UngetToken(); // so ParseMediaQueryExpression can handle it
|
|
query->SetType(nsGkAtoms::all);
|
|
query->SetTypeOmitted();
|
|
// Just parse the first expression here.
|
|
if (!ParseMediaQueryExpression(query)) {
|
|
OUTPUT_ERROR();
|
|
query->SetHadUnknownExpression();
|
|
}
|
|
} else if (singleCondition) {
|
|
// Since we are only trying to consume a single condition, which precludes
|
|
// media types and not/only, this should be the same as reaching immediate
|
|
// EOF (no condition to parse)
|
|
*aHitStop = true;
|
|
return true;
|
|
} else {
|
|
nsCOMPtr<nsIAtom> mediaType;
|
|
bool gotNotOrOnly = false;
|
|
for (;;) {
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
|
|
return false;
|
|
}
|
|
if (eCSSToken_Ident != mToken.mType) {
|
|
REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotIdent);
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
// case insensitive from CSS - must be lower cased
|
|
nsContentUtils::ASCIIToLower(mToken.mIdent);
|
|
mediaType = NS_Atomize(mToken.mIdent);
|
|
if (!gotNotOrOnly && mediaType == nsGkAtoms::_not) {
|
|
gotNotOrOnly = true;
|
|
query->SetNegated();
|
|
} else if (!gotNotOrOnly && mediaType == nsGkAtoms::only) {
|
|
gotNotOrOnly = true;
|
|
query->SetHasOnly();
|
|
} else if (mediaType == nsGkAtoms::_not ||
|
|
mediaType == nsGkAtoms::only ||
|
|
mediaType == nsGkAtoms::_and ||
|
|
mediaType == nsGkAtoms::_or) {
|
|
REPORT_UNEXPECTED_TOKEN(PEGatherMediaReservedMediaType);
|
|
UngetToken();
|
|
return false;
|
|
} else {
|
|
// valid media type
|
|
break;
|
|
}
|
|
}
|
|
query->SetType(mediaType);
|
|
}
|
|
|
|
for (;;) {
|
|
if (!GetToken(true)) {
|
|
*aHitStop = true;
|
|
// expected termination by EOF
|
|
if (!inAtRule)
|
|
break;
|
|
|
|
// unexpected termination by EOF
|
|
REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
|
|
break;
|
|
}
|
|
|
|
if (eCSSToken_Symbol == mToken.mType && inAtRule &&
|
|
(mToken.mSymbol == ';' || mToken.mSymbol == '{' || mToken.mSymbol == '}')) {
|
|
*aHitStop = true;
|
|
UngetToken();
|
|
break;
|
|
}
|
|
if (!singleCondition &&
|
|
eCSSToken_Symbol == mToken.mType && mToken.mSymbol == ',') {
|
|
// Done with the expressions for this query
|
|
break;
|
|
}
|
|
if (eCSSToken_Ident != mToken.mType ||
|
|
!mToken.mIdent.LowerCaseEqualsLiteral("and")) {
|
|
if (singleCondition) {
|
|
// We have a condition at this point -- if we're not chained to other
|
|
// conditions with and/or, we're done.
|
|
UngetToken();
|
|
break;
|
|
} else {
|
|
REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotComma);
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
}
|
|
if (!ParseMediaQueryExpression(query)) {
|
|
OUTPUT_ERROR();
|
|
query->SetHadUnknownExpression();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Returns false only when there is a low-level error in the scanner
|
|
// (out-of-memory).
|
|
bool
|
|
CSSParserImpl::GatherMedia(nsMediaList* aMedia,
|
|
bool aInAtRule)
|
|
{
|
|
eMediaQueryType type = aInAtRule ? eMediaQueryAtRule : eMediaQueryNormal;
|
|
for (;;) {
|
|
nsAutoPtr<nsMediaQuery> query;
|
|
bool hitStop;
|
|
if (!ParseMediaQuery(type, getter_Transfers(query), &hitStop)) {
|
|
NS_ASSERTION(!hitStop, "should return true when hit stop");
|
|
OUTPUT_ERROR();
|
|
if (query) {
|
|
query->SetHadUnknownExpression();
|
|
}
|
|
if (aInAtRule) {
|
|
const char16_t stopChars[] =
|
|
{ char16_t(','), char16_t('{'), char16_t(';'), char16_t('}'), char16_t(0) };
|
|
SkipUntilOneOf(stopChars);
|
|
} else {
|
|
SkipUntil(',');
|
|
}
|
|
// Rely on SkipUntilOneOf leaving mToken around as the last token read.
|
|
if (mToken.mType == eCSSToken_Symbol && aInAtRule &&
|
|
(mToken.mSymbol == '{' || mToken.mSymbol == ';' || mToken.mSymbol == '}')) {
|
|
UngetToken();
|
|
hitStop = true;
|
|
}
|
|
}
|
|
if (query) {
|
|
aMedia->AppendQuery(query);
|
|
}
|
|
if (hitStop) {
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseMediaQueryExpression(nsMediaQuery* aQuery)
|
|
{
|
|
if (!ExpectSymbol('(', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEMQExpectedExpressionStart);
|
|
return false;
|
|
}
|
|
if (! GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEMQExpressionEOF);
|
|
return false;
|
|
}
|
|
if (eCSSToken_Ident != mToken.mType) {
|
|
REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureName);
|
|
UngetToken();
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
nsMediaExpression *expr = aQuery->NewExpression();
|
|
|
|
// case insensitive from CSS - must be lower cased
|
|
nsContentUtils::ASCIIToLower(mToken.mIdent);
|
|
nsDependentString featureString(mToken.mIdent, 0);
|
|
uint8_t satisfiedReqFlags = 0;
|
|
|
|
// Strip off "-webkit-" prefix from featureString:
|
|
if (sWebkitPrefixedAliasesEnabled &&
|
|
StringBeginsWith(featureString, NS_LITERAL_STRING("-webkit-"))) {
|
|
satisfiedReqFlags |= nsMediaFeature::eHasWebkitPrefix;
|
|
featureString.Rebind(featureString, 8);
|
|
}
|
|
if (sWebkitDevicePixelRatioEnabled) {
|
|
satisfiedReqFlags |= nsMediaFeature::eWebkitDevicePixelRatioPrefEnabled;
|
|
}
|
|
|
|
// Strip off "min-"/"max-" prefix from featureString:
|
|
if (StringBeginsWith(featureString, NS_LITERAL_STRING("min-"))) {
|
|
expr->mRange = nsMediaExpression::eMin;
|
|
featureString.Rebind(featureString, 4);
|
|
} else if (StringBeginsWith(featureString, NS_LITERAL_STRING("max-"))) {
|
|
expr->mRange = nsMediaExpression::eMax;
|
|
featureString.Rebind(featureString, 4);
|
|
} else {
|
|
expr->mRange = nsMediaExpression::eEqual;
|
|
}
|
|
|
|
nsCOMPtr<nsIAtom> mediaFeatureAtom = NS_Atomize(featureString);
|
|
const nsMediaFeature *feature = nsMediaFeatures::features;
|
|
for (; feature->mName; ++feature) {
|
|
// See if name matches & all requirement flags are satisfied:
|
|
// (We check requirements by turning off all of the flags that have been
|
|
// satisfied, and then see if the result is 0.)
|
|
if (*(feature->mName) == mediaFeatureAtom &&
|
|
!(feature->mReqFlags & ~satisfiedReqFlags)) {
|
|
break;
|
|
}
|
|
}
|
|
if (!feature->mName ||
|
|
(expr->mRange != nsMediaExpression::eEqual &&
|
|
feature->mRangeType != nsMediaFeature::eMinMaxAllowed)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureName);
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
expr->mFeature = feature;
|
|
|
|
if (!GetToken(true) || mToken.IsSymbol(')')) {
|
|
// Query expressions for any feature can be given without a value.
|
|
// However, min/max prefixes are not allowed.
|
|
if (expr->mRange != nsMediaExpression::eEqual) {
|
|
REPORT_UNEXPECTED(PEMQNoMinMaxWithoutValue);
|
|
return false;
|
|
}
|
|
expr->mValue.Reset();
|
|
return true;
|
|
}
|
|
|
|
if (!mToken.IsSymbol(':')) {
|
|
REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureNameEnd);
|
|
UngetToken();
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
bool rv = false;
|
|
switch (feature->mValueType) {
|
|
case nsMediaFeature::eLength:
|
|
rv = ParseSingleTokenNonNegativeVariant(expr->mValue, VARIANT_LENGTH,
|
|
nullptr);
|
|
break;
|
|
case nsMediaFeature::eInteger:
|
|
case nsMediaFeature::eBoolInteger:
|
|
rv = ParseNonNegativeInteger(expr->mValue);
|
|
// Enforce extra restrictions for eBoolInteger
|
|
if (rv &&
|
|
feature->mValueType == nsMediaFeature::eBoolInteger &&
|
|
expr->mValue.GetIntValue() > 1)
|
|
rv = false;
|
|
break;
|
|
case nsMediaFeature::eFloat:
|
|
rv = ParseNonNegativeNumber(expr->mValue);
|
|
break;
|
|
case nsMediaFeature::eIntRatio:
|
|
{
|
|
// Two integers separated by '/', with optional whitespace on
|
|
// either side of the '/'.
|
|
RefPtr<nsCSSValue::Array> a = nsCSSValue::Array::Create(2);
|
|
expr->mValue.SetArrayValue(a, eCSSUnit_Array);
|
|
// We don't bother with ParseNonNegativeVariant since we have to
|
|
// check for != 0 as well; no need to worry about the UngetToken
|
|
// since we're throwing out up to the next ')' anyway.
|
|
rv = ParseSingleTokenVariant(a->Item(0), VARIANT_INTEGER, nullptr) &&
|
|
a->Item(0).GetIntValue() > 0 &&
|
|
ExpectSymbol('/', true) &&
|
|
ParseSingleTokenVariant(a->Item(1), VARIANT_INTEGER, nullptr) &&
|
|
a->Item(1).GetIntValue() > 0;
|
|
}
|
|
break;
|
|
case nsMediaFeature::eResolution:
|
|
rv = GetToken(true);
|
|
if (!rv)
|
|
break;
|
|
rv = mToken.mType == eCSSToken_Dimension && mToken.mNumber > 0.0f;
|
|
if (!rv) {
|
|
UngetToken();
|
|
break;
|
|
}
|
|
// No worries about whether unitless zero is allowed, since the
|
|
// value must be positive (and we checked that above).
|
|
NS_ASSERTION(!mToken.mIdent.IsEmpty(), "unit lied");
|
|
if (mToken.mIdent.LowerCaseEqualsLiteral("dpi")) {
|
|
expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Inch);
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("dppx")) {
|
|
expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Pixel);
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("dpcm")) {
|
|
expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Centimeter);
|
|
} else {
|
|
rv = false;
|
|
}
|
|
break;
|
|
case nsMediaFeature::eEnumerated:
|
|
rv = ParseSingleTokenVariant(expr->mValue, VARIANT_KEYWORD,
|
|
feature->mData.mKeywordTable);
|
|
break;
|
|
case nsMediaFeature::eIdent:
|
|
rv = ParseSingleTokenVariant(expr->mValue, VARIANT_IDENTIFIER, nullptr);
|
|
break;
|
|
}
|
|
if (!rv || !ExpectSymbol(')', true)) {
|
|
REPORT_UNEXPECTED(PEMQExpectedFeatureValue);
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Parse a CSS2 import rule: "@import STRING | URL [medium [, medium]]"
|
|
bool
|
|
CSSParserImpl::ParseImportRule(RuleAppendFunc aAppendFunc, void* aData)
|
|
{
|
|
RefPtr<nsMediaList> media = new nsMediaList();
|
|
|
|
uint32_t linenum, colnum;
|
|
nsAutoString url;
|
|
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
|
|
!ParseURLOrString(url)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEImportNotURI);
|
|
return false;
|
|
}
|
|
|
|
if (!ExpectSymbol(';', true)) {
|
|
if (!GatherMedia(media, true) ||
|
|
!ExpectSymbol(';', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEImportUnexpected);
|
|
// don't advance section, simply ignore invalid @import
|
|
return false;
|
|
}
|
|
|
|
// Safe to assert this, since we ensured that there is something
|
|
// other than the ';' coming after the @import's url() token.
|
|
NS_ASSERTION(media->Length() != 0, "media list must be nonempty");
|
|
}
|
|
|
|
ProcessImport(url, media, aAppendFunc, aData, linenum, colnum);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::ProcessImport(const nsString& aURLSpec,
|
|
nsMediaList* aMedia,
|
|
RuleAppendFunc aAppendFunc,
|
|
void* aData,
|
|
uint32_t aLineNumber,
|
|
uint32_t aColumnNumber)
|
|
{
|
|
RefPtr<css::ImportRule> rule = new css::ImportRule(aMedia, aURLSpec,
|
|
aLineNumber,
|
|
aColumnNumber);
|
|
(*aAppendFunc)(rule, aData);
|
|
|
|
// Diagnose bad URIs even if we don't have a child loader.
|
|
nsCOMPtr<nsIURI> url;
|
|
// Charset will be deduced from mBaseURI, which is more or less correct.
|
|
nsresult rv = NS_NewURI(getter_AddRefs(url), aURLSpec, nullptr, mBaseURI);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
if (rv == NS_ERROR_MALFORMED_URI) {
|
|
// import url is bad
|
|
REPORT_UNEXPECTED_P(PEImportBadURI, aURLSpec);
|
|
OUTPUT_ERROR();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (mChildLoader) {
|
|
mChildLoader->LoadChildSheet(mSheet, url, aMedia, rule, mReusableSheets);
|
|
}
|
|
}
|
|
|
|
// Parse the {} part of an @media or @-moz-document rule.
|
|
bool
|
|
CSSParserImpl::ParseGroupRule(css::GroupRule* aRule,
|
|
RuleAppendFunc aAppendFunc,
|
|
void* aData)
|
|
{
|
|
// XXXbz this could use better error reporting throughout the method
|
|
if (!ExpectSymbol('{', true)) {
|
|
return false;
|
|
}
|
|
|
|
// push rule on stack, loop over children
|
|
PushGroup(aRule);
|
|
nsCSSSection holdSection = mSection;
|
|
mSection = eCSSSection_General;
|
|
|
|
for (;;) {
|
|
// Get next non-whitespace token
|
|
if (! GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEGroupRuleEOF2);
|
|
break;
|
|
}
|
|
if (mToken.IsSymbol('}')) { // done!
|
|
UngetToken();
|
|
break;
|
|
}
|
|
if (eCSSToken_AtKeyword == mToken.mType) {
|
|
// Parse for nested rules
|
|
ParseAtRule(aAppendFunc, aData, true);
|
|
continue;
|
|
}
|
|
UngetToken();
|
|
ParseRuleSet(AppendRuleToSheet, this, true);
|
|
}
|
|
PopGroup();
|
|
|
|
if (!ExpectSymbol('}', true)) {
|
|
mSection = holdSection;
|
|
return false;
|
|
}
|
|
(*aAppendFunc)(aRule, aData);
|
|
return true;
|
|
}
|
|
|
|
// Parse a CSS2 media rule: "@media medium [, medium] { ... }"
|
|
bool
|
|
CSSParserImpl::ParseMediaRule(RuleAppendFunc aAppendFunc, void* aData)
|
|
{
|
|
RefPtr<nsMediaList> media = new nsMediaList();
|
|
uint32_t linenum, colnum;
|
|
if (GetNextTokenLocation(true, &linenum, &colnum) &&
|
|
GatherMedia(media, true)) {
|
|
// XXXbz this could use better error reporting throughout the method
|
|
RefPtr<css::MediaRule> rule = new css::MediaRule(linenum, colnum);
|
|
// Append first, so when we do SetMedia() the rule
|
|
// knows what its stylesheet is.
|
|
if (ParseGroupRule(rule, aAppendFunc, aData)) {
|
|
rule->SetMedia(media);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Parse a @-moz-document rule. This is like an @media rule, but instead
|
|
// of a medium it has a nonempty list of items where each item is either
|
|
// url(), url-prefix(), or domain().
|
|
bool
|
|
CSSParserImpl::ParseMozDocumentRule(RuleAppendFunc aAppendFunc, void* aData)
|
|
{
|
|
css::DocumentRule::URL *urls = nullptr;
|
|
css::DocumentRule::URL **next = &urls;
|
|
|
|
uint32_t linenum, colnum;
|
|
if (!GetNextTokenLocation(true, &linenum, &colnum)) {
|
|
return false;
|
|
}
|
|
|
|
do {
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEMozDocRuleEOF);
|
|
delete urls;
|
|
return false;
|
|
}
|
|
|
|
if (!(eCSSToken_URL == mToken.mType ||
|
|
(eCSSToken_Function == mToken.mType &&
|
|
(mToken.mIdent.LowerCaseEqualsLiteral("url-prefix") ||
|
|
mToken.mIdent.LowerCaseEqualsLiteral("domain") ||
|
|
mToken.mIdent.LowerCaseEqualsLiteral("regexp"))))) {
|
|
REPORT_UNEXPECTED_TOKEN(PEMozDocRuleBadFunc2);
|
|
UngetToken();
|
|
delete urls;
|
|
return false;
|
|
}
|
|
css::DocumentRule::URL *cur = *next = new css::DocumentRule::URL;
|
|
next = &cur->next;
|
|
if (mToken.mType == eCSSToken_URL) {
|
|
cur->func = css::DocumentRule::eURL;
|
|
CopyUTF16toUTF8(mToken.mIdent, cur->url);
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("regexp")) {
|
|
// regexp() is different from url-prefix() and domain() (but
|
|
// probably the way they *should* have been* in that it requires a
|
|
// string argument, and doesn't try to behave like url().
|
|
cur->func = css::DocumentRule::eRegExp;
|
|
GetToken(true);
|
|
// copy before we know it's valid (but before ExpectSymbol changes
|
|
// mToken.mIdent)
|
|
CopyUTF16toUTF8(mToken.mIdent, cur->url);
|
|
if (eCSSToken_String != mToken.mType || !ExpectSymbol(')', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEMozDocRuleNotString);
|
|
SkipUntil(')');
|
|
delete urls;
|
|
return false;
|
|
}
|
|
} else {
|
|
if (mToken.mIdent.LowerCaseEqualsLiteral("url-prefix")) {
|
|
cur->func = css::DocumentRule::eURLPrefix;
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("domain")) {
|
|
cur->func = css::DocumentRule::eDomain;
|
|
}
|
|
|
|
NS_ASSERTION(!mHavePushBack, "mustn't have pushback at this point");
|
|
mScanner->NextURL(mToken);
|
|
if (mToken.mType != eCSSToken_URL) {
|
|
REPORT_UNEXPECTED_TOKEN(PEMozDocRuleNotURI);
|
|
SkipUntil(')');
|
|
delete urls;
|
|
return false;
|
|
}
|
|
|
|
// We could try to make the URL (as long as it's not domain())
|
|
// canonical and absolute with NS_NewURI and GetSpec, but I'm
|
|
// inclined to think we shouldn't.
|
|
CopyUTF16toUTF8(mToken.mIdent, cur->url);
|
|
}
|
|
} while (ExpectSymbol(',', true));
|
|
|
|
RefPtr<css::DocumentRule> rule = new css::DocumentRule(linenum, colnum);
|
|
rule->SetURLs(urls);
|
|
|
|
return ParseGroupRule(rule, aAppendFunc, aData);
|
|
}
|
|
|
|
// Parse a CSS3 namespace rule: "@namespace [prefix] STRING | URL;"
|
|
bool
|
|
CSSParserImpl::ParseNameSpaceRule(RuleAppendFunc aAppendFunc, void* aData)
|
|
{
|
|
uint32_t linenum, colnum;
|
|
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
|
|
!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEAtNSPrefixEOF);
|
|
return false;
|
|
}
|
|
|
|
nsAutoString prefix;
|
|
nsAutoString url;
|
|
|
|
if (eCSSToken_Ident == mToken.mType) {
|
|
prefix = mToken.mIdent;
|
|
// user-specified identifiers are case-sensitive (bug 416106)
|
|
} else {
|
|
UngetToken();
|
|
}
|
|
|
|
if (!ParseURLOrString(url) || !ExpectSymbol(';', true)) {
|
|
if (mHavePushBack) {
|
|
REPORT_UNEXPECTED_TOKEN(PEAtNSUnexpected);
|
|
} else {
|
|
REPORT_UNEXPECTED_EOF(PEAtNSURIEOF);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
ProcessNameSpace(prefix, url, aAppendFunc, aData, linenum, colnum);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::ProcessNameSpace(const nsString& aPrefix,
|
|
const nsString& aURLSpec,
|
|
RuleAppendFunc aAppendFunc,
|
|
void* aData,
|
|
uint32_t aLineNumber,
|
|
uint32_t aColumnNumber)
|
|
{
|
|
nsCOMPtr<nsIAtom> prefix;
|
|
|
|
if (!aPrefix.IsEmpty()) {
|
|
prefix = NS_Atomize(aPrefix);
|
|
}
|
|
|
|
RefPtr<css::NameSpaceRule> rule = new css::NameSpaceRule(prefix, aURLSpec,
|
|
aLineNumber,
|
|
aColumnNumber);
|
|
(*aAppendFunc)(rule, aData);
|
|
|
|
// If this was the first namespace rule encountered, it will trigger
|
|
// creation of a namespace map.
|
|
if (!mNameSpaceMap) {
|
|
mNameSpaceMap = mSheet->GetNameSpaceMap();
|
|
}
|
|
}
|
|
|
|
// font-face-rule: '@font-face' '{' font-description '}'
|
|
// font-description: font-descriptor+
|
|
bool
|
|
CSSParserImpl::ParseFontFaceRule(RuleAppendFunc aAppendFunc, void* aData)
|
|
{
|
|
uint32_t linenum, colnum;
|
|
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
|
|
!ExpectSymbol('{', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEBadFontBlockStart);
|
|
return false;
|
|
}
|
|
|
|
RefPtr<nsCSSFontFaceRule> rule(new nsCSSFontFaceRule(linenum, colnum));
|
|
|
|
for (;;) {
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEFontFaceEOF);
|
|
break;
|
|
}
|
|
if (mToken.IsSymbol('}')) { // done!
|
|
UngetToken();
|
|
break;
|
|
}
|
|
|
|
// ignore extra semicolons
|
|
if (mToken.IsSymbol(';'))
|
|
continue;
|
|
|
|
if (!ParseFontDescriptor(rule)) {
|
|
REPORT_UNEXPECTED(PEDeclSkipped);
|
|
OUTPUT_ERROR();
|
|
if (!SkipDeclaration(true))
|
|
break;
|
|
}
|
|
}
|
|
if (!ExpectSymbol('}', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEBadFontBlockEnd);
|
|
return false;
|
|
}
|
|
(*aAppendFunc)(rule, aData);
|
|
return true;
|
|
}
|
|
|
|
// font-descriptor: font-family-desc
|
|
// | font-style-desc
|
|
// | font-weight-desc
|
|
// | font-stretch-desc
|
|
// | font-src-desc
|
|
// | unicode-range-desc
|
|
//
|
|
// All font-*-desc productions follow the pattern
|
|
// IDENT ':' value ';'
|
|
//
|
|
// On entry to this function, mToken is the IDENT.
|
|
|
|
bool
|
|
CSSParserImpl::ParseFontDescriptor(nsCSSFontFaceRule* aRule)
|
|
{
|
|
if (eCSSToken_Ident != mToken.mType) {
|
|
REPORT_UNEXPECTED_TOKEN(PEFontDescExpected);
|
|
return false;
|
|
}
|
|
|
|
nsString descName = mToken.mIdent;
|
|
if (!ExpectSymbol(':', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
|
|
OUTPUT_ERROR();
|
|
return false;
|
|
}
|
|
|
|
nsCSSFontDesc descID = nsCSSProps::LookupFontDesc(descName);
|
|
nsCSSValue value;
|
|
|
|
if (descID == eCSSFontDesc_UNKNOWN ||
|
|
(descID == eCSSFontDesc_Display &&
|
|
!Preferences::GetBool("layout.css.font-display.enabled"))) {
|
|
if (NonMozillaVendorIdentifier(descName)) {
|
|
// silently skip other vendors' extensions
|
|
SkipDeclaration(true);
|
|
return true;
|
|
} else {
|
|
REPORT_UNEXPECTED_P(PEUnknownFontDesc, descName);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!ParseFontDescriptorValue(descID, value)) {
|
|
REPORT_UNEXPECTED_P(PEValueParsingError, descName);
|
|
return false;
|
|
}
|
|
|
|
// Expect termination by ;, }, or EOF.
|
|
if (GetToken(true)) {
|
|
if (!mToken.IsSymbol(';') &&
|
|
!mToken.IsSymbol('}')) {
|
|
UngetToken();
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
|
|
return false;
|
|
}
|
|
UngetToken();
|
|
}
|
|
|
|
aRule->SetDesc(descID, value);
|
|
return true;
|
|
}
|
|
|
|
// @font-feature-values <font-family># {
|
|
// @<feature-type> {
|
|
// <feature-ident> : <feature-index>+;
|
|
// <feature-ident> : <feature-index>+;
|
|
// ...
|
|
// }
|
|
// ...
|
|
// }
|
|
|
|
bool
|
|
CSSParserImpl::ParseFontFeatureValuesRule(RuleAppendFunc aAppendFunc,
|
|
void* aData)
|
|
{
|
|
uint32_t linenum, colnum;
|
|
if (!GetNextTokenLocation(true, &linenum, &colnum)) {
|
|
return false;
|
|
}
|
|
|
|
RefPtr<nsCSSFontFeatureValuesRule>
|
|
valuesRule(new nsCSSFontFeatureValuesRule(linenum, colnum));
|
|
|
|
// parse family list
|
|
nsCSSValue fontlistValue;
|
|
|
|
if (!ParseFamily(fontlistValue) ||
|
|
fontlistValue.GetUnit() != eCSSUnit_FontFamilyList)
|
|
{
|
|
REPORT_UNEXPECTED_TOKEN(PEFFVNoFamily);
|
|
return false;
|
|
}
|
|
|
|
// add family to rule
|
|
const FontFamilyList* fontlist = fontlistValue.GetFontFamilyListValue();
|
|
|
|
// family list has generic ==> parse error
|
|
if (fontlist->HasGeneric()) {
|
|
REPORT_UNEXPECTED_TOKEN(PEFFVGenericInFamilyList);
|
|
return false;
|
|
}
|
|
|
|
valuesRule->SetFamilyList(*fontlist);
|
|
|
|
// open brace
|
|
if (!ExpectSymbol('{', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEFFVBlockStart);
|
|
return false;
|
|
}
|
|
|
|
// list of sets of feature values, each set bound to a specific
|
|
// feature-type (e.g. swash, annotation)
|
|
for (;;) {
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF);
|
|
break;
|
|
}
|
|
if (mToken.IsSymbol('}')) { // done!
|
|
UngetToken();
|
|
break;
|
|
}
|
|
|
|
if (!ParseFontFeatureValueSet(valuesRule)) {
|
|
if (!SkipAtRule(false)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!ExpectSymbol('}', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEFFVUnexpectedBlockEnd);
|
|
SkipUntil('}');
|
|
return false;
|
|
}
|
|
|
|
(*aAppendFunc)(valuesRule, aData);
|
|
return true;
|
|
}
|
|
|
|
#define NUMVALUES_NO_LIMIT 0xFFFF
|
|
|
|
// parse a single value set containing name-value pairs for a single feature type
|
|
// @<feature-type> { [ <feature-ident> : <feature-index>+ ; ]* }
|
|
// Ex: @swash { flowing: 1; delicate: 2; }
|
|
bool
|
|
CSSParserImpl::ParseFontFeatureValueSet(nsCSSFontFeatureValuesRule
|
|
*aFeatureValuesRule)
|
|
{
|
|
// -- @keyword (e.g. swash, styleset)
|
|
if (eCSSToken_AtKeyword != mToken.mType) {
|
|
REPORT_UNEXPECTED_TOKEN(PEFontFeatureValuesNoAt);
|
|
OUTPUT_ERROR();
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
// which font-specific variant of font-variant-alternates
|
|
int32_t whichVariant;
|
|
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
|
|
if (keyword == eCSSKeyword_UNKNOWN ||
|
|
!nsCSSProps::FindKeyword(keyword,
|
|
nsCSSProps::kFontVariantAlternatesFuncsKTable,
|
|
whichVariant))
|
|
{
|
|
if (!NonMozillaVendorIdentifier(mToken.mIdent)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEFFVUnknownFontVariantPropValue);
|
|
OUTPUT_ERROR();
|
|
}
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
nsAutoString featureType(mToken.mIdent);
|
|
|
|
// open brace
|
|
if (!ExpectSymbol('{', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEFFVValueSetStart);
|
|
return false;
|
|
}
|
|
|
|
// styleset and character-variant can be multi-valued, otherwise single value
|
|
int32_t limitNumValues;
|
|
|
|
switch (keyword) {
|
|
case eCSSKeyword_styleset:
|
|
limitNumValues = NUMVALUES_NO_LIMIT;
|
|
break;
|
|
case eCSSKeyword_character_variant:
|
|
limitNumValues = 2;
|
|
break;
|
|
default:
|
|
limitNumValues = 1;
|
|
break;
|
|
}
|
|
|
|
// -- ident integer+ [, ident integer+]
|
|
AutoTArray<gfxFontFeatureValueSet::ValueList, 5> values;
|
|
|
|
// list of font-feature-values-declaration's
|
|
for (;;) {
|
|
nsAutoString valueId;
|
|
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF);
|
|
break;
|
|
}
|
|
|
|
// ignore extra semicolons
|
|
if (mToken.IsSymbol(';')) {
|
|
continue;
|
|
}
|
|
|
|
// close brace ==> done
|
|
if (mToken.IsSymbol('}')) {
|
|
break;
|
|
}
|
|
|
|
// ident
|
|
if (eCSSToken_Ident != mToken.mType) {
|
|
REPORT_UNEXPECTED_TOKEN(PEFFVExpectedIdent);
|
|
if (!SkipDeclaration(true)) {
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
valueId.Assign(mToken.mIdent);
|
|
|
|
// colon
|
|
if (!ExpectSymbol(':', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
|
|
OUTPUT_ERROR();
|
|
if (!SkipDeclaration(true)) {
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// value list
|
|
AutoTArray<uint32_t,4> featureSelectors;
|
|
|
|
nsCSSValue intValue;
|
|
while (ParseNonNegativeInteger(intValue)) {
|
|
featureSelectors.AppendElement(uint32_t(intValue.GetIntValue()));
|
|
}
|
|
|
|
int32_t numValues = featureSelectors.Length();
|
|
|
|
if (numValues == 0) {
|
|
REPORT_UNEXPECTED_TOKEN(PEFFVExpectedValue);
|
|
OUTPUT_ERROR();
|
|
if (!SkipDeclaration(true)) {
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (numValues > limitNumValues) {
|
|
REPORT_UNEXPECTED_P(PEFFVTooManyValues, featureType);
|
|
OUTPUT_ERROR();
|
|
if (!SkipDeclaration(true)) {
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF);
|
|
gfxFontFeatureValueSet::ValueList v(valueId, featureSelectors);
|
|
values.AppendElement(v);
|
|
break;
|
|
}
|
|
|
|
// ';' or '}' to end definition
|
|
if (!mToken.IsSymbol(';') && !mToken.IsSymbol('}')) {
|
|
REPORT_UNEXPECTED_TOKEN(PEFFVValueDefinitionTrailing);
|
|
OUTPUT_ERROR();
|
|
if (!SkipDeclaration(true)) {
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
gfxFontFeatureValueSet::ValueList v(valueId, featureSelectors);
|
|
values.AppendElement(v);
|
|
|
|
if (mToken.IsSymbol('}')) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
aFeatureValuesRule->AddValueList(whichVariant, values);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseKeyframesRule(RuleAppendFunc aAppendFunc, void* aData)
|
|
{
|
|
uint32_t linenum, colnum;
|
|
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
|
|
!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEKeyframeNameEOF);
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType != eCSSToken_Ident) {
|
|
REPORT_UNEXPECTED_TOKEN(PEKeyframeBadName);
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
nsString name(mToken.mIdent);
|
|
|
|
if (!ExpectSymbol('{', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEKeyframeBrace);
|
|
return false;
|
|
}
|
|
|
|
RefPtr<nsCSSKeyframesRule> rule = new nsCSSKeyframesRule(name,
|
|
linenum, colnum);
|
|
|
|
while (!ExpectSymbol('}', true)) {
|
|
RefPtr<nsCSSKeyframeRule> kid = ParseKeyframeRule();
|
|
if (kid) {
|
|
rule->AppendStyleRule(kid);
|
|
} else {
|
|
OUTPUT_ERROR();
|
|
SkipRuleSet(true);
|
|
}
|
|
}
|
|
|
|
(*aAppendFunc)(rule, aData);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParsePageRule(RuleAppendFunc aAppendFunc, void* aData)
|
|
{
|
|
uint32_t linenum, colnum;
|
|
if (!GetNextTokenLocation(true, &linenum, &colnum)) {
|
|
return false;
|
|
}
|
|
|
|
// TODO: There can be page selectors after @page such as ":first", ":left".
|
|
uint32_t parseFlags = eParseDeclaration_InBraces |
|
|
eParseDeclaration_AllowImportant;
|
|
|
|
// Forbid viewport units in @page rules. See bug 811391.
|
|
MOZ_ASSERT(mViewportUnitsEnabled,
|
|
"Viewport units should be enabled outside of @page rules.");
|
|
mViewportUnitsEnabled = false;
|
|
RefPtr<css::Declaration> declaration =
|
|
ParseDeclarationBlock(parseFlags, eCSSContext_Page);
|
|
mViewportUnitsEnabled = true;
|
|
|
|
if (!declaration) {
|
|
return false;
|
|
}
|
|
|
|
RefPtr<nsCSSPageRule> rule =
|
|
new nsCSSPageRule(declaration, linenum, colnum);
|
|
|
|
(*aAppendFunc)(rule, aData);
|
|
return true;
|
|
}
|
|
|
|
already_AddRefed<nsCSSKeyframeRule>
|
|
CSSParserImpl::ParseKeyframeRule()
|
|
{
|
|
InfallibleTArray<float> selectorList;
|
|
uint32_t linenum, colnum;
|
|
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
|
|
!ParseKeyframeSelectorList(selectorList)) {
|
|
REPORT_UNEXPECTED(PEBadSelectorKeyframeRuleIgnored);
|
|
return nullptr;
|
|
}
|
|
|
|
// Ignore !important in keyframe rules
|
|
uint32_t parseFlags = eParseDeclaration_InBraces;
|
|
RefPtr<css::Declaration> declaration(ParseDeclarationBlock(parseFlags));
|
|
if (!declaration) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Takes ownership of declaration
|
|
RefPtr<nsCSSKeyframeRule> rule =
|
|
new nsCSSKeyframeRule(Move(selectorList), declaration.forget(),
|
|
linenum, colnum);
|
|
return rule.forget();
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseKeyframeSelectorList(InfallibleTArray<float>& aSelectorList)
|
|
{
|
|
for (;;) {
|
|
if (!GetToken(true)) {
|
|
// The first time through the loop, this means we got an empty
|
|
// list. Otherwise, it means we have a trailing comma.
|
|
return false;
|
|
}
|
|
float value;
|
|
switch (mToken.mType) {
|
|
case eCSSToken_Percentage:
|
|
value = mToken.mNumber;
|
|
break;
|
|
case eCSSToken_Ident:
|
|
if (mToken.mIdent.LowerCaseEqualsLiteral("from")) {
|
|
value = 0.0f;
|
|
break;
|
|
}
|
|
if (mToken.mIdent.LowerCaseEqualsLiteral("to")) {
|
|
value = 1.0f;
|
|
break;
|
|
}
|
|
MOZ_FALLTHROUGH;
|
|
default:
|
|
UngetToken();
|
|
// The first time through the loop, this means we got an empty
|
|
// list. Otherwise, it means we have a trailing comma.
|
|
return false;
|
|
}
|
|
aSelectorList.AppendElement(value);
|
|
if (!ExpectSymbol(',', true)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// supports_rule
|
|
// : "@supports" supports_condition group_rule_body
|
|
// ;
|
|
bool
|
|
CSSParserImpl::ParseSupportsRule(RuleAppendFunc aAppendFunc, void* aProcessData)
|
|
{
|
|
bool conditionMet = false;
|
|
nsString condition;
|
|
|
|
mScanner->StartRecording();
|
|
|
|
uint32_t linenum, colnum;
|
|
if (!GetNextTokenLocation(true, &linenum, &colnum)) {
|
|
return false;
|
|
}
|
|
|
|
bool parsed = ParseSupportsCondition(conditionMet);
|
|
|
|
if (!parsed) {
|
|
mScanner->StopRecording();
|
|
return false;
|
|
}
|
|
|
|
if (!ExpectSymbol('{', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PESupportsGroupRuleStart);
|
|
mScanner->StopRecording();
|
|
return false;
|
|
}
|
|
|
|
UngetToken();
|
|
mScanner->StopRecording(condition);
|
|
|
|
// Remove the "{" that would follow the condition.
|
|
if (condition.Length() != 0) {
|
|
condition.Truncate(condition.Length() - 1);
|
|
}
|
|
|
|
// Remove spaces from the start and end of the recorded supports condition.
|
|
condition.Trim(" ", true, true, false);
|
|
|
|
// Record whether we are in a failing @supports, so that property parse
|
|
// errors don't get reported.
|
|
nsAutoFailingSupportsRule failing(this, conditionMet);
|
|
|
|
RefPtr<css::GroupRule> rule = new CSSSupportsRule(conditionMet, condition,
|
|
linenum, colnum);
|
|
return ParseGroupRule(rule, aAppendFunc, aProcessData);
|
|
}
|
|
|
|
// supports_condition
|
|
// : supports_condition_in_parens supports_condition_terms
|
|
// | supports_condition_negation
|
|
// ;
|
|
bool
|
|
CSSParserImpl::ParseSupportsCondition(bool& aConditionMet)
|
|
{
|
|
nsAutoInSupportsCondition aisc(this);
|
|
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PESupportsConditionStartEOF2);
|
|
return false;
|
|
}
|
|
|
|
UngetToken();
|
|
|
|
mScanner->ClearSeenBadToken();
|
|
|
|
if (mToken.IsSymbol('(') ||
|
|
mToken.mType == eCSSToken_Function ||
|
|
mToken.mType == eCSSToken_URL ||
|
|
mToken.mType == eCSSToken_Bad_URL) {
|
|
bool result = ParseSupportsConditionInParens(aConditionMet) &&
|
|
ParseSupportsConditionTerms(aConditionMet) &&
|
|
!mScanner->SeenBadToken();
|
|
return result;
|
|
}
|
|
|
|
if (mToken.mType == eCSSToken_Ident &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("not")) {
|
|
bool result = ParseSupportsConditionNegation(aConditionMet) &&
|
|
!mScanner->SeenBadToken();
|
|
return result;
|
|
}
|
|
|
|
REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedStart);
|
|
return false;
|
|
}
|
|
|
|
// supports_condition_negation
|
|
// : 'not' S+ supports_condition_in_parens
|
|
// ;
|
|
bool
|
|
CSSParserImpl::ParseSupportsConditionNegation(bool& aConditionMet)
|
|
{
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PESupportsConditionNotEOF);
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType != eCSSToken_Ident ||
|
|
!mToken.mIdent.LowerCaseEqualsLiteral("not")) {
|
|
REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedNot);
|
|
return false;
|
|
}
|
|
|
|
if (!RequireWhitespace()) {
|
|
REPORT_UNEXPECTED(PESupportsWhitespaceRequired);
|
|
return false;
|
|
}
|
|
|
|
if (ParseSupportsConditionInParens(aConditionMet)) {
|
|
aConditionMet = !aConditionMet;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// supports_condition_in_parens
|
|
// : '(' S* supports_condition_in_parens_inside_parens ')' S*
|
|
// | supports_condition_pref
|
|
// | general_enclosed
|
|
// ;
|
|
bool
|
|
CSSParserImpl::ParseSupportsConditionInParens(bool& aConditionMet)
|
|
{
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PESupportsConditionInParensStartEOF);
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType == eCSSToken_URL) {
|
|
aConditionMet = false;
|
|
return true;
|
|
}
|
|
|
|
if (AgentRulesEnabled() &&
|
|
mToken.mType == eCSSToken_Function &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("-moz-bool-pref")) {
|
|
return ParseSupportsMozBoolPrefName(aConditionMet);
|
|
}
|
|
|
|
if (mToken.mType == eCSSToken_Function ||
|
|
mToken.mType == eCSSToken_Bad_URL) {
|
|
if (!SkipUntil(')')) {
|
|
REPORT_UNEXPECTED_EOF(PESupportsConditionInParensEOF);
|
|
return false;
|
|
}
|
|
aConditionMet = false;
|
|
return true;
|
|
}
|
|
|
|
if (!mToken.IsSymbol('(')) {
|
|
REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedOpenParenOrFunction);
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
if (!ParseSupportsConditionInParensInsideParens(aConditionMet)) {
|
|
if (!SkipUntil(')')) {
|
|
REPORT_UNEXPECTED_EOF(PESupportsConditionInParensEOF);
|
|
return false;
|
|
}
|
|
aConditionMet = false;
|
|
return true;
|
|
}
|
|
|
|
if (!(ExpectSymbol(')', true))) {
|
|
SkipUntil(')');
|
|
aConditionMet = false;
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// supports_condition_pref
|
|
// : '-moz-bool-pref(' bool_pref_name ')'
|
|
// ;
|
|
bool
|
|
CSSParserImpl::ParseSupportsMozBoolPrefName(bool& aConditionMet)
|
|
{
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType != eCSSToken_String) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
aConditionMet = Preferences::GetBool(
|
|
NS_ConvertUTF16toUTF8(mToken.mIdent).get());
|
|
|
|
if (!ExpectSymbol(')', true)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// supports_condition_in_parens_inside_parens
|
|
// : core_declaration
|
|
// | supports_condition_negation
|
|
// | supports_condition_in_parens supports_condition_terms
|
|
// ;
|
|
bool
|
|
CSSParserImpl::ParseSupportsConditionInParensInsideParens(bool& aConditionMet)
|
|
{
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType == eCSSToken_Ident) {
|
|
if (!mToken.mIdent.LowerCaseEqualsLiteral("not")) {
|
|
nsAutoString propertyName = mToken.mIdent;
|
|
if (!ExpectSymbol(':', true)) {
|
|
return false;
|
|
}
|
|
|
|
nsCSSPropertyID propID = LookupEnabledProperty(propertyName);
|
|
if (propID == eCSSProperty_UNKNOWN) {
|
|
if (ExpectSymbol(')', true)) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
aConditionMet = false;
|
|
SkipUntil(')');
|
|
UngetToken();
|
|
} else if (propID == eCSSPropertyExtra_variable) {
|
|
if (ExpectSymbol(')', false)) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
CSSVariableDeclarations::Type variableType;
|
|
nsString variableValue;
|
|
aConditionMet =
|
|
ParseVariableDeclaration(&variableType, variableValue) &&
|
|
ParsePriority() != ePriority_Error;
|
|
if (!aConditionMet) {
|
|
SkipUntil(')');
|
|
UngetToken();
|
|
}
|
|
} else {
|
|
if (ExpectSymbol(')', true)) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
aConditionMet = ParseProperty(propID) &&
|
|
ParsePriority() != ePriority_Error;
|
|
if (!aConditionMet) {
|
|
SkipUntil(')');
|
|
UngetToken();
|
|
}
|
|
mTempData.ClearProperty(propID);
|
|
mTempData.AssertInitialState();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
UngetToken();
|
|
return ParseSupportsConditionNegation(aConditionMet);
|
|
}
|
|
|
|
UngetToken();
|
|
return ParseSupportsConditionInParens(aConditionMet) &&
|
|
ParseSupportsConditionTerms(aConditionMet);
|
|
}
|
|
|
|
// supports_condition_terms
|
|
// : S+ 'and' supports_condition_terms_after_operator('and')
|
|
// | S+ 'or' supports_condition_terms_after_operator('or')
|
|
// |
|
|
// ;
|
|
bool
|
|
CSSParserImpl::ParseSupportsConditionTerms(bool& aConditionMet)
|
|
{
|
|
if (!RequireWhitespace() || !GetToken(false)) {
|
|
return true;
|
|
}
|
|
|
|
if (mToken.mType != eCSSToken_Ident) {
|
|
UngetToken();
|
|
return true;
|
|
}
|
|
|
|
if (mToken.mIdent.LowerCaseEqualsLiteral("and")) {
|
|
return ParseSupportsConditionTermsAfterOperator(aConditionMet, eAnd);
|
|
}
|
|
|
|
if (mToken.mIdent.LowerCaseEqualsLiteral("or")) {
|
|
return ParseSupportsConditionTermsAfterOperator(aConditionMet, eOr);
|
|
}
|
|
|
|
UngetToken();
|
|
return true;
|
|
}
|
|
|
|
// supports_condition_terms_after_operator(operator)
|
|
// : S+ supports_condition_in_parens ( <operator> supports_condition_in_parens )*
|
|
// ;
|
|
bool
|
|
CSSParserImpl::ParseSupportsConditionTermsAfterOperator(
|
|
bool& aConditionMet,
|
|
CSSParserImpl::SupportsConditionTermOperator aOperator)
|
|
{
|
|
if (!RequireWhitespace()) {
|
|
REPORT_UNEXPECTED(PESupportsWhitespaceRequired);
|
|
return false;
|
|
}
|
|
|
|
const char* token = aOperator == eAnd ? "and" : "or";
|
|
for (;;) {
|
|
bool termConditionMet = false;
|
|
if (!ParseSupportsConditionInParens(termConditionMet)) {
|
|
return false;
|
|
}
|
|
aConditionMet = aOperator == eAnd ? aConditionMet && termConditionMet :
|
|
aConditionMet || termConditionMet;
|
|
|
|
if (!GetToken(true)) {
|
|
return true;
|
|
}
|
|
|
|
if (mToken.mType != eCSSToken_Ident ||
|
|
!mToken.mIdent.LowerCaseEqualsASCII(token)) {
|
|
UngetToken();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseCounterStyleRule(RuleAppendFunc aAppendFunc, void* aData)
|
|
{
|
|
nsAutoString name;
|
|
uint32_t linenum, colnum;
|
|
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
|
|
!ParseCounterStyleName(name, true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PECounterStyleNotIdent);
|
|
return false;
|
|
}
|
|
|
|
if (!ExpectSymbol('{', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PECounterStyleBadBlockStart);
|
|
return false;
|
|
}
|
|
|
|
RefPtr<nsCSSCounterStyleRule> rule = new nsCSSCounterStyleRule(name,
|
|
linenum,
|
|
colnum);
|
|
for (;;) {
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PECounterStyleEOF);
|
|
break;
|
|
}
|
|
if (mToken.IsSymbol('}')) {
|
|
break;
|
|
}
|
|
if (mToken.IsSymbol(';')) {
|
|
continue;
|
|
}
|
|
|
|
if (!ParseCounterDescriptor(rule)) {
|
|
REPORT_UNEXPECTED(PEDeclSkipped);
|
|
OUTPUT_ERROR();
|
|
if (!SkipDeclaration(true)) {
|
|
REPORT_UNEXPECTED_EOF(PECounterStyleEOF);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int32_t system = rule->GetSystem();
|
|
bool isCorrect = false;
|
|
switch (system) {
|
|
case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
|
|
case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
|
|
case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
|
|
case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC:
|
|
case NS_STYLE_COUNTER_SYSTEM_FIXED: {
|
|
// check whether symbols is set and the length is sufficient
|
|
const nsCSSValue& symbols = rule->GetDesc(eCSSCounterDesc_Symbols);
|
|
if (symbols.GetUnit() == eCSSUnit_List &&
|
|
nsCSSCounterStyleRule::CheckDescValue(
|
|
system, eCSSCounterDesc_Symbols, symbols)) {
|
|
isCorrect = true;
|
|
}
|
|
break;
|
|
}
|
|
case NS_STYLE_COUNTER_SYSTEM_ADDITIVE: {
|
|
// for additive system, additive-symbols must be set
|
|
const nsCSSValue& symbols =
|
|
rule->GetDesc(eCSSCounterDesc_AdditiveSymbols);
|
|
if (symbols.GetUnit() == eCSSUnit_PairList) {
|
|
isCorrect = true;
|
|
}
|
|
break;
|
|
}
|
|
case NS_STYLE_COUNTER_SYSTEM_EXTENDS: {
|
|
// for extends system, symbols & additive-symbols must not be set
|
|
const nsCSSValue& symbols = rule->GetDesc(eCSSCounterDesc_Symbols);
|
|
const nsCSSValue& additiveSymbols =
|
|
rule->GetDesc(eCSSCounterDesc_AdditiveSymbols);
|
|
if (symbols.GetUnit() == eCSSUnit_Null &&
|
|
additiveSymbols.GetUnit() == eCSSUnit_Null) {
|
|
isCorrect = true;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
NS_NOTREACHED("unknown system");
|
|
}
|
|
|
|
if (isCorrect) {
|
|
(*aAppendFunc)(rule, aData);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseCounterStyleName(nsAString& aName, bool aForDefinition)
|
|
{
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType != eCSSToken_Ident) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
static const nsCSSKeyword kReservedNames[] = {
|
|
eCSSKeyword_none,
|
|
eCSSKeyword_decimal,
|
|
eCSSKeyword_UNKNOWN
|
|
};
|
|
|
|
nsCSSValue value; // we don't actually care about the value
|
|
if (!ParseCustomIdent(value, mToken.mIdent,
|
|
aForDefinition ? kReservedNames : nullptr)) {
|
|
REPORT_UNEXPECTED_TOKEN(PECounterStyleBadName);
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
aName = mToken.mIdent;
|
|
if (nsCSSProps::IsPredefinedCounterStyle(aName)) {
|
|
ToLowerCase(aName);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseCounterStyleNameValue(nsCSSValue& aValue)
|
|
{
|
|
nsString name;
|
|
if (ParseCounterStyleName(name, false)) {
|
|
aValue.SetStringValue(name, eCSSUnit_Ident);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseCounterDescriptor(nsCSSCounterStyleRule* aRule)
|
|
{
|
|
if (eCSSToken_Ident != mToken.mType) {
|
|
REPORT_UNEXPECTED_TOKEN(PECounterDescExpected);
|
|
return false;
|
|
}
|
|
|
|
nsString descName = mToken.mIdent;
|
|
if (!ExpectSymbol(':', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
|
|
OUTPUT_ERROR();
|
|
return false;
|
|
}
|
|
|
|
nsCSSCounterDesc descID = nsCSSProps::LookupCounterDesc(descName);
|
|
nsCSSValue value;
|
|
|
|
if (descID == eCSSCounterDesc_UNKNOWN) {
|
|
REPORT_UNEXPECTED_P(PEUnknownCounterDesc, descName);
|
|
return false;
|
|
}
|
|
|
|
if (!ParseCounterDescriptorValue(descID, value)) {
|
|
REPORT_UNEXPECTED_P(PEValueParsingError, descName);
|
|
return false;
|
|
}
|
|
|
|
if (!ExpectEndProperty()) {
|
|
return false;
|
|
}
|
|
|
|
aRule->SetDesc(descID, value);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseCounterDescriptorValue(nsCSSCounterDesc aDescID,
|
|
nsCSSValue& aValue)
|
|
{
|
|
// Should also include VARIANT_IMAGE, but it is not supported currently.
|
|
// See bug 1024179.
|
|
static const int32_t VARIANT_COUNTER_SYMBOL =
|
|
VARIANT_STRING | VARIANT_IDENTIFIER;
|
|
|
|
switch (aDescID) {
|
|
case eCSSCounterDesc_System: {
|
|
nsCSSValue system;
|
|
if (!ParseEnum(system, nsCSSProps::kCounterSystemKTable)) {
|
|
return false;
|
|
}
|
|
switch (system.GetIntValue()) {
|
|
case NS_STYLE_COUNTER_SYSTEM_FIXED: {
|
|
nsCSSValue start;
|
|
if (!ParseSingleTokenVariant(start, VARIANT_INTEGER, nullptr)) {
|
|
start.SetIntValue(1, eCSSUnit_Integer);
|
|
}
|
|
aValue.SetPairValue(system, start);
|
|
return true;
|
|
}
|
|
case NS_STYLE_COUNTER_SYSTEM_EXTENDS: {
|
|
nsCSSValue name;
|
|
if (!ParseCounterStyleNameValue(name)) {
|
|
REPORT_UNEXPECTED_TOKEN(PECounterExtendsNotIdent);
|
|
return false;
|
|
}
|
|
aValue.SetPairValue(system, name);
|
|
return true;
|
|
}
|
|
default:
|
|
aValue = system;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
case eCSSCounterDesc_Negative: {
|
|
nsCSSValue first, second;
|
|
if (!ParseSingleTokenVariant(first, VARIANT_COUNTER_SYMBOL, nullptr)) {
|
|
return false;
|
|
}
|
|
if (!ParseSingleTokenVariant(second, VARIANT_COUNTER_SYMBOL, nullptr)) {
|
|
aValue = first;
|
|
} else {
|
|
aValue.SetPairValue(first, second);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
case eCSSCounterDesc_Prefix:
|
|
case eCSSCounterDesc_Suffix:
|
|
return ParseSingleTokenVariant(aValue, VARIANT_COUNTER_SYMBOL, nullptr);
|
|
|
|
case eCSSCounterDesc_Range: {
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_AUTO, nullptr)) {
|
|
return true;
|
|
}
|
|
nsCSSValuePairList* item = aValue.SetPairListValue();
|
|
for (;;) {
|
|
nsCSSValuePair pair;
|
|
if (!ParseCounterRange(pair)) {
|
|
return false;
|
|
}
|
|
item->mXValue = pair.mXValue;
|
|
item->mYValue = pair.mYValue;
|
|
if (!ExpectSymbol(',', true)) {
|
|
return true;
|
|
}
|
|
item->mNext = new nsCSSValuePairList;
|
|
item = item->mNext;
|
|
}
|
|
// should always return in the loop
|
|
}
|
|
|
|
case eCSSCounterDesc_Pad: {
|
|
nsCSSValue width, symbol;
|
|
bool hasWidth = ParseNonNegativeInteger(width);
|
|
if (!ParseSingleTokenVariant(symbol, VARIANT_COUNTER_SYMBOL, nullptr) ||
|
|
(!hasWidth && !ParseNonNegativeInteger(width))) {
|
|
return false;
|
|
}
|
|
aValue.SetPairValue(width, symbol);
|
|
return true;
|
|
}
|
|
|
|
case eCSSCounterDesc_Fallback:
|
|
return ParseCounterStyleNameValue(aValue);
|
|
|
|
case eCSSCounterDesc_Symbols: {
|
|
nsCSSValueList* item = nullptr;
|
|
for (;;) {
|
|
nsCSSValue value;
|
|
if (!ParseSingleTokenVariant(value, VARIANT_COUNTER_SYMBOL, nullptr)) {
|
|
return !!item;
|
|
}
|
|
if (!item) {
|
|
item = aValue.SetListValue();
|
|
} else {
|
|
item->mNext = new nsCSSValueList;
|
|
item = item->mNext;
|
|
}
|
|
item->mValue = value;
|
|
}
|
|
// should always return in the loop
|
|
}
|
|
|
|
case eCSSCounterDesc_AdditiveSymbols: {
|
|
nsCSSValuePairList* item = nullptr;
|
|
int32_t lastWeight = -1;
|
|
for (;;) {
|
|
nsCSSValue weight, symbol;
|
|
bool hasWeight = ParseNonNegativeInteger(weight);
|
|
if (!ParseSingleTokenVariant(symbol, VARIANT_COUNTER_SYMBOL, nullptr) ||
|
|
(!hasWeight && !ParseNonNegativeInteger(weight))) {
|
|
return false;
|
|
}
|
|
if (lastWeight != -1 && weight.GetIntValue() >= lastWeight) {
|
|
REPORT_UNEXPECTED(PECounterASWeight);
|
|
return false;
|
|
}
|
|
lastWeight = weight.GetIntValue();
|
|
if (!item) {
|
|
item = aValue.SetPairListValue();
|
|
} else {
|
|
item->mNext = new nsCSSValuePairList;
|
|
item = item->mNext;
|
|
}
|
|
item->mXValue = weight;
|
|
item->mYValue = symbol;
|
|
if (!ExpectSymbol(',', true)) {
|
|
return true;
|
|
}
|
|
}
|
|
// should always return in the loop
|
|
}
|
|
|
|
case eCSSCounterDesc_SpeakAs:
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_AUTO | VARIANT_KEYWORD,
|
|
nsCSSProps::kCounterSpeakAsKTable)) {
|
|
if (aValue.GetUnit() == eCSSUnit_Enumerated &&
|
|
aValue.GetIntValue() == NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT) {
|
|
// Currently spell-out is not supported, so it is explicitly
|
|
// rejected here rather than parsed as a custom identifier.
|
|
// See bug 1024178.
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return ParseCounterStyleNameValue(aValue);
|
|
|
|
default:
|
|
NS_NOTREACHED("unknown descriptor");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseCounterRange(nsCSSValuePair& aPair)
|
|
{
|
|
static const int32_t VARIANT_BOUND = VARIANT_INTEGER | VARIANT_KEYWORD;
|
|
nsCSSValue lower, upper;
|
|
if (!ParseSingleTokenVariant(lower, VARIANT_BOUND,
|
|
nsCSSProps::kCounterRangeKTable) ||
|
|
!ParseSingleTokenVariant(upper, VARIANT_BOUND,
|
|
nsCSSProps::kCounterRangeKTable)) {
|
|
return false;
|
|
}
|
|
if (lower.GetUnit() != eCSSUnit_Enumerated &&
|
|
upper.GetUnit() != eCSSUnit_Enumerated &&
|
|
lower.GetIntValue() > upper.GetIntValue()) {
|
|
return false;
|
|
}
|
|
aPair = nsCSSValuePair(lower, upper);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::SkipUntil(char16_t aStopSymbol)
|
|
{
|
|
nsCSSToken* tk = &mToken;
|
|
AutoTArray<char16_t, 16> stack;
|
|
stack.AppendElement(aStopSymbol);
|
|
for (;;) {
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
if (eCSSToken_Symbol == tk->mType) {
|
|
char16_t symbol = tk->mSymbol;
|
|
uint32_t stackTopIndex = stack.Length() - 1;
|
|
if (symbol == stack.ElementAt(stackTopIndex)) {
|
|
stack.RemoveElementAt(stackTopIndex);
|
|
if (stackTopIndex == 0) {
|
|
return true;
|
|
}
|
|
|
|
// Just handle out-of-memory by parsing incorrectly. It's
|
|
// highly unlikely we're dealing with a legitimate style sheet
|
|
// anyway.
|
|
} else if ('{' == symbol) {
|
|
stack.AppendElement('}');
|
|
} else if ('[' == symbol) {
|
|
stack.AppendElement(']');
|
|
} else if ('(' == symbol) {
|
|
stack.AppendElement(')');
|
|
}
|
|
} else if (eCSSToken_Function == tk->mType ||
|
|
eCSSToken_Bad_URL == tk->mType) {
|
|
stack.AppendElement(')');
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::SkipBalancedContentUntil(char16_t aStopSymbol)
|
|
{
|
|
nsCSSToken* tk = &mToken;
|
|
AutoTArray<char16_t, 16> stack;
|
|
stack.AppendElement(aStopSymbol);
|
|
for (;;) {
|
|
if (!GetToken(true)) {
|
|
return true;
|
|
}
|
|
if (eCSSToken_Symbol == tk->mType) {
|
|
char16_t symbol = tk->mSymbol;
|
|
uint32_t stackTopIndex = stack.Length() - 1;
|
|
if (symbol == stack.ElementAt(stackTopIndex)) {
|
|
stack.RemoveElementAt(stackTopIndex);
|
|
if (stackTopIndex == 0) {
|
|
return true;
|
|
}
|
|
|
|
// Just handle out-of-memory by parsing incorrectly. It's
|
|
// highly unlikely we're dealing with a legitimate style sheet
|
|
// anyway.
|
|
} else if ('{' == symbol) {
|
|
stack.AppendElement('}');
|
|
} else if ('[' == symbol) {
|
|
stack.AppendElement(']');
|
|
} else if ('(' == symbol) {
|
|
stack.AppendElement(')');
|
|
} else if (')' == symbol ||
|
|
']' == symbol ||
|
|
'}' == symbol) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
} else if (eCSSToken_Function == tk->mType ||
|
|
eCSSToken_Bad_URL == tk->mType) {
|
|
stack.AppendElement(')');
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::SkipUntilOneOf(const char16_t* aStopSymbolChars)
|
|
{
|
|
nsCSSToken* tk = &mToken;
|
|
nsDependentString stopSymbolChars(aStopSymbolChars);
|
|
for (;;) {
|
|
if (!GetToken(true)) {
|
|
break;
|
|
}
|
|
if (eCSSToken_Symbol == tk->mType) {
|
|
char16_t symbol = tk->mSymbol;
|
|
if (stopSymbolChars.FindChar(symbol) != -1) {
|
|
break;
|
|
} else if ('{' == symbol) {
|
|
SkipUntil('}');
|
|
} else if ('[' == symbol) {
|
|
SkipUntil(']');
|
|
} else if ('(' == symbol) {
|
|
SkipUntil(')');
|
|
}
|
|
} else if (eCSSToken_Function == tk->mType ||
|
|
eCSSToken_Bad_URL == tk->mType) {
|
|
SkipUntil(')');
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::SkipUntilAllOf(const StopSymbolCharStack& aStopSymbolChars)
|
|
{
|
|
uint32_t i = aStopSymbolChars.Length();
|
|
while (i--) {
|
|
SkipUntil(aStopSymbolChars[i]);
|
|
}
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::SkipDeclaration(bool aCheckForBraces)
|
|
{
|
|
nsCSSToken* tk = &mToken;
|
|
for (;;) {
|
|
if (!GetToken(true)) {
|
|
if (aCheckForBraces) {
|
|
REPORT_UNEXPECTED_EOF(PESkipDeclBraceEOF);
|
|
}
|
|
return false;
|
|
}
|
|
if (eCSSToken_Symbol == tk->mType) {
|
|
char16_t symbol = tk->mSymbol;
|
|
if (';' == symbol) {
|
|
break;
|
|
}
|
|
if (aCheckForBraces) {
|
|
if ('}' == symbol) {
|
|
UngetToken();
|
|
break;
|
|
}
|
|
}
|
|
if ('{' == symbol) {
|
|
SkipUntil('}');
|
|
} else if ('(' == symbol) {
|
|
SkipUntil(')');
|
|
} else if ('[' == symbol) {
|
|
SkipUntil(']');
|
|
}
|
|
} else if (eCSSToken_Function == tk->mType ||
|
|
eCSSToken_Bad_URL == tk->mType) {
|
|
SkipUntil(')');
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::SkipRuleSet(bool aInsideBraces)
|
|
{
|
|
nsCSSToken* tk = &mToken;
|
|
for (;;) {
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PESkipRSBraceEOF);
|
|
break;
|
|
}
|
|
if (eCSSToken_Symbol == tk->mType) {
|
|
char16_t symbol = tk->mSymbol;
|
|
if ('}' == symbol && aInsideBraces) {
|
|
// leave block closer for higher-level grammar to consume
|
|
UngetToken();
|
|
break;
|
|
} else if ('{' == symbol) {
|
|
SkipUntil('}');
|
|
break;
|
|
} else if ('(' == symbol) {
|
|
SkipUntil(')');
|
|
} else if ('[' == symbol) {
|
|
SkipUntil(']');
|
|
}
|
|
} else if (eCSSToken_Function == tk->mType ||
|
|
eCSSToken_Bad_URL == tk->mType) {
|
|
SkipUntil(')');
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::PushGroup(css::GroupRule* aRule)
|
|
{
|
|
mGroupStack.AppendElement(aRule);
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::PopGroup()
|
|
{
|
|
uint32_t count = mGroupStack.Length();
|
|
if (0 < count) {
|
|
mGroupStack.RemoveElementAt(count - 1);
|
|
}
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::AppendRule(css::Rule* aRule)
|
|
{
|
|
uint32_t count = mGroupStack.Length();
|
|
if (0 < count) {
|
|
mGroupStack[count - 1]->AppendStyleRule(aRule);
|
|
}
|
|
else {
|
|
mSheet->AppendStyleRule(aRule);
|
|
}
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseRuleSet(RuleAppendFunc aAppendFunc, void* aData,
|
|
bool aInsideBraces)
|
|
{
|
|
// First get the list of selectors for the rule
|
|
nsCSSSelectorList* slist = nullptr;
|
|
uint32_t linenum, colnum;
|
|
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
|
|
!ParseSelectorList(slist, char16_t('{'))) {
|
|
REPORT_UNEXPECTED(PEBadSelectorRSIgnored);
|
|
OUTPUT_ERROR();
|
|
SkipRuleSet(aInsideBraces);
|
|
return false;
|
|
}
|
|
NS_ASSERTION(nullptr != slist, "null selector list");
|
|
CLEAR_ERROR();
|
|
|
|
// Next parse the declaration block
|
|
uint32_t parseFlags = eParseDeclaration_InBraces |
|
|
eParseDeclaration_AllowImportant;
|
|
RefPtr<css::Declaration> declaration = ParseDeclarationBlock(parseFlags);
|
|
if (nullptr == declaration) {
|
|
delete slist;
|
|
return false;
|
|
}
|
|
|
|
#if 0
|
|
slist->Dump();
|
|
fputs("{\n", stdout);
|
|
declaration->List();
|
|
fputs("}\n", stdout);
|
|
#endif
|
|
|
|
// Translate the selector list and declaration block into style data
|
|
|
|
RefPtr<css::StyleRule> rule = new css::StyleRule(slist, declaration,
|
|
linenum, colnum);
|
|
(*aAppendFunc)(rule, aData);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseSelectorList(nsCSSSelectorList*& aListHead,
|
|
char16_t aStopChar)
|
|
{
|
|
nsCSSSelectorList* list = nullptr;
|
|
if (! ParseSelectorGroup(list)) {
|
|
// must have at least one selector group
|
|
aListHead = nullptr;
|
|
return false;
|
|
}
|
|
NS_ASSERTION(nullptr != list, "no selector list");
|
|
aListHead = list;
|
|
|
|
// After that there must either be a "," or a "{" (the latter if
|
|
// StopChar is nonzero)
|
|
nsCSSToken* tk = &mToken;
|
|
for (;;) {
|
|
if (! GetToken(true)) {
|
|
if (aStopChar == char16_t(0)) {
|
|
return true;
|
|
}
|
|
|
|
REPORT_UNEXPECTED_EOF(PESelectorListExtraEOF);
|
|
break;
|
|
}
|
|
|
|
if (eCSSToken_Symbol == tk->mType) {
|
|
if (',' == tk->mSymbol) {
|
|
nsCSSSelectorList* newList = nullptr;
|
|
// Another selector group must follow
|
|
if (! ParseSelectorGroup(newList)) {
|
|
break;
|
|
}
|
|
// add new list to the end of the selector list
|
|
list->mNext = newList;
|
|
list = newList;
|
|
continue;
|
|
} else if (aStopChar == tk->mSymbol && aStopChar != char16_t(0)) {
|
|
UngetToken();
|
|
return true;
|
|
}
|
|
}
|
|
REPORT_UNEXPECTED_TOKEN(PESelectorListExtra);
|
|
UngetToken();
|
|
break;
|
|
}
|
|
|
|
delete aListHead;
|
|
aListHead = nullptr;
|
|
return false;
|
|
}
|
|
|
|
static bool IsUniversalSelector(const nsCSSSelector& aSelector)
|
|
{
|
|
return bool((aSelector.mNameSpace == kNameSpaceID_Unknown) &&
|
|
(aSelector.mLowercaseTag == nullptr) &&
|
|
(aSelector.mIDList == nullptr) &&
|
|
(aSelector.mClassList == nullptr) &&
|
|
(aSelector.mAttrList == nullptr) &&
|
|
(aSelector.mNegations == nullptr) &&
|
|
(aSelector.mPseudoClassList == nullptr));
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseSelectorGroup(nsCSSSelectorList*& aList)
|
|
{
|
|
char16_t combinator = 0;
|
|
nsAutoPtr<nsCSSSelectorList> list(new nsCSSSelectorList());
|
|
|
|
for (;;) {
|
|
if (!ParseSelector(list, combinator)) {
|
|
return false;
|
|
}
|
|
|
|
// Look for a combinator.
|
|
if (!GetToken(false)) {
|
|
break; // EOF ok here
|
|
}
|
|
|
|
combinator = char16_t(0);
|
|
if (mToken.mType == eCSSToken_Whitespace) {
|
|
if (!GetToken(true)) {
|
|
break; // EOF ok here
|
|
}
|
|
combinator = char16_t(' ');
|
|
}
|
|
|
|
if (mToken.mType != eCSSToken_Symbol) {
|
|
UngetToken(); // not a combinator
|
|
} else {
|
|
char16_t symbol = mToken.mSymbol;
|
|
if (symbol == '+' || symbol == '>' || symbol == '~') {
|
|
combinator = mToken.mSymbol;
|
|
} else {
|
|
UngetToken(); // not a combinator
|
|
if (symbol == ',' || symbol == '{' || symbol == ')') {
|
|
break; // end of selector group
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!combinator) {
|
|
REPORT_UNEXPECTED_TOKEN(PESelectorListExtra);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
aList = list.forget();
|
|
return true;
|
|
}
|
|
|
|
#define SEL_MASK_NSPACE 0x01
|
|
#define SEL_MASK_ELEM 0x02
|
|
#define SEL_MASK_ID 0x04
|
|
#define SEL_MASK_CLASS 0x08
|
|
#define SEL_MASK_ATTRIB 0x10
|
|
#define SEL_MASK_PCLASS 0x20
|
|
#define SEL_MASK_PELEM 0x40
|
|
|
|
//
|
|
// Parses an ID selector #name
|
|
//
|
|
CSSParserImpl::nsSelectorParsingStatus
|
|
CSSParserImpl::ParseIDSelector(int32_t& aDataMask,
|
|
nsCSSSelector& aSelector)
|
|
{
|
|
NS_ASSERTION(!mToken.mIdent.IsEmpty(),
|
|
"Empty mIdent in eCSSToken_ID token?");
|
|
aDataMask |= SEL_MASK_ID;
|
|
aSelector.AddID(mToken.mIdent);
|
|
return eSelectorParsingStatus_Continue;
|
|
}
|
|
|
|
//
|
|
// Parses a class selector .name
|
|
//
|
|
CSSParserImpl::nsSelectorParsingStatus
|
|
CSSParserImpl::ParseClassSelector(int32_t& aDataMask,
|
|
nsCSSSelector& aSelector)
|
|
{
|
|
if (! GetToken(false)) { // get ident
|
|
REPORT_UNEXPECTED_EOF(PEClassSelEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
if (eCSSToken_Ident != mToken.mType) { // malformed selector
|
|
REPORT_UNEXPECTED_TOKEN(PEClassSelNotIdent);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
aDataMask |= SEL_MASK_CLASS;
|
|
|
|
aSelector.AddClass(mToken.mIdent);
|
|
|
|
return eSelectorParsingStatus_Continue;
|
|
}
|
|
|
|
//
|
|
// Parse a type element selector or a universal selector
|
|
// namespace|type or namespace|* or *|* or *
|
|
//
|
|
CSSParserImpl::nsSelectorParsingStatus
|
|
CSSParserImpl::ParseTypeOrUniversalSelector(int32_t& aDataMask,
|
|
nsCSSSelector& aSelector,
|
|
bool aIsNegated)
|
|
{
|
|
nsAutoString buffer;
|
|
if (mToken.IsSymbol('*')) { // universal element selector, or universal namespace
|
|
if (ExpectSymbol('|', false)) { // was namespace
|
|
aDataMask |= SEL_MASK_NSPACE;
|
|
aSelector.SetNameSpace(kNameSpaceID_Unknown); // namespace wildcard
|
|
|
|
if (! GetToken(false)) {
|
|
REPORT_UNEXPECTED_EOF(PETypeSelEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
if (eCSSToken_Ident == mToken.mType) { // element name
|
|
aDataMask |= SEL_MASK_ELEM;
|
|
|
|
aSelector.SetTag(mToken.mIdent);
|
|
}
|
|
else if (mToken.IsSymbol('*')) { // universal selector
|
|
aDataMask |= SEL_MASK_ELEM;
|
|
// don't set tag
|
|
}
|
|
else {
|
|
REPORT_UNEXPECTED_TOKEN(PETypeSelNotType);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
}
|
|
else { // was universal element selector
|
|
SetDefaultNamespaceOnSelector(aSelector);
|
|
aDataMask |= SEL_MASK_ELEM;
|
|
// don't set any tag in the selector
|
|
}
|
|
if (! GetToken(false)) { // premature eof is ok (here!)
|
|
return eSelectorParsingStatus_Done;
|
|
}
|
|
}
|
|
else if (eCSSToken_Ident == mToken.mType) { // element name or namespace name
|
|
buffer = mToken.mIdent; // hang on to ident
|
|
|
|
if (ExpectSymbol('|', false)) { // was namespace
|
|
aDataMask |= SEL_MASK_NSPACE;
|
|
int32_t nameSpaceID = GetNamespaceIdForPrefix(buffer);
|
|
if (nameSpaceID == kNameSpaceID_Unknown) {
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
aSelector.SetNameSpace(nameSpaceID);
|
|
|
|
if (! GetToken(false)) {
|
|
REPORT_UNEXPECTED_EOF(PETypeSelEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
if (eCSSToken_Ident == mToken.mType) { // element name
|
|
aDataMask |= SEL_MASK_ELEM;
|
|
aSelector.SetTag(mToken.mIdent);
|
|
}
|
|
else if (mToken.IsSymbol('*')) { // universal selector
|
|
aDataMask |= SEL_MASK_ELEM;
|
|
// don't set tag
|
|
}
|
|
else {
|
|
REPORT_UNEXPECTED_TOKEN(PETypeSelNotType);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
}
|
|
else { // was element name
|
|
SetDefaultNamespaceOnSelector(aSelector);
|
|
aSelector.SetTag(buffer);
|
|
|
|
aDataMask |= SEL_MASK_ELEM;
|
|
}
|
|
if (! GetToken(false)) { // premature eof is ok (here!)
|
|
return eSelectorParsingStatus_Done;
|
|
}
|
|
}
|
|
else if (mToken.IsSymbol('|')) { // No namespace
|
|
aDataMask |= SEL_MASK_NSPACE;
|
|
aSelector.SetNameSpace(kNameSpaceID_None); // explicit NO namespace
|
|
|
|
// get mandatory tag
|
|
if (! GetToken(false)) {
|
|
REPORT_UNEXPECTED_EOF(PETypeSelEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
if (eCSSToken_Ident == mToken.mType) { // element name
|
|
aDataMask |= SEL_MASK_ELEM;
|
|
aSelector.SetTag(mToken.mIdent);
|
|
}
|
|
else if (mToken.IsSymbol('*')) { // universal selector
|
|
aDataMask |= SEL_MASK_ELEM;
|
|
// don't set tag
|
|
}
|
|
else {
|
|
REPORT_UNEXPECTED_TOKEN(PETypeSelNotType);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
if (! GetToken(false)) { // premature eof is ok (here!)
|
|
return eSelectorParsingStatus_Done;
|
|
}
|
|
}
|
|
else {
|
|
SetDefaultNamespaceOnSelector(aSelector);
|
|
}
|
|
|
|
if (aIsNegated) {
|
|
// restore last token read in case of a negated type selector
|
|
UngetToken();
|
|
}
|
|
return eSelectorParsingStatus_Continue;
|
|
}
|
|
|
|
//
|
|
// Parse attribute selectors [attr], [attr=value], [attr|=value],
|
|
// [attr~=value], [attr^=value], [attr$=value] and [attr*=value]
|
|
//
|
|
CSSParserImpl::nsSelectorParsingStatus
|
|
CSSParserImpl::ParseAttributeSelector(int32_t& aDataMask,
|
|
nsCSSSelector& aSelector)
|
|
{
|
|
if (! GetToken(true)) { // premature EOF
|
|
REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
|
|
int32_t nameSpaceID = kNameSpaceID_None;
|
|
nsAutoString attr;
|
|
if (mToken.IsSymbol('*')) { // wildcard namespace
|
|
nameSpaceID = kNameSpaceID_Unknown;
|
|
if (ExpectSymbol('|', false)) {
|
|
if (! GetToken(false)) { // premature EOF
|
|
REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
if (eCSSToken_Ident == mToken.mType) { // attr name
|
|
attr = mToken.mIdent;
|
|
}
|
|
else {
|
|
REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
}
|
|
else {
|
|
REPORT_UNEXPECTED_TOKEN(PEAttSelNoBar);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
}
|
|
else if (mToken.IsSymbol('|')) { // NO namespace
|
|
if (! GetToken(false)) { // premature EOF
|
|
REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
if (eCSSToken_Ident == mToken.mType) { // attr name
|
|
attr = mToken.mIdent;
|
|
}
|
|
else {
|
|
REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
}
|
|
else if (eCSSToken_Ident == mToken.mType) { // attr name or namespace
|
|
attr = mToken.mIdent; // hang on to it
|
|
if (ExpectSymbol('|', false)) { // was a namespace
|
|
nameSpaceID = GetNamespaceIdForPrefix(attr);
|
|
if (nameSpaceID == kNameSpaceID_Unknown) {
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
if (! GetToken(false)) { // premature EOF
|
|
REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
if (eCSSToken_Ident == mToken.mType) { // attr name
|
|
attr = mToken.mIdent;
|
|
}
|
|
else {
|
|
REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
}
|
|
}
|
|
else { // malformed
|
|
REPORT_UNEXPECTED_TOKEN(PEAttributeNameOrNamespaceExpected);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
|
|
bool gotEOF = false;
|
|
if (! GetToken(true)) { // premature EOF
|
|
// Treat this just like we saw a ']', but do still output the
|
|
// warning, similar to what ExpectSymbol does.
|
|
REPORT_UNEXPECTED_EOF(PEAttSelInnerEOF);
|
|
gotEOF = true;
|
|
}
|
|
if (gotEOF ||
|
|
(eCSSToken_Symbol == mToken.mType) ||
|
|
(eCSSToken_Includes == mToken.mType) ||
|
|
(eCSSToken_Dashmatch == mToken.mType) ||
|
|
(eCSSToken_Beginsmatch == mToken.mType) ||
|
|
(eCSSToken_Endsmatch == mToken.mType) ||
|
|
(eCSSToken_Containsmatch == mToken.mType)) {
|
|
uint8_t func;
|
|
// Important: Check the EOF/']' case first, since if gotEOF we
|
|
// don't want to be examining mToken.
|
|
if (gotEOF || ']' == mToken.mSymbol) {
|
|
aDataMask |= SEL_MASK_ATTRIB;
|
|
aSelector.AddAttribute(nameSpaceID, attr);
|
|
func = NS_ATTR_FUNC_SET;
|
|
}
|
|
else if (eCSSToken_Includes == mToken.mType) {
|
|
func = NS_ATTR_FUNC_INCLUDES;
|
|
}
|
|
else if (eCSSToken_Dashmatch == mToken.mType) {
|
|
func = NS_ATTR_FUNC_DASHMATCH;
|
|
}
|
|
else if (eCSSToken_Beginsmatch == mToken.mType) {
|
|
func = NS_ATTR_FUNC_BEGINSMATCH;
|
|
}
|
|
else if (eCSSToken_Endsmatch == mToken.mType) {
|
|
func = NS_ATTR_FUNC_ENDSMATCH;
|
|
}
|
|
else if (eCSSToken_Containsmatch == mToken.mType) {
|
|
func = NS_ATTR_FUNC_CONTAINSMATCH;
|
|
}
|
|
else if ('=' == mToken.mSymbol) {
|
|
func = NS_ATTR_FUNC_EQUALS;
|
|
}
|
|
else {
|
|
REPORT_UNEXPECTED_TOKEN(PEAttSelUnexpected);
|
|
UngetToken(); // bad function
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
if (NS_ATTR_FUNC_SET != func) { // get value
|
|
if (! GetToken(true)) { // premature EOF
|
|
REPORT_UNEXPECTED_EOF(PEAttSelValueEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
if ((eCSSToken_Ident == mToken.mType) || (eCSSToken_String == mToken.mType)) {
|
|
nsAutoString value(mToken.mIdent);
|
|
// Avoid duplicating the eof handling by just not checking for
|
|
// the 'i' annotation if we got eof.
|
|
typedef nsAttrSelector::ValueCaseSensitivity ValueCaseSensitivity;
|
|
ValueCaseSensitivity valueCaseSensitivity =
|
|
ValueCaseSensitivity::CaseSensitive;
|
|
bool eof = !GetToken(true);
|
|
if (!eof) {
|
|
if (eCSSToken_Ident == mToken.mType &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("i")) {
|
|
valueCaseSensitivity = ValueCaseSensitivity::CaseInsensitive;
|
|
eof = !GetToken(true);
|
|
}
|
|
}
|
|
bool gotClosingBracket;
|
|
if (eof) { // premature EOF
|
|
// Report a warning, but then treat it as a closing bracket.
|
|
REPORT_UNEXPECTED_EOF(PEAttSelCloseEOF);
|
|
gotClosingBracket = true;
|
|
} else {
|
|
gotClosingBracket = mToken.IsSymbol(']');
|
|
}
|
|
if (gotClosingBracket) {
|
|
// For cases when this style sheet is applied to an HTML
|
|
// element in an HTML document, and the attribute selector is
|
|
// for a non-namespaced attribute, then check to see if it's
|
|
// one of the known attributes whose VALUE is
|
|
// case-insensitive.
|
|
if (valueCaseSensitivity != ValueCaseSensitivity::CaseInsensitive &&
|
|
nameSpaceID == kNameSpaceID_None) {
|
|
static const char* caseInsensitiveHTMLAttribute[] = {
|
|
// list based on http://www.w3.org/TR/html4/
|
|
"lang",
|
|
"dir",
|
|
"http-equiv",
|
|
"text",
|
|
"link",
|
|
"vlink",
|
|
"alink",
|
|
"compact",
|
|
"align",
|
|
"frame",
|
|
"rules",
|
|
"valign",
|
|
"scope",
|
|
"axis",
|
|
"nowrap",
|
|
"hreflang",
|
|
"rel",
|
|
"rev",
|
|
"charset",
|
|
"codetype",
|
|
"declare",
|
|
"valuetype",
|
|
"shape",
|
|
"nohref",
|
|
"media",
|
|
"bgcolor",
|
|
"clear",
|
|
"color",
|
|
"face",
|
|
"noshade",
|
|
"noresize",
|
|
"scrolling",
|
|
"target",
|
|
"method",
|
|
"enctype",
|
|
"accept-charset",
|
|
"accept",
|
|
"checked",
|
|
"multiple",
|
|
"selected",
|
|
"disabled",
|
|
"readonly",
|
|
"language",
|
|
"defer",
|
|
"type",
|
|
// additional attributes not in HTML4
|
|
"direction", // marquee
|
|
nullptr
|
|
};
|
|
short i = 0;
|
|
const char* htmlAttr;
|
|
while ((htmlAttr = caseInsensitiveHTMLAttribute[i++])) {
|
|
if (attr.LowerCaseEqualsASCII(htmlAttr)) {
|
|
valueCaseSensitivity = ValueCaseSensitivity::CaseInsensitiveInHTML;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
aDataMask |= SEL_MASK_ATTRIB;
|
|
aSelector.AddAttribute(nameSpaceID, attr, func, value,
|
|
valueCaseSensitivity);
|
|
}
|
|
else {
|
|
REPORT_UNEXPECTED_TOKEN(PEAttSelNoClose);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
}
|
|
else {
|
|
REPORT_UNEXPECTED_TOKEN(PEAttSelBadValue);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
REPORT_UNEXPECTED_TOKEN(PEAttSelUnexpected);
|
|
UngetToken(); // bad dog, no biscut!
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
return eSelectorParsingStatus_Continue;
|
|
}
|
|
|
|
//
|
|
// Parse pseudo-classes and pseudo-elements
|
|
//
|
|
CSSParserImpl::nsSelectorParsingStatus
|
|
CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
|
|
nsCSSSelector& aSelector,
|
|
bool aIsNegated,
|
|
nsIAtom** aPseudoElement,
|
|
nsAtomList** aPseudoElementArgs,
|
|
CSSPseudoElementType* aPseudoElementType)
|
|
{
|
|
NS_ASSERTION(aIsNegated || (aPseudoElement && aPseudoElementArgs),
|
|
"expected location to store pseudo element");
|
|
NS_ASSERTION(!aIsNegated || (!aPseudoElement && !aPseudoElementArgs),
|
|
"negated selectors shouldn't have a place to store "
|
|
"pseudo elements");
|
|
if (! GetToken(false)) { // premature eof
|
|
REPORT_UNEXPECTED_EOF(PEPseudoSelEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
|
|
// First, find out whether we are parsing a CSS3 pseudo-element
|
|
bool parsingPseudoElement = false;
|
|
if (mToken.IsSymbol(':')) {
|
|
parsingPseudoElement = true;
|
|
if (! GetToken(false)) { // premature eof
|
|
REPORT_UNEXPECTED_EOF(PEPseudoSelEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
}
|
|
|
|
// Do some sanity-checking on the token
|
|
if (eCSSToken_Ident != mToken.mType && eCSSToken_Function != mToken.mType) {
|
|
// malformed selector
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoSelBadName);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
|
|
// OK, now we know we have an mIdent. Atomize it. All the atoms, for
|
|
// pseudo-classes as well as pseudo-elements, start with a single ':'.
|
|
nsAutoString buffer;
|
|
buffer.Append(char16_t(':'));
|
|
buffer.Append(mToken.mIdent);
|
|
nsContentUtils::ASCIIToLower(buffer);
|
|
nsCOMPtr<nsIAtom> pseudo = NS_Atomize(buffer);
|
|
|
|
// stash away some info about this pseudo so we only have to get it once.
|
|
bool isTreePseudo = false;
|
|
CSSEnabledState enabledState = EnabledState();
|
|
CSSPseudoElementType pseudoElementType =
|
|
nsCSSPseudoElements::GetPseudoType(pseudo, enabledState);
|
|
CSSPseudoClassType pseudoClassType =
|
|
nsCSSPseudoClasses::GetPseudoType(pseudo, enabledState);
|
|
bool pseudoClassIsUserAction =
|
|
nsCSSPseudoClasses::IsUserActionPseudoClass(pseudoClassType);
|
|
|
|
if (nsCSSAnonBoxes::IsNonElement(pseudo)) {
|
|
// Non-element anonymous boxes should not match any rule.
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoSelUnknown);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
|
|
// We currently allow :-moz-placeholder and ::-moz-placeholder. We have to
|
|
// be a bit stricter regarding the pseudo-element parsing rules.
|
|
if (pseudoElementType == CSSPseudoElementType::mozPlaceholder &&
|
|
pseudoClassType == CSSPseudoClassType::mozPlaceholder) {
|
|
if (parsingPseudoElement) {
|
|
pseudoClassType = CSSPseudoClassType::NotPseudo;
|
|
} else {
|
|
pseudoElementType = CSSPseudoElementType::NotPseudo;
|
|
}
|
|
}
|
|
|
|
#ifdef MOZ_XUL
|
|
isTreePseudo = (pseudoElementType == CSSPseudoElementType::XULTree);
|
|
// If a tree pseudo-element is using the function syntax, it will
|
|
// get isTree set here and will pass the check below that only
|
|
// allows functions if they are in our list of things allowed to be
|
|
// functions. If it is _not_ using the function syntax, isTree will
|
|
// be false, and it will still pass that check. So the tree
|
|
// pseudo-elements are allowed to be either functions or not, as
|
|
// desired.
|
|
bool isTree = (eCSSToken_Function == mToken.mType) && isTreePseudo;
|
|
#endif
|
|
bool isPseudoElement = (pseudoElementType < CSSPseudoElementType::Count);
|
|
// anonymous boxes are only allowed if they're the tree boxes or we have
|
|
// enabled agent rules
|
|
bool isAnonBox = isTreePseudo ||
|
|
(pseudoElementType == CSSPseudoElementType::AnonBox &&
|
|
AgentRulesEnabled());
|
|
bool isPseudoClass =
|
|
(pseudoClassType != CSSPseudoClassType::NotPseudo);
|
|
|
|
NS_ASSERTION(!isPseudoClass ||
|
|
pseudoElementType == CSSPseudoElementType::NotPseudo,
|
|
"Why is this atom both a pseudo-class and a pseudo-element?");
|
|
NS_ASSERTION(isPseudoClass + isPseudoElement + isAnonBox <= 1,
|
|
"Shouldn't be more than one of these");
|
|
|
|
if (!isPseudoClass && !isPseudoElement && !isAnonBox) {
|
|
// Not a pseudo-class, not a pseudo-element.... forget it
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoSelUnknown);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
|
|
// If it's a function token, it better be on our "ok" list, and if the name
|
|
// is that of a function pseudo it better be a function token
|
|
if ((eCSSToken_Function == mToken.mType) !=
|
|
(
|
|
#ifdef MOZ_XUL
|
|
isTree ||
|
|
#endif
|
|
CSSPseudoClassType::negation == pseudoClassType ||
|
|
nsCSSPseudoClasses::HasStringArg(pseudoClassType) ||
|
|
nsCSSPseudoClasses::HasNthPairArg(pseudoClassType) ||
|
|
nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType))) {
|
|
// There are no other function pseudos
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoSelNonFunc);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
|
|
// If it starts with "::", it better be a pseudo-element
|
|
if (parsingPseudoElement &&
|
|
!isPseudoElement &&
|
|
!isAnonBox) {
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoSelNotPE);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
|
|
if (aSelector.IsPseudoElement()) {
|
|
CSSPseudoElementType type = aSelector.PseudoType();
|
|
if (!nsCSSPseudoElements::PseudoElementSupportsUserActionState(type)) {
|
|
// We only allow user action pseudo-classes on certain pseudo-elements.
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoSelNoUserActionPC);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
if (!isPseudoClass || !pseudoClassIsUserAction) {
|
|
// CSS 4 Selectors says that pseudo-elements can only be followed by
|
|
// a user action pseudo-class.
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoClassNotUserAction);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
}
|
|
|
|
if (!parsingPseudoElement &&
|
|
CSSPseudoClassType::negation == pseudoClassType) {
|
|
if (aIsNegated) { // :not() can't be itself negated
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoSelDoubleNot);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
// CSS 3 Negation pseudo-class takes one simple selector as argument
|
|
nsSelectorParsingStatus parsingStatus =
|
|
ParseNegatedSimpleSelector(aDataMask, aSelector);
|
|
if (eSelectorParsingStatus_Continue != parsingStatus) {
|
|
return parsingStatus;
|
|
}
|
|
}
|
|
else if (!parsingPseudoElement && isPseudoClass) {
|
|
aDataMask |= SEL_MASK_PCLASS;
|
|
if (eCSSToken_Function == mToken.mType) {
|
|
nsSelectorParsingStatus parsingStatus;
|
|
if (nsCSSPseudoClasses::HasStringArg(pseudoClassType)) {
|
|
parsingStatus =
|
|
ParsePseudoClassWithIdentArg(aSelector, pseudoClassType);
|
|
}
|
|
else if (nsCSSPseudoClasses::HasNthPairArg(pseudoClassType)) {
|
|
parsingStatus =
|
|
ParsePseudoClassWithNthPairArg(aSelector, pseudoClassType);
|
|
}
|
|
else {
|
|
MOZ_ASSERT(nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType),
|
|
"unexpected pseudo with function token");
|
|
parsingStatus = ParsePseudoClassWithSelectorListArg(aSelector,
|
|
pseudoClassType);
|
|
}
|
|
if (eSelectorParsingStatus_Continue != parsingStatus) {
|
|
if (eSelectorParsingStatus_Error == parsingStatus) {
|
|
SkipUntil(')');
|
|
}
|
|
return parsingStatus;
|
|
}
|
|
}
|
|
else {
|
|
aSelector.AddPseudoClass(pseudoClassType);
|
|
}
|
|
}
|
|
else if (isPseudoElement || isAnonBox) {
|
|
// Pseudo-element. Make some more sanity checks.
|
|
|
|
if (aIsNegated) { // pseudo-elements can't be negated
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoSelPEInNot);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
// CSS2 pseudo-elements and -moz-tree-* pseudo-elements are allowed
|
|
// to have a single ':' on them. Others (CSS3+ pseudo-elements and
|
|
// various -moz-* pseudo-elements) must have |parsingPseudoElement|
|
|
// set.
|
|
if (!parsingPseudoElement &&
|
|
!nsCSSPseudoElements::IsCSS2PseudoElement(pseudo)
|
|
#ifdef MOZ_XUL
|
|
&& !isTreePseudo
|
|
#endif
|
|
) {
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoSelNewStyleOnly);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
|
|
if (0 == (aDataMask & SEL_MASK_PELEM)) {
|
|
aDataMask |= SEL_MASK_PELEM;
|
|
NS_ADDREF(*aPseudoElement = pseudo);
|
|
*aPseudoElementType = pseudoElementType;
|
|
|
|
#ifdef MOZ_XUL
|
|
if (isTree) {
|
|
// We have encountered a pseudoelement of the form
|
|
// -moz-tree-xxxx(a,b,c). We parse (a,b,c) and add each
|
|
// item in the list to the pseudoclass list. They will be pulled
|
|
// from the list later along with the pseudo-element.
|
|
if (!ParseTreePseudoElement(aPseudoElementArgs)) {
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Pseudo-elements can only be followed by user action pseudo-classes
|
|
// or be the end of the selector. So the next non-whitespace token must
|
|
// be ':', '{' or ',' or EOF.
|
|
if (!GetToken(true)) { // premature eof is ok (here!)
|
|
return eSelectorParsingStatus_Done;
|
|
}
|
|
if (parsingPseudoElement && mToken.IsSymbol(':')) {
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Continue;
|
|
}
|
|
if ((mToken.IsSymbol('{') || mToken.IsSymbol(','))) {
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Done;
|
|
}
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoSelEndOrUserActionPC);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
else { // multiple pseudo elements, not legal
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoSelMultiplePE);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
}
|
|
#ifdef DEBUG
|
|
else {
|
|
// We should never end up here. Indeed, if we ended up here, we know (from
|
|
// the current if/else cascade) that !isPseudoElement and !isAnonBox. But
|
|
// then due to our earlier check we know that isPseudoClass. Since we
|
|
// didn't fall into the isPseudoClass case in this cascade, we must have
|
|
// parsingPseudoElement. But we've already checked the
|
|
// parsingPseudoElement && !isPseudoClass && !isAnonBox case and bailed if
|
|
// it's happened.
|
|
NS_NOTREACHED("How did this happen?");
|
|
}
|
|
#endif
|
|
return eSelectorParsingStatus_Continue;
|
|
}
|
|
|
|
//
|
|
// Parse the argument of a negation pseudo-class :not()
|
|
//
|
|
CSSParserImpl::nsSelectorParsingStatus
|
|
CSSParserImpl::ParseNegatedSimpleSelector(int32_t& aDataMask,
|
|
nsCSSSelector& aSelector)
|
|
{
|
|
if (! GetToken(true)) { // premature eof
|
|
REPORT_UNEXPECTED_EOF(PENegationEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
|
|
if (mToken.IsSymbol(')')) {
|
|
REPORT_UNEXPECTED_TOKEN(PENegationBadArg);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
|
|
// Create a new nsCSSSelector and add it to the end of
|
|
// aSelector.mNegations.
|
|
// Given the current parsing rules, every selector in mNegations
|
|
// contains only one simple selector (css3 definition) within it.
|
|
// This could easily change in future versions of CSS, and the only
|
|
// thing we need to change to support that is this parsing code and the
|
|
// serialization code for nsCSSSelector.
|
|
nsCSSSelector *newSel = new nsCSSSelector();
|
|
nsCSSSelector* negations = &aSelector;
|
|
while (negations->mNegations) {
|
|
negations = negations->mNegations;
|
|
}
|
|
negations->mNegations = newSel;
|
|
|
|
nsSelectorParsingStatus parsingStatus;
|
|
if (eCSSToken_ID == mToken.mType) { // #id
|
|
parsingStatus = ParseIDSelector(aDataMask, *newSel);
|
|
}
|
|
else if (mToken.IsSymbol('.')) { // .class
|
|
parsingStatus = ParseClassSelector(aDataMask, *newSel);
|
|
}
|
|
else if (mToken.IsSymbol(':')) { // :pseudo
|
|
parsingStatus = ParsePseudoSelector(aDataMask, *newSel, true,
|
|
nullptr, nullptr, nullptr);
|
|
}
|
|
else if (mToken.IsSymbol('[')) { // [attribute
|
|
parsingStatus = ParseAttributeSelector(aDataMask, *newSel);
|
|
if (eSelectorParsingStatus_Error == parsingStatus) {
|
|
// Skip forward to the matching ']'
|
|
SkipUntil(']');
|
|
}
|
|
}
|
|
else {
|
|
// then it should be a type element or universal selector
|
|
parsingStatus = ParseTypeOrUniversalSelector(aDataMask, *newSel, true);
|
|
}
|
|
if (eSelectorParsingStatus_Error == parsingStatus) {
|
|
REPORT_UNEXPECTED_TOKEN(PENegationBadInner);
|
|
SkipUntil(')');
|
|
return parsingStatus;
|
|
}
|
|
// close the parenthesis
|
|
if (!ExpectSymbol(')', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PENegationNoClose);
|
|
SkipUntil(')');
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
|
|
NS_ASSERTION(newSel->mNameSpace == kNameSpaceID_Unknown ||
|
|
(!newSel->mIDList && !newSel->mClassList &&
|
|
!newSel->mPseudoClassList && !newSel->mAttrList),
|
|
"Need to fix the serialization code to deal with this");
|
|
|
|
return eSelectorParsingStatus_Continue;
|
|
}
|
|
|
|
//
|
|
// Parse the argument of a pseudo-class that has an ident arg
|
|
//
|
|
CSSParserImpl::nsSelectorParsingStatus
|
|
CSSParserImpl::ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector,
|
|
CSSPseudoClassType aType)
|
|
{
|
|
if (! GetToken(true)) { // premature eof
|
|
REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
// We expect an identifier with a language abbreviation
|
|
if (eCSSToken_Ident != mToken.mType) {
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotIdent);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
|
|
}
|
|
|
|
// -moz-locale-dir and dir take an identifier argument. While
|
|
// only 'ltr' and 'rtl' (case-insensitively) will match anything, any
|
|
// other identifier is still valid.
|
|
if (aType == CSSPseudoClassType::mozLocaleDir ||
|
|
aType == CSSPseudoClassType::mozDir ||
|
|
aType == CSSPseudoClassType::dir) {
|
|
nsContentUtils::ASCIIToLower(mToken.mIdent); // case insensitive
|
|
}
|
|
|
|
// Add the pseudo with the language parameter
|
|
aSelector.AddPseudoClass(aType, mToken.mIdent.get());
|
|
|
|
// close the parenthesis
|
|
if (!ExpectSymbol(')', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose);
|
|
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
|
|
}
|
|
|
|
return eSelectorParsingStatus_Continue;
|
|
}
|
|
|
|
CSSParserImpl::nsSelectorParsingStatus
|
|
CSSParserImpl::ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
|
|
CSSPseudoClassType aType)
|
|
{
|
|
int32_t numbers[2] = { 0, 0 };
|
|
int32_t sign[2] = { 1, 1 };
|
|
bool hasSign[2] = { false, false };
|
|
bool lookForB = true;
|
|
|
|
// Follow the whitespace rules as proposed in
|
|
// http://lists.w3.org/Archives/Public/www-style/2008Mar/0121.html
|
|
|
|
if (! GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
|
|
if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) {
|
|
hasSign[0] = true;
|
|
if (mToken.IsSymbol('-')) {
|
|
sign[0] = -1;
|
|
}
|
|
if (! GetToken(false)) {
|
|
REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
}
|
|
|
|
// A helper function that checks if the token starts with literal string
|
|
// |aStr| using a case-insensitive match.
|
|
auto TokenBeginsWith = [this] (const nsLiteralString& aStr) {
|
|
return StringBeginsWith(mToken.mIdent, aStr,
|
|
nsASCIICaseInsensitiveStringComparator());
|
|
};
|
|
|
|
if (eCSSToken_Ident == mToken.mType || eCSSToken_Dimension == mToken.mType) {
|
|
// The CSS tokenization doesn't handle :nth-child() containing - well:
|
|
// 2n-1 is a dimension
|
|
// n-1 is an identifier
|
|
// The easiest way to deal with that is to push everything from the
|
|
// minus on back onto the scanner's pushback buffer.
|
|
uint32_t truncAt = 0;
|
|
if (TokenBeginsWith(NS_LITERAL_STRING("n-"))) {
|
|
truncAt = 1;
|
|
} else if (TokenBeginsWith(NS_LITERAL_STRING("-n-")) && !hasSign[0]) {
|
|
truncAt = 2;
|
|
}
|
|
if (truncAt != 0) {
|
|
mScanner->Backup(mToken.mIdent.Length() - truncAt);
|
|
mToken.mIdent.Truncate(truncAt);
|
|
}
|
|
}
|
|
|
|
if (eCSSToken_Ident == mToken.mType) {
|
|
if (mToken.mIdent.LowerCaseEqualsLiteral("odd") && !hasSign[0]) {
|
|
numbers[0] = 2;
|
|
numbers[1] = 1;
|
|
lookForB = false;
|
|
}
|
|
else if (mToken.mIdent.LowerCaseEqualsLiteral("even") && !hasSign[0]) {
|
|
numbers[0] = 2;
|
|
numbers[1] = 0;
|
|
lookForB = false;
|
|
}
|
|
else if (mToken.mIdent.LowerCaseEqualsLiteral("n")) {
|
|
numbers[0] = sign[0];
|
|
}
|
|
else if (mToken.mIdent.LowerCaseEqualsLiteral("-n") && !hasSign[0]) {
|
|
numbers[0] = -1;
|
|
}
|
|
else {
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
|
|
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
|
|
}
|
|
}
|
|
else if (eCSSToken_Number == mToken.mType) {
|
|
if (!mToken.mIntegerValid) {
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
|
|
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
|
|
}
|
|
// for +-an case
|
|
if (mToken.mHasSign && hasSign[0]) {
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
|
|
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
|
|
}
|
|
int32_t intValue = mToken.mInteger * sign[0];
|
|
// for -a/**/n case
|
|
if (! GetToken(false)) {
|
|
numbers[1] = intValue;
|
|
lookForB = false;
|
|
}
|
|
else {
|
|
if (eCSSToken_Ident == mToken.mType && mToken.mIdent.LowerCaseEqualsLiteral("n")) {
|
|
numbers[0] = intValue;
|
|
}
|
|
else if (eCSSToken_Ident == mToken.mType && TokenBeginsWith(NS_LITERAL_STRING("n-"))) {
|
|
numbers[0] = intValue;
|
|
mScanner->Backup(mToken.mIdent.Length() - 1);
|
|
}
|
|
else {
|
|
UngetToken();
|
|
numbers[1] = intValue;
|
|
lookForB = false;
|
|
}
|
|
}
|
|
}
|
|
else if (eCSSToken_Dimension == mToken.mType) {
|
|
if (!mToken.mIntegerValid || !mToken.mIdent.LowerCaseEqualsLiteral("n")) {
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
|
|
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
|
|
}
|
|
// for +-an case
|
|
if ( mToken.mHasSign && hasSign[0] ) {
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
|
|
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
|
|
}
|
|
numbers[0] = mToken.mInteger * sign[0];
|
|
}
|
|
// XXX If it's a ')', is that valid? (as 0n+0)
|
|
else {
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
|
|
}
|
|
|
|
if (! GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
if (lookForB && !mToken.IsSymbol(')')) {
|
|
// The '+' or '-' sign can optionally be separated by whitespace.
|
|
// If it is separated by whitespace from what follows it, it appears
|
|
// as a separate token rather than part of the number token.
|
|
if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) {
|
|
hasSign[1] = true;
|
|
if (mToken.IsSymbol('-')) {
|
|
sign[1] = -1;
|
|
}
|
|
if (! GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
}
|
|
if (eCSSToken_Number != mToken.mType ||
|
|
!mToken.mIntegerValid || mToken.mHasSign == hasSign[1]) {
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
|
|
UngetToken();
|
|
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
|
|
}
|
|
numbers[1] = mToken.mInteger * sign[1];
|
|
if (! GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
|
|
return eSelectorParsingStatus_Error;
|
|
}
|
|
}
|
|
if (!mToken.IsSymbol(')')) {
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose);
|
|
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
|
|
}
|
|
aSelector.AddPseudoClass(aType, numbers);
|
|
return eSelectorParsingStatus_Continue;
|
|
}
|
|
|
|
//
|
|
// Parse the argument of a pseudo-class that has a selector list argument.
|
|
// Such selector lists cannot contain combinators, but can contain
|
|
// anything that goes between a pair of combinators.
|
|
//
|
|
CSSParserImpl::nsSelectorParsingStatus
|
|
CSSParserImpl::ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
|
|
CSSPseudoClassType aType)
|
|
{
|
|
nsAutoPtr<nsCSSSelectorList> slist;
|
|
if (! ParseSelectorList(*getter_Transfers(slist), char16_t(')'))) {
|
|
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
|
|
}
|
|
|
|
// Check that none of the selectors in the list have combinators or
|
|
// pseudo-elements.
|
|
for (nsCSSSelectorList *l = slist; l; l = l->mNext) {
|
|
nsCSSSelector *s = l->mSelectors;
|
|
if (s->mNext || s->IsPseudoElement()) {
|
|
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
|
|
}
|
|
}
|
|
|
|
// Add the pseudo with the selector list parameter
|
|
aSelector.AddPseudoClass(aType, slist.forget());
|
|
|
|
// close the parenthesis
|
|
if (!ExpectSymbol(')', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose);
|
|
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
|
|
}
|
|
|
|
return eSelectorParsingStatus_Continue;
|
|
}
|
|
|
|
|
|
/**
|
|
* This is the format for selectors:
|
|
* operator? [[namespace |]? element_name]? [ ID | class | attrib | pseudo ]*
|
|
*/
|
|
bool
|
|
CSSParserImpl::ParseSelector(nsCSSSelectorList* aList,
|
|
char16_t aPrevCombinator)
|
|
{
|
|
if (! GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PESelectorEOF);
|
|
return false;
|
|
}
|
|
|
|
nsCSSSelector* selector = aList->AddSelector(aPrevCombinator);
|
|
nsCOMPtr<nsIAtom> pseudoElement;
|
|
nsAutoPtr<nsAtomList> pseudoElementArgs;
|
|
CSSPseudoElementType pseudoElementType = CSSPseudoElementType::NotPseudo;
|
|
|
|
int32_t dataMask = 0;
|
|
nsSelectorParsingStatus parsingStatus =
|
|
ParseTypeOrUniversalSelector(dataMask, *selector, false);
|
|
|
|
while (parsingStatus == eSelectorParsingStatus_Continue) {
|
|
if (mToken.IsSymbol(':')) { // :pseudo
|
|
parsingStatus = ParsePseudoSelector(dataMask, *selector, false,
|
|
getter_AddRefs(pseudoElement),
|
|
getter_Transfers(pseudoElementArgs),
|
|
&pseudoElementType);
|
|
if (pseudoElement &&
|
|
pseudoElementType != CSSPseudoElementType::AnonBox) {
|
|
// Pseudo-elements other than anonymous boxes are represented with
|
|
// a special ':' combinator.
|
|
|
|
aList->mWeight += selector->CalcWeight();
|
|
|
|
selector = aList->AddSelector(':');
|
|
|
|
selector->mLowercaseTag.swap(pseudoElement);
|
|
selector->mClassList = pseudoElementArgs.forget();
|
|
selector->SetPseudoType(pseudoElementType);
|
|
}
|
|
} else if (selector->IsPseudoElement()) {
|
|
// Once we parsed a pseudo-element, we can only parse
|
|
// pseudo-classes (and only a limited set, which
|
|
// ParsePseudoSelector knows how to handle).
|
|
parsingStatus = eSelectorParsingStatus_Done;
|
|
UngetToken();
|
|
break;
|
|
} else if (eCSSToken_ID == mToken.mType) { // #id
|
|
parsingStatus = ParseIDSelector(dataMask, *selector);
|
|
} else if (mToken.IsSymbol('.')) { // .class
|
|
parsingStatus = ParseClassSelector(dataMask, *selector);
|
|
}
|
|
else if (mToken.IsSymbol('[')) { // [attribute
|
|
parsingStatus = ParseAttributeSelector(dataMask, *selector);
|
|
if (eSelectorParsingStatus_Error == parsingStatus) {
|
|
SkipUntil(']');
|
|
}
|
|
}
|
|
else { // not a selector token, we're done
|
|
parsingStatus = eSelectorParsingStatus_Done;
|
|
UngetToken();
|
|
break;
|
|
}
|
|
|
|
if (parsingStatus != eSelectorParsingStatus_Continue) {
|
|
break;
|
|
}
|
|
|
|
if (! GetToken(false)) { // premature eof is ok (here!)
|
|
parsingStatus = eSelectorParsingStatus_Done;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (parsingStatus == eSelectorParsingStatus_Error) {
|
|
return false;
|
|
}
|
|
|
|
if (!dataMask) {
|
|
if (selector->mNext) {
|
|
REPORT_UNEXPECTED(PESelectorGroupExtraCombinator);
|
|
} else {
|
|
REPORT_UNEXPECTED(PESelectorGroupNoSelector);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (pseudoElementType == CSSPseudoElementType::AnonBox) {
|
|
// We got an anonymous box pseudo-element; it must be the only
|
|
// thing in this selector group.
|
|
if (selector->mNext || !IsUniversalSelector(*selector)) {
|
|
REPORT_UNEXPECTED(PEAnonBoxNotAlone);
|
|
return false;
|
|
}
|
|
|
|
// Rewrite the current selector as this pseudo-element.
|
|
// It does not contribute to selector weight.
|
|
selector->mLowercaseTag.swap(pseudoElement);
|
|
selector->mClassList = pseudoElementArgs.forget();
|
|
selector->SetPseudoType(pseudoElementType);
|
|
return true;
|
|
}
|
|
|
|
aList->mWeight += selector->CalcWeight();
|
|
|
|
return true;
|
|
}
|
|
|
|
already_AddRefed<css::Declaration>
|
|
CSSParserImpl::ParseDeclarationBlock(uint32_t aFlags, nsCSSContextType aContext)
|
|
{
|
|
bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0;
|
|
|
|
MOZ_ASSERT(mWebkitBoxUnprefixState == eNotParsingDecls,
|
|
"Someone forgot to clear mWebkitBoxUnprefixState!");
|
|
AutoRestore<WebkitBoxUnprefixState> autoRestore(mWebkitBoxUnprefixState);
|
|
mWebkitBoxUnprefixState = eHaveNotUnprefixed;
|
|
|
|
if (checkForBraces) {
|
|
if (!ExpectSymbol('{', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEBadDeclBlockStart);
|
|
OUTPUT_ERROR();
|
|
return nullptr;
|
|
}
|
|
}
|
|
RefPtr<css::Declaration> declaration = new css::Declaration();
|
|
mData.AssertInitialState();
|
|
for (;;) {
|
|
bool changed = false;
|
|
if (!ParseDeclaration(declaration, aFlags, true, &changed, aContext)) {
|
|
if (!SkipDeclaration(checkForBraces)) {
|
|
break;
|
|
}
|
|
if (checkForBraces) {
|
|
if (ExpectSymbol('}', true)) {
|
|
break;
|
|
}
|
|
}
|
|
// Since the skipped declaration didn't end the block we parse
|
|
// the next declaration.
|
|
}
|
|
}
|
|
declaration->CompressFrom(&mData);
|
|
return declaration.forget();
|
|
}
|
|
|
|
CSSParseResult
|
|
CSSParserImpl::ParseColor(nsCSSValue& aValue)
|
|
{
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEColorEOF);
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
|
|
nsCSSToken* tk = &mToken;
|
|
nscolor rgba;
|
|
switch (tk->mType) {
|
|
case eCSSToken_ID:
|
|
case eCSSToken_Hash:
|
|
// #rgb, #rrggbb, #rgba, #rrggbbaa
|
|
if (NS_HexToRGBA(tk->mIdent, nsHexColorType::AllowAlpha, &rgba)) {
|
|
nsCSSUnit unit;
|
|
switch (tk->mIdent.Length()) {
|
|
case 3:
|
|
unit = eCSSUnit_ShortHexColor;
|
|
break;
|
|
case 4:
|
|
unit = eCSSUnit_ShortHexColorAlpha;
|
|
break;
|
|
case 6:
|
|
unit = eCSSUnit_HexColor;
|
|
break;
|
|
default:
|
|
MOZ_FALLTHROUGH_ASSERT("unexpected hex color length");
|
|
case 8:
|
|
unit = eCSSUnit_HexColorAlpha;
|
|
break;
|
|
}
|
|
aValue.SetIntegerColorValue(rgba, unit);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
break;
|
|
|
|
case eCSSToken_Ident:
|
|
if (NS_ColorNameToRGB(tk->mIdent, &rgba)) {
|
|
aValue.SetStringValue(tk->mIdent, eCSSUnit_Ident);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
else {
|
|
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(tk->mIdent);
|
|
if (eCSSKeyword_UNKNOWN < keyword) { // known keyword
|
|
int32_t value;
|
|
if (nsCSSProps::FindKeyword(keyword, nsCSSProps::kColorKTable, value)) {
|
|
aValue.SetIntValue(value, eCSSUnit_EnumColor);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case eCSSToken_Function:
|
|
if (mToken.mIdent.LowerCaseEqualsLiteral("rgb")) {
|
|
// rgb ( component , component , component )
|
|
if (GetToken(true)) {
|
|
UngetToken();
|
|
}
|
|
if (mToken.mType == eCSSToken_Number) {
|
|
uint8_t r, g, b;
|
|
if (ParseNumberColorComponent(r, ',') &&
|
|
ParseNumberColorComponent(g, ',') &&
|
|
ParseNumberColorComponent(b, ')')) {
|
|
aValue.SetIntegerColorValue(NS_RGB(r, g, b), eCSSUnit_RGBColor);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
} else {
|
|
float r, g, b;
|
|
if (ParsePercentageColorComponent(r, ',') &&
|
|
ParsePercentageColorComponent(g, ',') &&
|
|
ParsePercentageColorComponent(b, ')')) {
|
|
aValue.SetFloatColorValue(r, g, b, 1.0f,
|
|
eCSSUnit_PercentageRGBColor);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
}
|
|
SkipUntil(')');
|
|
return CSSParseResult::Error;
|
|
}
|
|
else if (mToken.mIdent.LowerCaseEqualsLiteral("rgba")) {
|
|
// rgba ( component , component , component , opacity )
|
|
if (GetToken(true)) {
|
|
UngetToken();
|
|
}
|
|
if (mToken.mType == eCSSToken_Number) {
|
|
uint8_t r, g, b, a;
|
|
if (ParseNumberColorComponent(r, ',') &&
|
|
ParseNumberColorComponent(g, ',') &&
|
|
ParseNumberColorComponent(b, ',') &&
|
|
ParseColorOpacity(a)) {
|
|
aValue.SetIntegerColorValue(NS_RGBA(r, g, b, a),
|
|
eCSSUnit_RGBAColor);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
} else {
|
|
float r, g, b, a;
|
|
if (ParsePercentageColorComponent(r, ',') &&
|
|
ParsePercentageColorComponent(g, ',') &&
|
|
ParsePercentageColorComponent(b, ',') &&
|
|
ParseColorOpacity(a)) {
|
|
aValue.SetFloatColorValue(r, g, b, a, eCSSUnit_PercentageRGBAColor);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
}
|
|
SkipUntil(')');
|
|
return CSSParseResult::Error;
|
|
}
|
|
else if (mToken.mIdent.LowerCaseEqualsLiteral("hsl")) {
|
|
// hsl ( hue , saturation , lightness )
|
|
// "hue" is a number, "saturation" and "lightness" are percentages.
|
|
float h, s, l;
|
|
if (ParseHSLColor(h, s, l, ')')) {
|
|
aValue.SetFloatColorValue(h, s, l, 1.0f, eCSSUnit_HSLColor);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
SkipUntil(')');
|
|
return CSSParseResult::Error;
|
|
}
|
|
else if (mToken.mIdent.LowerCaseEqualsLiteral("hsla")) {
|
|
// hsla ( hue , saturation , lightness , opacity )
|
|
// "hue" is a number, "saturation" and "lightness" are percentages,
|
|
// "opacity" is a number.
|
|
float h, s, l, a;
|
|
if (ParseHSLColor(h, s, l, ',') &&
|
|
ParseColorOpacity(a)) {
|
|
aValue.SetFloatColorValue(h, s, l, a, eCSSUnit_HSLAColor);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
SkipUntil(')');
|
|
return CSSParseResult::Error;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// try 'xxyyzz' without '#' prefix for compatibility with IE and Nav4x (bug 23236 and 45804)
|
|
if (mHashlessColorQuirk) {
|
|
// - If the string starts with 'a-f', the nsCSSScanner builds the
|
|
// token as a eCSSToken_Ident and we can parse the string as a
|
|
// 'xxyyzz' RGB color.
|
|
// - If it only contains '0-9' digits, the token is a
|
|
// eCSSToken_Number and it must be converted back to a 6
|
|
// characters string to be parsed as a RGB color.
|
|
// - If it starts with '0-9' and contains any 'a-f', the token is a
|
|
// eCSSToken_Dimension, the mNumber part must be converted back to
|
|
// a string and the mIdent part must be appended to that string so
|
|
// that the resulting string has 6 characters.
|
|
// Note: This is a hack for Nav compatibility. Do not attempt to
|
|
// simplify it by hacking into the ncCSSScanner. This would be very
|
|
// bad.
|
|
nsAutoString str;
|
|
char buffer[20];
|
|
switch (tk->mType) {
|
|
case eCSSToken_Ident:
|
|
str.Assign(tk->mIdent);
|
|
break;
|
|
|
|
case eCSSToken_Number:
|
|
if (tk->mIntegerValid) {
|
|
SprintfLiteral(buffer, "%06d", tk->mInteger);
|
|
str.AssignWithConversion(buffer);
|
|
}
|
|
break;
|
|
|
|
case eCSSToken_Dimension:
|
|
if (tk->mIdent.Length() <= 6) {
|
|
SprintfLiteral(buffer, "%06.0f", tk->mNumber);
|
|
nsAutoString temp;
|
|
temp.AssignWithConversion(buffer);
|
|
temp.Right(str, 6 - tk->mIdent.Length());
|
|
str.Append(tk->mIdent);
|
|
}
|
|
break;
|
|
default:
|
|
// There is a whole bunch of cases that are
|
|
// not handled by this switch. Ignore them.
|
|
break;
|
|
}
|
|
// The hashless color quirk does not support 4 & 8 digit colors with alpha.
|
|
if (NS_HexToRGBA(str, nsHexColorType::NoAlpha, &rgba)) {
|
|
aValue.SetIntegerColorValue(rgba, eCSSUnit_HexColor);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
}
|
|
|
|
// It's not a color
|
|
REPORT_UNEXPECTED_TOKEN(PEColorNotColor);
|
|
UngetToken();
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseNumberColorComponent(uint8_t& aComponent, char aStop)
|
|
{
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEColorComponentEOF);
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType != eCSSToken_Number || !mToken.mIntegerValid) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedInt);
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
float value = mToken.mNumber;
|
|
if (value < 0.0f) value = 0.0f;
|
|
if (value > 255.0f) value = 255.0f;
|
|
|
|
if (ExpectSymbol(aStop, true)) {
|
|
aComponent = NSToIntRound(value);
|
|
return true;
|
|
}
|
|
REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, aStop);
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParsePercentageColorComponent(float& aComponent, char aStop)
|
|
{
|
|
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 (value < 0.0f) value = 0.0f;
|
|
if (value > 1.0f) value = 1.0f;
|
|
|
|
if (ExpectSymbol(aStop, true)) {
|
|
aComponent = value;
|
|
return true;
|
|
}
|
|
REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, aStop);
|
|
return false;
|
|
}
|
|
|
|
|
|
bool
|
|
CSSParserImpl::ParseHSLColor(float& aHue, float& aSaturation, float& aLightness,
|
|
char aStop)
|
|
{
|
|
float h, s, l;
|
|
|
|
// Get the hue
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEColorHueEOF);
|
|
return false;
|
|
}
|
|
if (mToken.mType != eCSSToken_Number) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedNumber);
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
h = mToken.mNumber;
|
|
h /= 360.0f;
|
|
// hue values are wraparound
|
|
h = h - floor(h);
|
|
|
|
if (!ExpectSymbol(',', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedComma);
|
|
return false;
|
|
}
|
|
|
|
// Get the saturation
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEColorSaturationEOF);
|
|
return false;
|
|
}
|
|
if (mToken.mType != eCSSToken_Percentage) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedPercent);
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
s = mToken.mNumber;
|
|
if (s < 0.0f) s = 0.0f;
|
|
if (s > 1.0f) s = 1.0f;
|
|
|
|
if (!ExpectSymbol(',', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedComma);
|
|
return false;
|
|
}
|
|
|
|
// Get the lightness
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEColorLightnessEOF);
|
|
return false;
|
|
}
|
|
if (mToken.mType != eCSSToken_Percentage) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedPercent);
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
l = mToken.mNumber;
|
|
if (l < 0.0f) l = 0.0f;
|
|
if (l > 1.0f) l = 1.0f;
|
|
|
|
if (ExpectSymbol(aStop, true)) {
|
|
aHue = h;
|
|
aSaturation = s;
|
|
aLightness = l;
|
|
return true;
|
|
}
|
|
|
|
REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, aStop);
|
|
return false;
|
|
}
|
|
|
|
|
|
bool
|
|
CSSParserImpl::ParseColorOpacity(uint8_t& aOpacity)
|
|
{
|
|
float floatOpacity;
|
|
if (!ParseColorOpacity(floatOpacity)) {
|
|
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*mToken.mNumber - value) <= 0.51f,
|
|
"FloatToColorComponent did something weird");
|
|
|
|
aOpacity = value;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseColorOpacity(float& aOpacity)
|
|
{
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEColorOpacityEOF);
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType != eCSSToken_Number) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedNumber);
|
|
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;
|
|
}
|
|
|
|
#ifdef MOZ_XUL
|
|
bool
|
|
CSSParserImpl::ParseTreePseudoElement(nsAtomList **aPseudoElementArgs)
|
|
{
|
|
// The argument to a tree pseudo-element is a sequence of identifiers
|
|
// that are either space- or comma-separated. (Was the intent to
|
|
// allow only comma-separated? That's not what was done.)
|
|
nsCSSSelector fakeSelector; // so we can reuse AddPseudoClass
|
|
|
|
while (!ExpectSymbol(')', true)) {
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
if (eCSSToken_Ident == mToken.mType) {
|
|
fakeSelector.AddClass(mToken.mIdent);
|
|
}
|
|
else if (!mToken.IsSymbol(',')) {
|
|
UngetToken();
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
}
|
|
*aPseudoElementArgs = fakeSelector.mClassList;
|
|
fakeSelector.mClassList = nullptr;
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
nsCSSKeyword
|
|
CSSParserImpl::LookupKeywordPrefixAware(nsAString& aKeywordStr,
|
|
const KTableEntry aKeywordTable[])
|
|
{
|
|
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aKeywordStr);
|
|
|
|
if (aKeywordTable == nsCSSProps::kDisplayKTable) {
|
|
// NOTE: This code will be considerably simpler once we can do away with
|
|
// all Unprefixing Service code, in bug 1259348. But for the time being, we
|
|
// have to support two different strategies for handling -webkit-box here:
|
|
// (1) "Native support" (sWebkitPrefixedAliasesEnabled): we assume that
|
|
// -webkit-box will parse correctly (via an entry in kDisplayKTable),
|
|
// and we simply make a note that we've parsed it (so that we can we
|
|
// can give later "-moz-box" styling special handling as noted below).
|
|
// (2) "Unprefixing Service support" (ShouldUseUnprefixingService): we
|
|
// convert "-webkit-box" directly to modern "flex" (& do the same for
|
|
// any later "-moz-box" styling).
|
|
//
|
|
// Note that sWebkitPrefixedAliasesEnabled and
|
|
// ShouldUseUnprefixingService() are mutually exlusive, because the latter
|
|
// explicitly defers to the former.
|
|
if ((keyword == eCSSKeyword__webkit_box ||
|
|
keyword == eCSSKeyword__webkit_inline_box)) {
|
|
const bool usingUnprefixingService = ShouldUseUnprefixingService();
|
|
if (sWebkitPrefixedAliasesEnabled || usingUnprefixingService) {
|
|
// Make a note that we're accepting some "-webkit-{inline-}box" styling,
|
|
// so we can give special treatment to subsequent "-moz-{inline}-box".
|
|
// (See special treatment below.)
|
|
if (mWebkitBoxUnprefixState == eHaveNotUnprefixed) {
|
|
mWebkitBoxUnprefixState = eHaveUnprefixed;
|
|
}
|
|
if (usingUnprefixingService) {
|
|
// When we're using the unprefixing service, we treat
|
|
// "display:-webkit-box" as if it were "display:flex"
|
|
// (and "-webkit-inline-box" as "inline-flex").
|
|
return (keyword == eCSSKeyword__webkit_box) ?
|
|
eCSSKeyword_flex : eCSSKeyword_inline_flex;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we've seen "display: -webkit-box" (or "-webkit-inline-box") in an
|
|
// earlier declaration and we honored it, then we have to watch out for
|
|
// later "display: -moz-box" (and "-moz-inline-box") declarations; they're
|
|
// likely just a halfhearted attempt at compatibility, and they actually
|
|
// end up stomping on our emulation of the earlier -webkit-box
|
|
// display-value, via the CSS cascade. To prevent this problem, we treat
|
|
// "display: -moz-box" & "-moz-inline-box" as if they were simply a
|
|
// repetition of the webkit equivalent that we already parsed.
|
|
if (mWebkitBoxUnprefixState == eHaveUnprefixed &&
|
|
(keyword == eCSSKeyword__moz_box ||
|
|
keyword == eCSSKeyword__moz_inline_box)) {
|
|
MOZ_ASSERT(sWebkitPrefixedAliasesEnabled || ShouldUseUnprefixingService(),
|
|
"mDidUnprefixWebkitBoxInEarlierDecl should only be set if "
|
|
"we're supporting webkit-prefixed aliases, or if we're using "
|
|
"the css unprefixing service on this site");
|
|
if (sWebkitPrefixedAliasesEnabled) {
|
|
return (keyword == eCSSKeyword__moz_box) ?
|
|
eCSSKeyword__webkit_box : eCSSKeyword__webkit_inline_box;
|
|
}
|
|
// (If we get here, we're using the Unprefixing Service, which means
|
|
// we're unprefixing all the way to modern flexbox display values.)
|
|
return (keyword == eCSSKeyword__moz_box) ?
|
|
eCSSKeyword_flex : eCSSKeyword_inline_flex;
|
|
}
|
|
}
|
|
|
|
return keyword;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ShouldUseUnprefixingService() const
|
|
{
|
|
if (!sUnprefixingServiceEnabled) {
|
|
// Unprefixing is globally disabled.
|
|
return false;
|
|
}
|
|
if (sWebkitPrefixedAliasesEnabled) {
|
|
// Native webkit-prefix support is enabled, which trumps the unprefixing
|
|
// service for handling prefixed CSS. Don't try to use both at once.
|
|
return false;
|
|
}
|
|
|
|
#ifdef NIGHTLY_BUILD
|
|
if (sUnprefixingServiceGloballyWhitelisted) {
|
|
// Unprefixing is globally whitelisted,
|
|
// so no need to check mSheetPrincipal.
|
|
return true;
|
|
}
|
|
#endif
|
|
// Unprefixing enabled; see if our principal is whitelisted for unprefixing.
|
|
return mSheetPrincipal && mSheetPrincipal->IsOnCSSUnprefixingWhitelist();
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParsePropertyWithUnprefixingService(
|
|
const nsAString& aPropertyName,
|
|
css::Declaration* aDeclaration,
|
|
uint32_t aFlags,
|
|
bool aMustCallValueAppended,
|
|
bool* aChanged,
|
|
nsCSSContextType aContext)
|
|
{
|
|
MOZ_ASSERT(ShouldUseUnprefixingService(),
|
|
"Caller should've checked ShouldUseUnprefixingService()");
|
|
|
|
nsCOMPtr<nsICSSUnprefixingService> unprefixingSvc =
|
|
do_GetService(NS_CSSUNPREFIXINGSERVICE_CONTRACTID);
|
|
NS_ENSURE_TRUE(unprefixingSvc, false);
|
|
|
|
// Save the state so we can jump back to this spot if our unprefixing fails
|
|
// (so we can behave as if we didn't even try to unprefix).
|
|
nsAutoCSSParserInputStateRestorer parserStateBeforeTryingToUnprefix(this);
|
|
|
|
// Caller has already parsed the first half of the declaration --
|
|
// aPropertyName and the ":". Now, we record the rest of the CSS declaration
|
|
// (the part after ':') into rightHalfOfDecl. (This is the property value,
|
|
// plus anything else up to the end of the declaration -- maybe "!important",
|
|
// maybe trailing junk characters, maybe a semicolon, maybe a trailing "}".)
|
|
bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0;
|
|
nsAutoString rightHalfOfDecl;
|
|
mScanner->StartRecording();
|
|
SkipDeclaration(checkForBraces);
|
|
mScanner->StopRecording(rightHalfOfDecl);
|
|
|
|
// Try to unprefix:
|
|
bool success;
|
|
nsAutoString unprefixedDecl;
|
|
nsresult rv =
|
|
unprefixingSvc->GenerateUnprefixedDeclaration(aPropertyName,
|
|
rightHalfOfDecl,
|
|
unprefixedDecl, &success);
|
|
if (NS_FAILED(rv) || !success) {
|
|
return false;
|
|
}
|
|
|
|
// Attempt to parse the unprefixed declaration:
|
|
nsAutoScannerChanger scannerChanger(this, unprefixedDecl);
|
|
success = ParseDeclaration(aDeclaration,
|
|
aFlags | eParseDeclaration_FromUnprefixingSvc,
|
|
aMustCallValueAppended, aChanged, aContext);
|
|
if (success) {
|
|
// We succeeded, so we'll leave the parser pointing at the end of
|
|
// the declaration; don't restore it to the pre-recording position.
|
|
parserStateBeforeTryingToUnprefix.DoNotRestore();
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseWebkitPrefixedGradientWithService(
|
|
nsAString& aPrefixedFuncName,
|
|
nsCSSValue& aValue)
|
|
{
|
|
MOZ_ASSERT(ShouldUseUnprefixingService(),
|
|
"Should only call if we're allowed to use unprefixing service");
|
|
|
|
// Record the body of the "-webkit-*gradient" function into a string.
|
|
// Note: we're already just after the opening "(".
|
|
nsAutoString prefixedFuncBody;
|
|
mScanner->StartRecording();
|
|
bool gotCloseParen = SkipUntil(')');
|
|
mScanner->StopRecording(prefixedFuncBody);
|
|
if (gotCloseParen) {
|
|
// Strip off trailing close-paren, so that the value we pass to the
|
|
// unprefixing service is *just* the function-body (no parens).
|
|
prefixedFuncBody.Truncate(prefixedFuncBody.Length() - 1);
|
|
}
|
|
|
|
// NOTE: Even if we fail, we'll be leaving the parser's cursor just after
|
|
// the close of the "-webkit-*gradient(...)" expression. This is the same
|
|
// behavior that the other Parse*Gradient functions have in their failure
|
|
// cases -- they call "SkipUntil(')') before returning false. So this is
|
|
// probably what we want.
|
|
nsCOMPtr<nsICSSUnprefixingService> unprefixingSvc =
|
|
do_GetService(NS_CSSUNPREFIXINGSERVICE_CONTRACTID);
|
|
NS_ENSURE_TRUE(unprefixingSvc, false);
|
|
|
|
bool success;
|
|
nsAutoString unprefixedFuncName;
|
|
nsAutoString unprefixedFuncBody;
|
|
nsresult rv =
|
|
unprefixingSvc->GenerateUnprefixedGradientValue(aPrefixedFuncName,
|
|
prefixedFuncBody,
|
|
unprefixedFuncName,
|
|
unprefixedFuncBody,
|
|
&success);
|
|
|
|
if (NS_FAILED(rv) || !success) {
|
|
return false;
|
|
}
|
|
|
|
// JS service thinks it successfully converted the gradient! Now let's try
|
|
// to parse the resulting string.
|
|
|
|
// First, add a close-paren if we originally recorded one (so that what we're
|
|
// about to put into the CSS parser is a faithful representation of what it
|
|
// would've seen if it were just parsing the original input stream):
|
|
if (gotCloseParen) {
|
|
unprefixedFuncBody.Append(char16_t(')'));
|
|
}
|
|
|
|
nsAutoScannerChanger scannerChanger(this, unprefixedFuncBody);
|
|
if (unprefixedFuncName.EqualsLiteral("linear-gradient")) {
|
|
return ParseLinearGradient(aValue, 0);
|
|
}
|
|
if (unprefixedFuncName.EqualsLiteral("radial-gradient")) {
|
|
return ParseRadialGradient(aValue, 0);
|
|
}
|
|
|
|
NS_ERROR("CSSUnprefixingService returned an unrecognized type of "
|
|
"gradient function");
|
|
|
|
return false;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
bool
|
|
CSSParserImpl::ParseDeclaration(css::Declaration* aDeclaration,
|
|
uint32_t aFlags,
|
|
bool aMustCallValueAppended,
|
|
bool* aChanged,
|
|
nsCSSContextType aContext)
|
|
{
|
|
NS_PRECONDITION(aContext == eCSSContext_General ||
|
|
aContext == eCSSContext_Page,
|
|
"Must be page or general context");
|
|
|
|
bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0;
|
|
|
|
mTempData.AssertInitialState();
|
|
|
|
// Get property name
|
|
nsCSSToken* tk = &mToken;
|
|
nsAutoString propertyName;
|
|
for (;;) {
|
|
if (!GetToken(true)) {
|
|
if (checkForBraces) {
|
|
REPORT_UNEXPECTED_EOF(PEDeclEndEOF);
|
|
}
|
|
return false;
|
|
}
|
|
if (eCSSToken_Ident == tk->mType) {
|
|
propertyName = tk->mIdent;
|
|
// grab the ident before the ExpectSymbol trashes the token
|
|
if (!ExpectSymbol(':', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
|
|
REPORT_UNEXPECTED(PEDeclDropped);
|
|
OUTPUT_ERROR();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
if (tk->IsSymbol(';')) {
|
|
// dangling semicolons are skipped
|
|
continue;
|
|
}
|
|
|
|
if (!tk->IsSymbol('}')) {
|
|
REPORT_UNEXPECTED_TOKEN(PEParseDeclarationDeclExpected);
|
|
REPORT_UNEXPECTED(PEDeclSkipped);
|
|
OUTPUT_ERROR();
|
|
|
|
if (eCSSToken_AtKeyword == tk->mType) {
|
|
SkipAtRule(checkForBraces);
|
|
return true; // Not a declaration, but don't skip until ';'
|
|
}
|
|
}
|
|
// Not a declaration...
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
// Don't report property parse errors if we're inside a failing @supports
|
|
// rule.
|
|
nsAutoSuppressErrors suppressErrors(this, mInFailingSupportsRule);
|
|
|
|
// Information about a parsed non-custom property.
|
|
nsCSSPropertyID propID;
|
|
|
|
// Information about a parsed custom property.
|
|
CSSVariableDeclarations::Type variableType;
|
|
nsString variableValue;
|
|
|
|
// Check if the property name is a custom property.
|
|
bool customProperty = nsLayoutUtils::CSSVariablesEnabled() &&
|
|
nsCSSProps::IsCustomPropertyName(propertyName) &&
|
|
aContext == eCSSContext_General;
|
|
|
|
if (customProperty) {
|
|
if (!ParseVariableDeclaration(&variableType, variableValue)) {
|
|
REPORT_UNEXPECTED_P(PEValueParsingError, propertyName);
|
|
REPORT_UNEXPECTED(PEDeclDropped);
|
|
OUTPUT_ERROR();
|
|
return false;
|
|
}
|
|
} else {
|
|
// Map property name to its ID.
|
|
propID = LookupEnabledProperty(propertyName);
|
|
if (eCSSProperty_UNKNOWN == propID ||
|
|
eCSSPropertyExtra_variable == propID ||
|
|
(aContext == eCSSContext_Page &&
|
|
!nsCSSProps::PropHasFlags(propID,
|
|
CSS_PROPERTY_APPLIES_TO_PAGE_RULE))) { // unknown property
|
|
if (NonMozillaVendorIdentifier(propertyName)) {
|
|
if (!mInSupportsCondition &&
|
|
aContext == eCSSContext_General &&
|
|
!(aFlags & eParseDeclaration_FromUnprefixingSvc) && // no recursion
|
|
ShouldUseUnprefixingService()) {
|
|
if (ParsePropertyWithUnprefixingService(propertyName,
|
|
aDeclaration, aFlags,
|
|
aMustCallValueAppended,
|
|
aChanged, aContext)) {
|
|
return true;
|
|
}
|
|
}
|
|
} else {
|
|
REPORT_UNEXPECTED_P(PEUnknownProperty, propertyName);
|
|
REPORT_UNEXPECTED(PEDeclDropped);
|
|
OUTPUT_ERROR();
|
|
}
|
|
return false;
|
|
}
|
|
// Then parse the property.
|
|
if (!ParseProperty(propID)) {
|
|
// XXX Much better to put stuff in the value parsers instead...
|
|
REPORT_UNEXPECTED_P(PEValueParsingError, propertyName);
|
|
REPORT_UNEXPECTED(PEDeclDropped);
|
|
OUTPUT_ERROR();
|
|
mTempData.ClearProperty(propID);
|
|
mTempData.AssertInitialState();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CLEAR_ERROR();
|
|
|
|
// Look for "!important".
|
|
PriorityParsingStatus status;
|
|
if ((aFlags & eParseDeclaration_AllowImportant) != 0) {
|
|
status = ParsePriority();
|
|
} else {
|
|
status = ePriority_None;
|
|
}
|
|
|
|
// Look for a semicolon or close brace.
|
|
if (status != ePriority_Error) {
|
|
if (!GetToken(true)) {
|
|
// EOF is always ok
|
|
} else if (mToken.IsSymbol(';')) {
|
|
// semicolon is always ok
|
|
} else if (mToken.IsSymbol('}')) {
|
|
// brace is ok if checkForBraces, but don't eat it
|
|
UngetToken();
|
|
if (!checkForBraces) {
|
|
status = ePriority_Error;
|
|
}
|
|
} else {
|
|
UngetToken();
|
|
status = ePriority_Error;
|
|
}
|
|
}
|
|
|
|
if (status == ePriority_Error) {
|
|
if (checkForBraces) {
|
|
REPORT_UNEXPECTED_TOKEN(PEBadDeclOrRuleEnd2);
|
|
} else {
|
|
REPORT_UNEXPECTED_TOKEN(PEBadDeclEnd);
|
|
}
|
|
REPORT_UNEXPECTED(PEDeclDropped);
|
|
OUTPUT_ERROR();
|
|
if (!customProperty) {
|
|
mTempData.ClearProperty(propID);
|
|
}
|
|
mTempData.AssertInitialState();
|
|
return false;
|
|
}
|
|
|
|
if (customProperty) {
|
|
MOZ_ASSERT(Substring(propertyName, 0,
|
|
CSS_CUSTOM_NAME_PREFIX_LENGTH).EqualsLiteral("--"));
|
|
// remove '--'
|
|
nsDependentString varName(propertyName, CSS_CUSTOM_NAME_PREFIX_LENGTH);
|
|
aDeclaration->AddVariableDeclaration(varName, variableType, variableValue,
|
|
status == ePriority_Important, false);
|
|
} else {
|
|
*aChanged |= mData.TransferFromBlock(mTempData, propID, EnabledState(),
|
|
status == ePriority_Important,
|
|
false, aMustCallValueAppended,
|
|
aDeclaration, GetDocument());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static const nsCSSPropertyID kBorderTopIDs[] = {
|
|
eCSSProperty_border_top_width,
|
|
eCSSProperty_border_top_style,
|
|
eCSSProperty_border_top_color
|
|
};
|
|
static const nsCSSPropertyID kBorderRightIDs[] = {
|
|
eCSSProperty_border_right_width,
|
|
eCSSProperty_border_right_style,
|
|
eCSSProperty_border_right_color
|
|
};
|
|
static const nsCSSPropertyID kBorderBottomIDs[] = {
|
|
eCSSProperty_border_bottom_width,
|
|
eCSSProperty_border_bottom_style,
|
|
eCSSProperty_border_bottom_color
|
|
};
|
|
static const nsCSSPropertyID kBorderLeftIDs[] = {
|
|
eCSSProperty_border_left_width,
|
|
eCSSProperty_border_left_style,
|
|
eCSSProperty_border_left_color
|
|
};
|
|
static const nsCSSPropertyID kBorderInlineStartIDs[] = {
|
|
eCSSProperty_border_inline_start_width,
|
|
eCSSProperty_border_inline_start_style,
|
|
eCSSProperty_border_inline_start_color
|
|
};
|
|
static const nsCSSPropertyID kBorderInlineEndIDs[] = {
|
|
eCSSProperty_border_inline_end_width,
|
|
eCSSProperty_border_inline_end_style,
|
|
eCSSProperty_border_inline_end_color
|
|
};
|
|
static const nsCSSPropertyID kBorderBlockStartIDs[] = {
|
|
eCSSProperty_border_block_start_width,
|
|
eCSSProperty_border_block_start_style,
|
|
eCSSProperty_border_block_start_color
|
|
};
|
|
static const nsCSSPropertyID kBorderBlockEndIDs[] = {
|
|
eCSSProperty_border_block_end_width,
|
|
eCSSProperty_border_block_end_style,
|
|
eCSSProperty_border_block_end_color
|
|
};
|
|
static const nsCSSPropertyID kColumnRuleIDs[] = {
|
|
eCSSProperty_column_rule_width,
|
|
eCSSProperty_column_rule_style,
|
|
eCSSProperty_column_rule_color
|
|
};
|
|
|
|
bool
|
|
CSSParserImpl::ParseEnum(nsCSSValue& aValue,
|
|
const KTableEntry aKeywordTable[])
|
|
{
|
|
nsSubstring* ident = NextIdent();
|
|
if (nullptr == ident) {
|
|
return false;
|
|
}
|
|
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(*ident);
|
|
if (eCSSKeyword_UNKNOWN < keyword) {
|
|
int32_t value;
|
|
if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) {
|
|
aValue.SetIntValue(value, eCSSUnit_Enumerated);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Put the unknown identifier back and return
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
|
|
struct UnitInfo {
|
|
char name[6]; // needs to be long enough for the longest unit, with
|
|
// terminating null.
|
|
uint32_t length;
|
|
nsCSSUnit unit;
|
|
int32_t type;
|
|
};
|
|
|
|
#define STR_WITH_LEN(_str) \
|
|
_str, sizeof(_str) - 1
|
|
|
|
const UnitInfo UnitData[] = {
|
|
{ STR_WITH_LEN("px"), eCSSUnit_Pixel, VARIANT_LENGTH },
|
|
{ STR_WITH_LEN("em"), eCSSUnit_EM, VARIANT_LENGTH },
|
|
{ STR_WITH_LEN("ex"), eCSSUnit_XHeight, VARIANT_LENGTH },
|
|
{ STR_WITH_LEN("pt"), eCSSUnit_Point, VARIANT_LENGTH },
|
|
{ STR_WITH_LEN("in"), eCSSUnit_Inch, VARIANT_LENGTH },
|
|
{ STR_WITH_LEN("cm"), eCSSUnit_Centimeter, VARIANT_LENGTH },
|
|
{ STR_WITH_LEN("ch"), eCSSUnit_Char, VARIANT_LENGTH },
|
|
{ STR_WITH_LEN("rem"), eCSSUnit_RootEM, VARIANT_LENGTH },
|
|
{ STR_WITH_LEN("mm"), eCSSUnit_Millimeter, VARIANT_LENGTH },
|
|
{ STR_WITH_LEN("mozmm"), eCSSUnit_PhysicalMillimeter, VARIANT_LENGTH },
|
|
{ STR_WITH_LEN("vw"), eCSSUnit_ViewportWidth, VARIANT_LENGTH },
|
|
{ STR_WITH_LEN("vh"), eCSSUnit_ViewportHeight, VARIANT_LENGTH },
|
|
{ STR_WITH_LEN("vmin"), eCSSUnit_ViewportMin, VARIANT_LENGTH },
|
|
{ STR_WITH_LEN("vmax"), eCSSUnit_ViewportMax, VARIANT_LENGTH },
|
|
{ STR_WITH_LEN("pc"), eCSSUnit_Pica, VARIANT_LENGTH },
|
|
{ STR_WITH_LEN("q"), eCSSUnit_Quarter, VARIANT_LENGTH },
|
|
{ STR_WITH_LEN("deg"), eCSSUnit_Degree, VARIANT_ANGLE },
|
|
{ STR_WITH_LEN("grad"), eCSSUnit_Grad, VARIANT_ANGLE },
|
|
{ STR_WITH_LEN("rad"), eCSSUnit_Radian, VARIANT_ANGLE },
|
|
{ STR_WITH_LEN("turn"), eCSSUnit_Turn, VARIANT_ANGLE },
|
|
{ STR_WITH_LEN("hz"), eCSSUnit_Hertz, VARIANT_FREQUENCY },
|
|
{ STR_WITH_LEN("khz"), eCSSUnit_Kilohertz, VARIANT_FREQUENCY },
|
|
{ STR_WITH_LEN("s"), eCSSUnit_Seconds, VARIANT_TIME },
|
|
{ STR_WITH_LEN("ms"), eCSSUnit_Milliseconds, VARIANT_TIME }
|
|
};
|
|
|
|
#undef STR_WITH_LEN
|
|
|
|
bool
|
|
CSSParserImpl::TranslateDimension(nsCSSValue& aValue,
|
|
uint32_t aVariantMask,
|
|
float aNumber,
|
|
const nsString& aUnit)
|
|
{
|
|
nsCSSUnit units;
|
|
int32_t type = 0;
|
|
if (!aUnit.IsEmpty()) {
|
|
uint32_t i;
|
|
for (i = 0; i < ArrayLength(UnitData); ++i) {
|
|
if (aUnit.LowerCaseEqualsASCII(UnitData[i].name,
|
|
UnitData[i].length)) {
|
|
units = UnitData[i].unit;
|
|
type = UnitData[i].type;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == ArrayLength(UnitData)) {
|
|
// Unknown unit
|
|
return false;
|
|
}
|
|
|
|
if (!mViewportUnitsEnabled &&
|
|
(eCSSUnit_ViewportWidth == units ||
|
|
eCSSUnit_ViewportHeight == units ||
|
|
eCSSUnit_ViewportMin == units ||
|
|
eCSSUnit_ViewportMax == units)) {
|
|
// Viewport units aren't allowed right now, probably because we're
|
|
// inside an @page declaration. Fail.
|
|
return false;
|
|
}
|
|
|
|
if ((VARIANT_ABSOLUTE_DIMENSION & aVariantMask) != 0 &&
|
|
!nsCSSValue::IsPixelLengthUnit(units)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
// Must be a zero number...
|
|
NS_ASSERTION(0 == aNumber, "numbers without units must be 0");
|
|
if ((VARIANT_LENGTH & aVariantMask) != 0) {
|
|
units = eCSSUnit_Pixel;
|
|
type = VARIANT_LENGTH;
|
|
}
|
|
else if ((VARIANT_ANGLE & aVariantMask) != 0) {
|
|
NS_ASSERTION(aVariantMask & VARIANT_ZERO_ANGLE,
|
|
"must have allowed zero angle");
|
|
units = eCSSUnit_Degree;
|
|
type = VARIANT_ANGLE;
|
|
}
|
|
else {
|
|
NS_ERROR("Variant mask does not include dimension; why were we called?");
|
|
return false;
|
|
}
|
|
}
|
|
if ((type & aVariantMask) != 0) {
|
|
aValue.SetFloatValue(aNumber, units);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Note that this does include VARIANT_CALC, which is numeric. This is
|
|
// because calc() parsing, as proposed, drops range restrictions inside
|
|
// the calc() expression and clamps the result of the calculation to the
|
|
// range.
|
|
#define VARIANT_ALL_NONNUMERIC \
|
|
VARIANT_KEYWORD | \
|
|
VARIANT_COLOR | \
|
|
VARIANT_URL | \
|
|
VARIANT_STRING | \
|
|
VARIANT_COUNTER | \
|
|
VARIANT_ATTR | \
|
|
VARIANT_IDENTIFIER | \
|
|
VARIANT_IDENTIFIER_NO_INHERIT | \
|
|
VARIANT_AUTO | \
|
|
VARIANT_INHERIT | \
|
|
VARIANT_NONE | \
|
|
VARIANT_NORMAL | \
|
|
VARIANT_SYSFONT | \
|
|
VARIANT_GRADIENT | \
|
|
VARIANT_TIMING_FUNCTION | \
|
|
VARIANT_ALL | \
|
|
VARIANT_CALC | \
|
|
VARIANT_OPENTYPE_SVG_KEYWORD
|
|
|
|
CSSParseResult
|
|
CSSParserImpl::ParseVariantWithRestrictions(nsCSSValue& aValue,
|
|
int32_t aVariantMask,
|
|
const KTableEntry aKeywordTable[],
|
|
uint32_t aRestrictions)
|
|
{
|
|
switch (aRestrictions) {
|
|
default:
|
|
MOZ_FALLTHROUGH_ASSERT("should not be reached");
|
|
case 0:
|
|
return ParseVariant(aValue, aVariantMask, aKeywordTable);
|
|
case CSS_PROPERTY_VALUE_NONNEGATIVE:
|
|
return ParseNonNegativeVariant(aValue, aVariantMask, aKeywordTable);
|
|
case CSS_PROPERTY_VALUE_AT_LEAST_ONE:
|
|
return ParseOneOrLargerVariant(aValue, aVariantMask, aKeywordTable);
|
|
}
|
|
}
|
|
|
|
// Note that callers passing VARIANT_CALC in aVariantMask will get
|
|
// full-range parsing inside the calc() expression, and the code that
|
|
// computes the calc will be required to clamp the resulting value to an
|
|
// appropriate range.
|
|
CSSParseResult
|
|
CSSParserImpl::ParseNonNegativeVariant(nsCSSValue& aValue,
|
|
int32_t aVariantMask,
|
|
const KTableEntry aKeywordTable[])
|
|
{
|
|
// The variant mask must only contain non-numeric variants or the ones
|
|
// that we specifically handle.
|
|
MOZ_ASSERT((aVariantMask & ~(VARIANT_ALL_NONNUMERIC |
|
|
VARIANT_NUMBER |
|
|
VARIANT_LENGTH |
|
|
VARIANT_PERCENT |
|
|
VARIANT_INTEGER)) == 0,
|
|
"need to update code below to handle additional variants");
|
|
|
|
CSSParseResult result = ParseVariant(aValue, aVariantMask, aKeywordTable);
|
|
if (result == CSSParseResult::Ok) {
|
|
if (eCSSUnit_Number == aValue.GetUnit() ||
|
|
aValue.IsLengthUnit()){
|
|
if (aValue.GetFloatValue() < 0) {
|
|
UngetToken();
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
}
|
|
else if (aValue.GetUnit() == eCSSUnit_Percent) {
|
|
if (aValue.GetPercentValue() < 0) {
|
|
UngetToken();
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
} else if (aValue.GetUnit() == eCSSUnit_Integer) {
|
|
if (aValue.GetIntValue() < 0) {
|
|
UngetToken();
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Note that callers passing VARIANT_CALC in aVariantMask will get
|
|
// full-range parsing inside the calc() expression, and the code that
|
|
// computes the calc will be required to clamp the resulting value to an
|
|
// appropriate range.
|
|
CSSParseResult
|
|
CSSParserImpl::ParseOneOrLargerVariant(nsCSSValue& aValue,
|
|
int32_t aVariantMask,
|
|
const KTableEntry aKeywordTable[])
|
|
{
|
|
// The variant mask must only contain non-numeric variants or the ones
|
|
// that we specifically handle.
|
|
MOZ_ASSERT((aVariantMask & ~(VARIANT_ALL_NONNUMERIC |
|
|
VARIANT_NUMBER |
|
|
VARIANT_INTEGER)) == 0,
|
|
"need to update code below to handle additional variants");
|
|
|
|
CSSParseResult result = ParseVariant(aValue, aVariantMask, aKeywordTable);
|
|
if (result == CSSParseResult::Ok) {
|
|
if (aValue.GetUnit() == eCSSUnit_Integer) {
|
|
if (aValue.GetIntValue() < 1) {
|
|
UngetToken();
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
} else if (eCSSUnit_Number == aValue.GetUnit()) {
|
|
if (aValue.GetFloatValue() < 1.0f) {
|
|
UngetToken();
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static bool
|
|
IsCSSTokenCalcFunction(const nsCSSToken& aToken)
|
|
{
|
|
return aToken.mType == eCSSToken_Function &&
|
|
(aToken.mIdent.LowerCaseEqualsLiteral("calc") ||
|
|
aToken.mIdent.LowerCaseEqualsLiteral("-moz-calc"));
|
|
}
|
|
|
|
// Assigns to aValue iff it returns CSSParseResult::Ok.
|
|
CSSParseResult
|
|
CSSParserImpl::ParseVariant(nsCSSValue& aValue,
|
|
uint32_t aVariantMask,
|
|
const KTableEntry aKeywordTable[])
|
|
{
|
|
NS_ASSERTION(!(mHashlessColorQuirk && (aVariantMask & VARIANT_COLOR)) ||
|
|
!(aVariantMask & VARIANT_NUMBER),
|
|
"can't distinguish colors from numbers");
|
|
NS_ASSERTION(!(mHashlessColorQuirk && (aVariantMask & VARIANT_COLOR)) ||
|
|
!(mUnitlessLengthQuirk && (aVariantMask & VARIANT_LENGTH)),
|
|
"can't distinguish colors from lengths");
|
|
NS_ASSERTION(!(mUnitlessLengthQuirk && (aVariantMask & VARIANT_LENGTH)) ||
|
|
!(aVariantMask & VARIANT_NUMBER),
|
|
"can't distinguish lengths from numbers");
|
|
MOZ_ASSERT(!(aVariantMask & VARIANT_IDENTIFIER) ||
|
|
!(aVariantMask & VARIANT_IDENTIFIER_NO_INHERIT),
|
|
"must not set both VARIANT_IDENTIFIER and "
|
|
"VARIANT_IDENTIFIER_NO_INHERIT");
|
|
|
|
uint32_t lineBefore, colBefore;
|
|
if (!GetNextTokenLocation(true, &lineBefore, &colBefore) ||
|
|
!GetToken(true)) {
|
|
// Must be at EOF.
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
|
|
nsCSSToken* tk = &mToken;
|
|
if (((aVariantMask & (VARIANT_AHK | VARIANT_NORMAL | VARIANT_NONE | VARIANT_ALL)) != 0) &&
|
|
(eCSSToken_Ident == tk->mType)) {
|
|
nsCSSKeyword keyword = LookupKeywordPrefixAware(tk->mIdent,
|
|
aKeywordTable);
|
|
|
|
if (eCSSKeyword_UNKNOWN < keyword) { // known keyword
|
|
if ((aVariantMask & VARIANT_AUTO) != 0) {
|
|
if (eCSSKeyword_auto == keyword) {
|
|
aValue.SetAutoValue();
|
|
return CSSParseResult::Ok;
|
|
}
|
|
}
|
|
if ((aVariantMask & VARIANT_INHERIT) != 0) {
|
|
// XXX Should we check IsParsingCompoundProperty, or do all
|
|
// callers handle it? (Not all callers set it, though, since
|
|
// they want the quirks that are disabled by setting it.)
|
|
|
|
// IMPORTANT: If new keywords are added here,
|
|
// they probably need to be added in ParseCustomIdent as well.
|
|
if (eCSSKeyword_inherit == keyword) {
|
|
aValue.SetInheritValue();
|
|
return CSSParseResult::Ok;
|
|
}
|
|
else if (eCSSKeyword_initial == keyword) {
|
|
aValue.SetInitialValue();
|
|
return CSSParseResult::Ok;
|
|
}
|
|
else if (eCSSKeyword_unset == keyword &&
|
|
nsLayoutUtils::UnsetValueEnabled()) {
|
|
aValue.SetUnsetValue();
|
|
return CSSParseResult::Ok;
|
|
}
|
|
}
|
|
if ((aVariantMask & VARIANT_NONE) != 0) {
|
|
if (eCSSKeyword_none == keyword) {
|
|
aValue.SetNoneValue();
|
|
return CSSParseResult::Ok;
|
|
}
|
|
}
|
|
if ((aVariantMask & VARIANT_ALL) != 0) {
|
|
if (eCSSKeyword_all == keyword) {
|
|
aValue.SetAllValue();
|
|
return CSSParseResult::Ok;
|
|
}
|
|
}
|
|
if ((aVariantMask & VARIANT_NORMAL) != 0) {
|
|
if (eCSSKeyword_normal == keyword) {
|
|
aValue.SetNormalValue();
|
|
return CSSParseResult::Ok;
|
|
}
|
|
}
|
|
if ((aVariantMask & VARIANT_SYSFONT) != 0) {
|
|
if (eCSSKeyword__moz_use_system_font == keyword &&
|
|
!IsParsingCompoundProperty()) {
|
|
aValue.SetSystemFontValue();
|
|
return CSSParseResult::Ok;
|
|
}
|
|
}
|
|
if ((aVariantMask & VARIANT_OPENTYPE_SVG_KEYWORD) != 0) {
|
|
if (sOpentypeSVGEnabled) {
|
|
aVariantMask |= VARIANT_KEYWORD;
|
|
}
|
|
}
|
|
if ((aVariantMask & VARIANT_KEYWORD) != 0) {
|
|
int32_t value;
|
|
if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) {
|
|
aValue.SetIntValue(value, eCSSUnit_Enumerated);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Check VARIANT_NUMBER and VARIANT_INTEGER before VARIANT_LENGTH or
|
|
// VARIANT_ZERO_ANGLE.
|
|
if (((aVariantMask & VARIANT_NUMBER) != 0) &&
|
|
(eCSSToken_Number == tk->mType)) {
|
|
aValue.SetFloatValue(tk->mNumber, eCSSUnit_Number);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
if (((aVariantMask & VARIANT_INTEGER) != 0) &&
|
|
(eCSSToken_Number == tk->mType) && tk->mIntegerValid) {
|
|
aValue.SetIntValue(tk->mInteger, eCSSUnit_Integer);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
if (((aVariantMask & (VARIANT_LENGTH | VARIANT_ANGLE |
|
|
VARIANT_FREQUENCY | VARIANT_TIME)) != 0 &&
|
|
eCSSToken_Dimension == tk->mType) ||
|
|
((aVariantMask & (VARIANT_LENGTH | VARIANT_ZERO_ANGLE)) != 0 &&
|
|
eCSSToken_Number == tk->mType &&
|
|
tk->mNumber == 0.0f)) {
|
|
if ((aVariantMask & VARIANT_NONNEGATIVE_DIMENSION) != 0 &&
|
|
tk->mNumber < 0.0) {
|
|
UngetToken();
|
|
AssertNextTokenAt(lineBefore, colBefore);
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
if (TranslateDimension(aValue, aVariantMask, tk->mNumber, tk->mIdent)) {
|
|
return CSSParseResult::Ok;
|
|
}
|
|
// Put the token back; we didn't parse it, so we shouldn't consume it
|
|
UngetToken();
|
|
AssertNextTokenAt(lineBefore, colBefore);
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
if (((aVariantMask & VARIANT_PERCENT) != 0) &&
|
|
(eCSSToken_Percentage == tk->mType)) {
|
|
aValue.SetPercentValue(tk->mNumber);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
if (mUnitlessLengthQuirk) { // NONSTANDARD: Nav interprets unitless numbers as px
|
|
if (((aVariantMask & VARIANT_LENGTH) != 0) &&
|
|
(eCSSToken_Number == tk->mType)) {
|
|
aValue.SetFloatValue(tk->mNumber, eCSSUnit_Pixel);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
}
|
|
|
|
if (IsSVGMode() && !IsParsingCompoundProperty()) {
|
|
// STANDARD: SVG Spec states that lengths and coordinates can be unitless
|
|
// in which case they default to user-units (1 px = 1 user unit)
|
|
if (((aVariantMask & VARIANT_LENGTH) != 0) &&
|
|
(eCSSToken_Number == tk->mType)) {
|
|
aValue.SetFloatValue(tk->mNumber, eCSSUnit_Pixel);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
}
|
|
|
|
if (((aVariantMask & VARIANT_URL) != 0) &&
|
|
eCSSToken_URL == tk->mType) {
|
|
SetValueToURL(aValue, tk->mIdent);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
if ((aVariantMask & VARIANT_GRADIENT) != 0 &&
|
|
eCSSToken_Function == tk->mType) {
|
|
// a generated gradient
|
|
nsDependentString tmp(tk->mIdent, 0);
|
|
uint8_t gradientFlags = 0;
|
|
if (sMozGradientsEnabled &&
|
|
StringBeginsWith(tmp, NS_LITERAL_STRING("-moz-"))) {
|
|
tmp.Rebind(tmp, 5);
|
|
gradientFlags |= eGradient_MozLegacy;
|
|
} else if (sWebkitPrefixedAliasesEnabled &&
|
|
StringBeginsWith(tmp, NS_LITERAL_STRING("-webkit-"))) {
|
|
tmp.Rebind(tmp, 8);
|
|
gradientFlags |= eGradient_WebkitLegacy;
|
|
}
|
|
if (StringBeginsWith(tmp, NS_LITERAL_STRING("repeating-"))) {
|
|
tmp.Rebind(tmp, 10);
|
|
gradientFlags |= eGradient_Repeating;
|
|
}
|
|
|
|
if (tmp.LowerCaseEqualsLiteral("linear-gradient")) {
|
|
if (!ParseLinearGradient(aValue, gradientFlags)) {
|
|
return CSSParseResult::Error;
|
|
}
|
|
return CSSParseResult::Ok;
|
|
}
|
|
if (tmp.LowerCaseEqualsLiteral("radial-gradient")) {
|
|
if (!ParseRadialGradient(aValue, gradientFlags)) {
|
|
return CSSParseResult::Error;
|
|
}
|
|
return CSSParseResult::Ok;
|
|
}
|
|
if ((gradientFlags == eGradient_WebkitLegacy) &&
|
|
tmp.LowerCaseEqualsLiteral("gradient")) {
|
|
// Note: we check gradientFlags using '==' to select *exactly*
|
|
// eGradient_WebkitLegacy -- and exclude eGradient_Repeating -- because
|
|
// we don't want to accept -webkit-repeating-gradient() expressions.
|
|
// (This is not a recognized syntax.)
|
|
if (!ParseWebkitGradient(aValue)) {
|
|
return CSSParseResult::Error;
|
|
}
|
|
return CSSParseResult::Ok;
|
|
}
|
|
|
|
if (ShouldUseUnprefixingService() &&
|
|
!gradientFlags &&
|
|
StringBeginsWith(tmp, NS_LITERAL_STRING("-webkit-"))) {
|
|
// Copy 'tmp' into a string on the stack, since as soon as we
|
|
// start parsing, its backing store (in "tk") will be overwritten
|
|
nsAutoString prefixedFuncName(tmp);
|
|
if (!ParseWebkitPrefixedGradientWithService(prefixedFuncName, aValue)) {
|
|
return CSSParseResult::Error;
|
|
}
|
|
return CSSParseResult::Ok;
|
|
}
|
|
}
|
|
if ((aVariantMask & VARIANT_IMAGE_RECT) != 0 &&
|
|
eCSSToken_Function == tk->mType &&
|
|
tk->mIdent.LowerCaseEqualsLiteral("-moz-image-rect")) {
|
|
if (!ParseImageRect(aValue)) {
|
|
return CSSParseResult::Error;
|
|
}
|
|
return CSSParseResult::Ok;
|
|
}
|
|
if ((aVariantMask & VARIANT_ELEMENT) != 0 &&
|
|
eCSSToken_Function == tk->mType &&
|
|
tk->mIdent.LowerCaseEqualsLiteral("-moz-element")) {
|
|
if (!ParseElement(aValue)) {
|
|
return CSSParseResult::Error;
|
|
}
|
|
return CSSParseResult::Ok;
|
|
}
|
|
if ((aVariantMask & VARIANT_COLOR) != 0) {
|
|
if (mHashlessColorQuirk || // NONSTANDARD: Nav interprets 'xxyyzz' values even without '#' prefix
|
|
(eCSSToken_ID == tk->mType) ||
|
|
(eCSSToken_Hash == tk->mType) ||
|
|
(eCSSToken_Ident == tk->mType) ||
|
|
((eCSSToken_Function == tk->mType) &&
|
|
(tk->mIdent.LowerCaseEqualsLiteral("rgb") ||
|
|
tk->mIdent.LowerCaseEqualsLiteral("hsl") ||
|
|
tk->mIdent.LowerCaseEqualsLiteral("rgba") ||
|
|
tk->mIdent.LowerCaseEqualsLiteral("hsla"))))
|
|
{
|
|
// Put token back so that parse color can get it
|
|
UngetToken();
|
|
return ParseColor(aValue);
|
|
}
|
|
}
|
|
if (((aVariantMask & VARIANT_STRING) != 0) &&
|
|
(eCSSToken_String == tk->mType)) {
|
|
nsAutoString buffer;
|
|
buffer.Append(tk->mIdent);
|
|
aValue.SetStringValue(buffer, eCSSUnit_String);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
if (((aVariantMask &
|
|
(VARIANT_IDENTIFIER | VARIANT_IDENTIFIER_NO_INHERIT)) != 0) &&
|
|
(eCSSToken_Ident == tk->mType) &&
|
|
((aVariantMask & VARIANT_IDENTIFIER) != 0 ||
|
|
!(tk->mIdent.LowerCaseEqualsLiteral("inherit") ||
|
|
tk->mIdent.LowerCaseEqualsLiteral("initial") ||
|
|
(tk->mIdent.LowerCaseEqualsLiteral("unset") &&
|
|
nsLayoutUtils::UnsetValueEnabled())))) {
|
|
aValue.SetStringValue(tk->mIdent, eCSSUnit_Ident);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
if (((aVariantMask & VARIANT_COUNTER) != 0) &&
|
|
(eCSSToken_Function == tk->mType) &&
|
|
(tk->mIdent.LowerCaseEqualsLiteral("counter") ||
|
|
tk->mIdent.LowerCaseEqualsLiteral("counters"))) {
|
|
if (!ParseCounter(aValue)) {
|
|
return CSSParseResult::Error;
|
|
}
|
|
return CSSParseResult::Ok;
|
|
}
|
|
if (((aVariantMask & VARIANT_ATTR) != 0) &&
|
|
(eCSSToken_Function == tk->mType) &&
|
|
tk->mIdent.LowerCaseEqualsLiteral("attr")) {
|
|
if (!ParseAttr(aValue)) {
|
|
SkipUntil(')');
|
|
return CSSParseResult::Error;
|
|
}
|
|
return CSSParseResult::Ok;
|
|
}
|
|
if (((aVariantMask & VARIANT_TIMING_FUNCTION) != 0) &&
|
|
(eCSSToken_Function == tk->mType)) {
|
|
if (tk->mIdent.LowerCaseEqualsLiteral("cubic-bezier")) {
|
|
if (!ParseTransitionTimingFunctionValues(aValue)) {
|
|
SkipUntil(')');
|
|
return CSSParseResult::Error;
|
|
}
|
|
return CSSParseResult::Ok;
|
|
}
|
|
if (tk->mIdent.LowerCaseEqualsLiteral("steps")) {
|
|
if (!ParseTransitionStepTimingFunctionValues(aValue)) {
|
|
SkipUntil(')');
|
|
return CSSParseResult::Error;
|
|
}
|
|
return CSSParseResult::Ok;
|
|
}
|
|
}
|
|
if ((aVariantMask & VARIANT_CALC) &&
|
|
IsCSSTokenCalcFunction(*tk)) {
|
|
// calc() currently allows only lengths and percents and number inside it.
|
|
// And note that in current implementation, number cannot be mixed with
|
|
// length and percent.
|
|
if (!ParseCalc(aValue, aVariantMask & VARIANT_LPN)) {
|
|
return CSSParseResult::Error;
|
|
}
|
|
return CSSParseResult::Ok;
|
|
}
|
|
|
|
UngetToken();
|
|
AssertNextTokenAt(lineBefore, colBefore);
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseCustomIdent(nsCSSValue& aValue,
|
|
const nsAutoString& aIdentValue,
|
|
const nsCSSKeyword aExcludedKeywords[],
|
|
const nsCSSProps::KTableEntry aPropertyKTable[])
|
|
{
|
|
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aIdentValue);
|
|
if (keyword == eCSSKeyword_UNKNOWN) {
|
|
// Fast path for identifiers that are not known CSS keywords:
|
|
aValue.SetStringValue(mToken.mIdent, eCSSUnit_Ident);
|
|
return true;
|
|
}
|
|
if (keyword == eCSSKeyword_inherit ||
|
|
keyword == eCSSKeyword_initial ||
|
|
keyword == eCSSKeyword_unset ||
|
|
keyword == eCSSKeyword_default ||
|
|
(aPropertyKTable &&
|
|
nsCSSProps::FindIndexOfKeyword(keyword, aPropertyKTable) >= 0)) {
|
|
return false;
|
|
}
|
|
if (aExcludedKeywords) {
|
|
for (uint32_t i = 0;; i++) {
|
|
nsCSSKeyword excludedKeyword = aExcludedKeywords[i];
|
|
if (excludedKeyword == eCSSKeyword_UNKNOWN) {
|
|
break;
|
|
}
|
|
if (excludedKeyword == keyword) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
aValue.SetStringValue(mToken.mIdent, eCSSUnit_Ident);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseCounter(nsCSSValue& aValue)
|
|
{
|
|
nsCSSUnit unit = (mToken.mIdent.LowerCaseEqualsLiteral("counter") ?
|
|
eCSSUnit_Counter : eCSSUnit_Counters);
|
|
|
|
// A non-iterative for loop to break out when an error occurs.
|
|
for (;;) {
|
|
if (!GetToken(true)) {
|
|
break;
|
|
}
|
|
if (eCSSToken_Ident != mToken.mType) {
|
|
UngetToken();
|
|
break;
|
|
}
|
|
|
|
RefPtr<nsCSSValue::Array> val =
|
|
nsCSSValue::Array::Create(unit == eCSSUnit_Counter ? 2 : 3);
|
|
|
|
val->Item(0).SetStringValue(mToken.mIdent, eCSSUnit_Ident);
|
|
|
|
if (eCSSUnit_Counters == unit) {
|
|
// must have a comma and then a separator string
|
|
if (!ExpectSymbol(',', true) || !GetToken(true)) {
|
|
break;
|
|
}
|
|
if (eCSSToken_String != mToken.mType) {
|
|
UngetToken();
|
|
break;
|
|
}
|
|
val->Item(1).SetStringValue(mToken.mIdent, eCSSUnit_String);
|
|
}
|
|
|
|
// get optional type
|
|
int32_t typeItem = eCSSUnit_Counters == unit ? 2 : 1;
|
|
nsCSSValue& type = val->Item(typeItem);
|
|
if (ExpectSymbol(',', true)) {
|
|
if (!ParseCounterStyleNameValue(type) && !ParseSymbols(type)) {
|
|
break;
|
|
}
|
|
} else {
|
|
type.SetStringValue(NS_LITERAL_STRING("decimal"), eCSSUnit_Ident);
|
|
}
|
|
|
|
if (!ExpectSymbol(')', true)) {
|
|
break;
|
|
}
|
|
|
|
aValue.SetArrayValue(val, unit);
|
|
return true;
|
|
}
|
|
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseAttr(nsCSSValue& aValue)
|
|
{
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
|
|
nsAutoString attr;
|
|
if (eCSSToken_Ident == mToken.mType) { // attr name or namespace
|
|
nsAutoString holdIdent(mToken.mIdent);
|
|
if (ExpectSymbol('|', false)) { // namespace
|
|
int32_t nameSpaceID = GetNamespaceIdForPrefix(holdIdent);
|
|
if (nameSpaceID == kNameSpaceID_Unknown) {
|
|
return false;
|
|
}
|
|
attr.AppendInt(nameSpaceID, 10);
|
|
attr.Append(char16_t('|'));
|
|
if (! GetToken(false)) {
|
|
REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
|
|
return false;
|
|
}
|
|
if (eCSSToken_Ident == mToken.mType) {
|
|
attr.Append(mToken.mIdent);
|
|
}
|
|
else {
|
|
REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
}
|
|
else { // no namespace
|
|
attr = holdIdent;
|
|
}
|
|
}
|
|
else if (mToken.IsSymbol('*')) { // namespace wildcard
|
|
// Wildcard namespace makes no sense here and is not allowed
|
|
REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
else if (mToken.IsSymbol('|')) { // explicit NO namespace
|
|
if (! GetToken(false)) {
|
|
REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
|
|
return false;
|
|
}
|
|
if (eCSSToken_Ident == mToken.mType) {
|
|
attr.Append(mToken.mIdent);
|
|
}
|
|
else {
|
|
REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
REPORT_UNEXPECTED_TOKEN(PEAttributeNameOrNamespaceExpected);
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
if (!ExpectSymbol(')', true)) {
|
|
return false;
|
|
}
|
|
aValue.SetStringValue(attr, eCSSUnit_Attr);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseSymbols(nsCSSValue& aValue)
|
|
{
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
if (mToken.mType != eCSSToken_Function &&
|
|
!mToken.mIdent.LowerCaseEqualsLiteral("symbols")) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
RefPtr<nsCSSValue::Array> params = nsCSSValue::Array::Create(2);
|
|
nsCSSValue& type = params->Item(0);
|
|
nsCSSValue& symbols = params->Item(1);
|
|
|
|
if (!ParseEnum(type, nsCSSProps::kCounterSymbolsSystemKTable)) {
|
|
type.SetIntValue(NS_STYLE_COUNTER_SYSTEM_SYMBOLIC, eCSSUnit_Enumerated);
|
|
}
|
|
|
|
bool first = true;
|
|
nsCSSValueList* item = symbols.SetListValue();
|
|
for (;;) {
|
|
// FIXME Should also include VARIANT_IMAGE. See bug 1071436.
|
|
if (!ParseSingleTokenVariant(item->mValue, VARIANT_STRING, nullptr)) {
|
|
break;
|
|
}
|
|
if (ExpectSymbol(')', true)) {
|
|
if (first) {
|
|
switch (type.GetIntValue()) {
|
|
case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
|
|
case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
|
|
// require at least two symbols
|
|
return false;
|
|
}
|
|
}
|
|
aValue.SetArrayValue(params, eCSSUnit_Symbols);
|
|
return true;
|
|
}
|
|
item->mNext = new nsCSSValueList;
|
|
item = item->mNext;
|
|
first = false;
|
|
}
|
|
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::SetValueToURL(nsCSSValue& aValue, const nsString& aURL)
|
|
{
|
|
if (!mSheetPrincipal) {
|
|
if (!mSheetPrincipalRequired) {
|
|
/* Pretend to succeed. */
|
|
return true;
|
|
}
|
|
|
|
NS_NOTREACHED("Codepaths that expect to parse URLs MUST pass in an "
|
|
"origin principal");
|
|
return false;
|
|
}
|
|
|
|
RefPtr<nsStringBuffer> buffer(nsCSSValue::BufferFromString(aURL));
|
|
|
|
// Note: urlVal retains its own reference to |buffer|.
|
|
mozilla::css::URLValue *urlVal =
|
|
new mozilla::css::URLValue(buffer, mBaseURI, mSheetURI, mSheetPrincipal);
|
|
aValue.SetURLValue(urlVal);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Parse the image-orientation property, which has the grammar:
|
|
* <angle> flip? | flip | from-image
|
|
*/
|
|
bool
|
|
CSSParserImpl::ParseImageOrientation(nsCSSValue& aValue)
|
|
{
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
|
|
// 'inherit', 'initial' and 'unset' must be alone
|
|
return true;
|
|
}
|
|
|
|
// Check for an angle with optional 'flip'.
|
|
nsCSSValue angle;
|
|
if (ParseSingleTokenVariant(angle, VARIANT_ANGLE, nullptr)) {
|
|
nsCSSValue flip;
|
|
|
|
if (ParseSingleTokenVariant(flip, VARIANT_KEYWORD,
|
|
nsCSSProps::kImageOrientationFlipKTable)) {
|
|
RefPtr<nsCSSValue::Array> array = nsCSSValue::Array::Create(2);
|
|
array->Item(0) = angle;
|
|
array->Item(1) = flip;
|
|
aValue.SetArrayValue(array, eCSSUnit_Array);
|
|
} else {
|
|
aValue = angle;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// The remaining possibilities (bare 'flip' and 'from-image') are both
|
|
// keywords, so we can handle them at the same time.
|
|
nsCSSValue keyword;
|
|
if (ParseSingleTokenVariant(keyword, VARIANT_KEYWORD,
|
|
nsCSSProps::kImageOrientationKTable)) {
|
|
aValue = keyword;
|
|
return true;
|
|
}
|
|
|
|
// All possibilities failed.
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Parse the arguments of -moz-image-rect() function.
|
|
* -moz-image-rect(<uri>, <top>, <right>, <bottom>, <left>)
|
|
*/
|
|
bool
|
|
CSSParserImpl::ParseImageRect(nsCSSValue& aImage)
|
|
{
|
|
// A non-iterative for loop to break out when an error occurs.
|
|
for (;;) {
|
|
nsCSSValue newFunction;
|
|
static const uint32_t kNumArgs = 5;
|
|
nsCSSValue::Array* func =
|
|
newFunction.InitFunction(eCSSKeyword__moz_image_rect, kNumArgs);
|
|
|
|
// func->Item(0) is reserved for the function name.
|
|
nsCSSValue& url = func->Item(1);
|
|
nsCSSValue& top = func->Item(2);
|
|
nsCSSValue& right = func->Item(3);
|
|
nsCSSValue& bottom = func->Item(4);
|
|
nsCSSValue& left = func->Item(5);
|
|
|
|
nsAutoString urlString;
|
|
if (!ParseURLOrString(urlString) ||
|
|
!SetValueToURL(url, urlString) ||
|
|
!ExpectSymbol(',', true)) {
|
|
break;
|
|
}
|
|
|
|
static const int32_t VARIANT_SIDE = VARIANT_NUMBER | VARIANT_PERCENT;
|
|
if (!ParseSingleTokenNonNegativeVariant(top, VARIANT_SIDE, nullptr) ||
|
|
!ExpectSymbol(',', true) ||
|
|
!ParseSingleTokenNonNegativeVariant(right, VARIANT_SIDE, nullptr) ||
|
|
!ExpectSymbol(',', true) ||
|
|
!ParseSingleTokenNonNegativeVariant(bottom, VARIANT_SIDE, nullptr) ||
|
|
!ExpectSymbol(',', true) ||
|
|
!ParseSingleTokenNonNegativeVariant(left, VARIANT_SIDE, nullptr) ||
|
|
!ExpectSymbol(')', true))
|
|
break;
|
|
|
|
aImage = newFunction;
|
|
return true;
|
|
}
|
|
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
// <element>: -moz-element(# <element_id> )
|
|
bool
|
|
CSSParserImpl::ParseElement(nsCSSValue& aValue)
|
|
{
|
|
// A non-iterative for loop to break out when an error occurs.
|
|
for (;;) {
|
|
if (!GetToken(true))
|
|
break;
|
|
|
|
if (mToken.mType == eCSSToken_ID) {
|
|
aValue.SetStringValue(mToken.mIdent, eCSSUnit_Element);
|
|
} else {
|
|
UngetToken();
|
|
break;
|
|
}
|
|
|
|
if (!ExpectSymbol(')', true))
|
|
break;
|
|
|
|
return true;
|
|
}
|
|
|
|
// If we detect a syntax error, we must match the opening parenthesis of the
|
|
// function with the closing parenthesis and skip all the tokens in between.
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
// flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
|
|
bool
|
|
CSSParserImpl::ParseFlex()
|
|
{
|
|
// First check for inherit / initial / unset
|
|
nsCSSValue tmpVal;
|
|
if (ParseSingleTokenVariant(tmpVal, VARIANT_INHERIT, nullptr)) {
|
|
AppendValue(eCSSProperty_flex_grow, tmpVal);
|
|
AppendValue(eCSSProperty_flex_shrink, tmpVal);
|
|
AppendValue(eCSSProperty_flex_basis, tmpVal);
|
|
return true;
|
|
}
|
|
|
|
// Next, check for 'none' == '0 0 auto'
|
|
if (ParseSingleTokenVariant(tmpVal, VARIANT_NONE, nullptr)) {
|
|
AppendValue(eCSSProperty_flex_grow, nsCSSValue(0.0f, eCSSUnit_Number));
|
|
AppendValue(eCSSProperty_flex_shrink, nsCSSValue(0.0f, eCSSUnit_Number));
|
|
AppendValue(eCSSProperty_flex_basis, nsCSSValue(eCSSUnit_Auto));
|
|
return true;
|
|
}
|
|
|
|
// OK, try parsing our value as individual per-subproperty components:
|
|
// [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
|
|
|
|
// Each subproperty has a default value that it takes when it's omitted in a
|
|
// "flex" shorthand value. These default values are *only* for the shorthand
|
|
// syntax -- they're distinct from the subproperties' own initial values. We
|
|
// start with each subproperty at its default, as if we had "flex: 1 1 0%".
|
|
nsCSSValue flexGrow(1.0f, eCSSUnit_Number);
|
|
nsCSSValue flexShrink(1.0f, eCSSUnit_Number);
|
|
nsCSSValue flexBasis(0.0f, eCSSUnit_Percent);
|
|
|
|
// OVERVIEW OF PARSING STRATEGY:
|
|
// =============================
|
|
// a) Parse the first component as either flex-basis or flex-grow.
|
|
// b) If it wasn't flex-grow, parse the _next_ component as flex-grow.
|
|
// c) Now we've just parsed flex-grow -- so try parsing the next thing as
|
|
// flex-shrink.
|
|
// d) Finally: If we didn't get flex-basis at the beginning, try to parse
|
|
// it now, at the end.
|
|
//
|
|
// More details in each section below.
|
|
|
|
uint32_t flexBasisVariantMask =
|
|
(nsCSSProps::ParserVariant(eCSSProperty_flex_basis) & ~(VARIANT_INHERIT));
|
|
|
|
// (a) Parse first component. It can be either be a 'flex-basis' value or a
|
|
// 'flex-grow' value, so we use the flex-basis-specific variant mask, along
|
|
// with VARIANT_NUMBER to accept 'flex-grow' values.
|
|
//
|
|
// NOTE: if we encounter unitless 0 here, we *must* interpret it as a
|
|
// 'flex-grow' value (a number), *not* as a 'flex-basis' value (a length).
|
|
// Conveniently, that's the behavior this combined variant-mask gives us --
|
|
// it'll treat unitless 0 as a number. The flexbox spec requires this:
|
|
// "a unitless zero that is not already preceded by two flex factors must be
|
|
// interpreted as a flex factor.
|
|
if (ParseNonNegativeVariant(tmpVal, flexBasisVariantMask | VARIANT_NUMBER,
|
|
nsCSSProps::kWidthKTable) != CSSParseResult::Ok) {
|
|
// First component was not a valid flex-basis or flex-grow value. Fail.
|
|
return false;
|
|
}
|
|
|
|
// Record what we just parsed as either flex-basis or flex-grow:
|
|
bool wasFirstComponentFlexBasis = (tmpVal.GetUnit() != eCSSUnit_Number);
|
|
(wasFirstComponentFlexBasis ? flexBasis : flexGrow) = tmpVal;
|
|
|
|
// (b) If we didn't get flex-grow yet, parse _next_ component as flex-grow.
|
|
bool doneParsing = false;
|
|
if (wasFirstComponentFlexBasis) {
|
|
if (ParseNonNegativeNumber(tmpVal)) {
|
|
flexGrow = tmpVal;
|
|
} else {
|
|
// Failed to parse anything after our flex-basis -- that's fine. We can
|
|
// skip the remaining parsing.
|
|
doneParsing = true;
|
|
}
|
|
}
|
|
|
|
if (!doneParsing) {
|
|
// (c) OK -- the last thing we parsed was flex-grow, so look for a
|
|
// flex-shrink in the next position.
|
|
if (ParseNonNegativeNumber(tmpVal)) {
|
|
flexShrink = tmpVal;
|
|
}
|
|
|
|
// d) Finally: If we didn't get flex-basis at the beginning, try to parse
|
|
// it now, at the end.
|
|
//
|
|
// NOTE: If we encounter unitless 0 in this final position, we'll parse it
|
|
// as a 'flex-basis' value. That's OK, because we know it must have
|
|
// been "preceded by 2 flex factors" (justification below), which gets us
|
|
// out of the spec's requirement of otherwise having to treat unitless 0
|
|
// as a flex factor.
|
|
//
|
|
// JUSTIFICATION: How do we know that a unitless 0 here must have been
|
|
// preceded by 2 flex factors? Well, suppose we had a unitless 0 that
|
|
// was preceded by only 1 flex factor. Then, we would have already
|
|
// accepted this unitless 0 as the 'flex-shrink' value, up above (since
|
|
// it's a valid flex-shrink value), and we'd have moved on to the next
|
|
// token (if any). And of course, if we instead had a unitless 0 preceded
|
|
// by *no* flex factors (if it were the first token), we would've already
|
|
// parsed it in our very first call to ParseNonNegativeVariant(). So, any
|
|
// unitless 0 encountered here *must* have been preceded by 2 flex factors.
|
|
if (!wasFirstComponentFlexBasis) {
|
|
CSSParseResult result =
|
|
ParseNonNegativeVariant(tmpVal, flexBasisVariantMask,
|
|
nsCSSProps::kWidthKTable);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
} else if (result == CSSParseResult::Ok) {
|
|
flexBasis = tmpVal;
|
|
}
|
|
}
|
|
}
|
|
|
|
AppendValue(eCSSProperty_flex_grow, flexGrow);
|
|
AppendValue(eCSSProperty_flex_shrink, flexShrink);
|
|
AppendValue(eCSSProperty_flex_basis, flexBasis);
|
|
|
|
return true;
|
|
}
|
|
|
|
// flex-flow: <flex-direction> || <flex-wrap>
|
|
bool
|
|
CSSParserImpl::ParseFlexFlow()
|
|
{
|
|
static const nsCSSPropertyID kFlexFlowSubprops[] = {
|
|
eCSSProperty_flex_direction,
|
|
eCSSProperty_flex_wrap
|
|
};
|
|
const size_t numProps = MOZ_ARRAY_LENGTH(kFlexFlowSubprops);
|
|
nsCSSValue values[numProps];
|
|
|
|
int32_t found = ParseChoice(values, kFlexFlowSubprops, numProps);
|
|
|
|
// Bail if we didn't successfully parse anything
|
|
if (found < 1) {
|
|
return false;
|
|
}
|
|
|
|
// If either property didn't get an explicit value, use its initial value.
|
|
if ((found & 1) == 0) {
|
|
values[0].SetIntValue(NS_STYLE_FLEX_DIRECTION_ROW, eCSSUnit_Enumerated);
|
|
}
|
|
if ((found & 2) == 0) {
|
|
values[1].SetIntValue(NS_STYLE_FLEX_WRAP_NOWRAP, eCSSUnit_Enumerated);
|
|
}
|
|
|
|
// Store these values and declare success!
|
|
for (size_t i = 0; i < numProps; i++) {
|
|
AppendValue(kFlexFlowSubprops[i], values[i]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseGridAutoFlow()
|
|
{
|
|
nsCSSValue value;
|
|
if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
|
|
AppendValue(eCSSProperty_grid_auto_flow, value);
|
|
return true;
|
|
}
|
|
|
|
static const int32_t mask[] = {
|
|
NS_STYLE_GRID_AUTO_FLOW_ROW | NS_STYLE_GRID_AUTO_FLOW_COLUMN,
|
|
MASK_END_VALUE
|
|
};
|
|
if (!ParseBitmaskValues(value, nsCSSProps::kGridAutoFlowKTable, mask)) {
|
|
return false;
|
|
}
|
|
int32_t bitField = value.GetIntValue();
|
|
|
|
// If neither row nor column is provided, row is assumed.
|
|
if (!(bitField & (NS_STYLE_GRID_AUTO_FLOW_ROW |
|
|
NS_STYLE_GRID_AUTO_FLOW_COLUMN))) {
|
|
value.SetIntValue(bitField | NS_STYLE_GRID_AUTO_FLOW_ROW,
|
|
eCSSUnit_Enumerated);
|
|
}
|
|
|
|
AppendValue(eCSSProperty_grid_auto_flow, value);
|
|
return true;
|
|
}
|
|
|
|
static const nsCSSKeyword kGridLineKeywords[] = {
|
|
eCSSKeyword_span,
|
|
eCSSKeyword_UNKNOWN // End-of-array marker
|
|
};
|
|
|
|
CSSParseResult
|
|
CSSParserImpl::ParseGridLineNames(nsCSSValue& aValue)
|
|
{
|
|
if (!ExpectSymbol('[', true)) {
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
if (!GetToken(true) || mToken.IsSymbol(']')) {
|
|
return CSSParseResult::Ok;
|
|
}
|
|
// 'return' so far leaves aValue untouched, to represent an empty list.
|
|
|
|
nsCSSValueList* item;
|
|
if (aValue.GetUnit() == eCSSUnit_List) {
|
|
// Find the end of an existing list.
|
|
// The grid-template shorthand uses this, at most once for a given list.
|
|
|
|
// NOTE: we could avoid this traversal by somehow keeping around
|
|
// a pointer to the last item from the previous call.
|
|
// It's not yet clear if this is worth the additional code complexity.
|
|
item = aValue.GetListValue();
|
|
while (item->mNext) {
|
|
item = item->mNext;
|
|
}
|
|
item->mNext = new nsCSSValueList;
|
|
item = item->mNext;
|
|
} else {
|
|
MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Null, "Unexpected unit");
|
|
item = aValue.SetListValue();
|
|
}
|
|
for (;;) {
|
|
if (!(eCSSToken_Ident == mToken.mType &&
|
|
ParseCustomIdent(item->mValue, mToken.mIdent, kGridLineKeywords))) {
|
|
UngetToken();
|
|
SkipUntil(']');
|
|
return CSSParseResult::Error;
|
|
}
|
|
if (!GetToken(true) || mToken.IsSymbol(']')) {
|
|
return CSSParseResult::Ok;
|
|
}
|
|
item->mNext = new nsCSSValueList;
|
|
item = item->mNext;
|
|
}
|
|
}
|
|
|
|
// Assuming the 'repeat(' function token has already been consumed,
|
|
// parse the rest of repeat(<positive-integer> | auto-fill, <line-names>+)
|
|
// Append to the linked list whose end is given by |aTailPtr|,
|
|
// and update |aTailPtr| to point to the new end of the list.
|
|
bool
|
|
CSSParserImpl::ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr)
|
|
{
|
|
int32_t repetitions;
|
|
Maybe<int32_t> repeatAutoEnum;
|
|
if (!ParseGridTrackRepeatIntro(true, &repetitions, &repeatAutoEnum)) {
|
|
return false;
|
|
}
|
|
if (repeatAutoEnum.isSome()) {
|
|
// Parse exactly one <line-names>.
|
|
nsCSSValue listValue;
|
|
nsCSSValueList* list = listValue.SetListValue();
|
|
if (ParseGridLineNames(list->mValue) != CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
if (!ExpectSymbol(')', true)) {
|
|
return false;
|
|
}
|
|
// Instead of hooking up this list into the flat name list as usual,
|
|
// we create a pair(Int, List) where the first value is the auto-fill
|
|
// keyword and the second is the name list to repeat.
|
|
nsCSSValue kwd;
|
|
kwd.SetIntValue(repeatAutoEnum.value(), eCSSUnit_Enumerated);
|
|
*aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList;
|
|
(*aTailPtr)->mValue.SetPairValue(kwd, listValue);
|
|
return true;
|
|
}
|
|
|
|
// Parse at least one <line-names>
|
|
nsCSSValueList* tail = *aTailPtr;
|
|
do {
|
|
tail->mNext = new nsCSSValueList;
|
|
tail = tail->mNext;
|
|
if (ParseGridLineNames(tail->mValue) != CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
} while (!ExpectSymbol(')', true));
|
|
nsCSSValueList* firstRepeatedItem = (*aTailPtr)->mNext;
|
|
nsCSSValueList* lastRepeatedItem = tail;
|
|
|
|
// Our repeated items are already in the target list once,
|
|
// so they need to be repeated |repetitions - 1| more times.
|
|
MOZ_ASSERT(repetitions > 0, "Expected positive repetitions");
|
|
while (--repetitions) {
|
|
nsCSSValueList* repeatedItem = firstRepeatedItem;
|
|
for (;;) {
|
|
tail->mNext = new nsCSSValueList;
|
|
tail = tail->mNext;
|
|
tail->mValue = repeatedItem->mValue;
|
|
if (repeatedItem == lastRepeatedItem) {
|
|
break;
|
|
}
|
|
repeatedItem = repeatedItem->mNext;
|
|
}
|
|
}
|
|
*aTailPtr = tail;
|
|
return true;
|
|
}
|
|
|
|
// Assuming a 'subgrid' keyword was already consumed, parse <line-name-list>?
|
|
bool
|
|
CSSParserImpl::ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue)
|
|
{
|
|
nsCSSValueList* item = aValue.SetListValue();
|
|
// This marker distinguishes the value from a <track-list>.
|
|
item->mValue.SetIntValue(NS_STYLE_GRID_TEMPLATE_SUBGRID,
|
|
eCSSUnit_Enumerated);
|
|
bool haveRepeatAuto = false;
|
|
for (;;) {
|
|
// First try to parse <name-repeat>, i.e.
|
|
// repeat(<positive-integer> | auto-fill, <line-names>+)
|
|
if (!GetToken(true)) {
|
|
return true;
|
|
}
|
|
if (mToken.mType == eCSSToken_Function &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("repeat")) {
|
|
nsCSSValueList* startOfRepeat = item;
|
|
if (!ParseGridLineNameListRepeat(&item)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
if (startOfRepeat->mNext->mValue.GetUnit() == eCSSUnit_Pair) {
|
|
if (haveRepeatAuto) {
|
|
REPORT_UNEXPECTED(PEMoreThanOneGridRepeatAutoFillInNameList);
|
|
return false;
|
|
}
|
|
haveRepeatAuto = true;
|
|
}
|
|
} else {
|
|
UngetToken();
|
|
|
|
// This was not a repeat() function. Try to parse <line-names>.
|
|
nsCSSValue lineNames;
|
|
CSSParseResult result = ParseGridLineNames(lineNames);
|
|
if (result == CSSParseResult::NotFound) {
|
|
return true;
|
|
}
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
}
|
|
item->mNext = new nsCSSValueList;
|
|
item = item->mNext;
|
|
item->mValue = lineNames;
|
|
}
|
|
}
|
|
}
|
|
|
|
CSSParseResult
|
|
CSSParserImpl::ParseGridTrackBreadth(nsCSSValue& aValue)
|
|
{
|
|
CSSParseResult result = ParseNonNegativeVariant(aValue,
|
|
VARIANT_AUTO | VARIANT_LPCALC | VARIANT_KEYWORD,
|
|
nsCSSProps::kGridTrackBreadthKTable);
|
|
|
|
if (result == CSSParseResult::Ok ||
|
|
result == CSSParseResult::Error) {
|
|
return result;
|
|
}
|
|
|
|
// Attempt to parse <flex> (a dimension with the "fr" unit).
|
|
if (!GetToken(true)) {
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
if (!(eCSSToken_Dimension == mToken.mType &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("fr") &&
|
|
mToken.mNumber >= 0)) {
|
|
UngetToken();
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
aValue.SetFloatValue(mToken.mNumber, eCSSUnit_FlexFraction);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
|
|
// Parse a <track-size>, or <fixed-size> when aFlags has eFixedTrackSize.
|
|
CSSParseResult
|
|
CSSParserImpl::ParseGridTrackSize(nsCSSValue& aValue,
|
|
GridTrackSizeFlags aFlags)
|
|
{
|
|
const bool requireFixedSize =
|
|
!!(aFlags & GridTrackSizeFlags::eFixedTrackSize);
|
|
// Attempt to parse a single <track-breadth>.
|
|
CSSParseResult result = ParseGridTrackBreadth(aValue);
|
|
if (requireFixedSize && result == CSSParseResult::Ok &&
|
|
!aValue.IsLengthPercentCalcUnit()) {
|
|
result = CSSParseResult::Error;
|
|
}
|
|
if (result == CSSParseResult::Ok ||
|
|
result == CSSParseResult::Error) {
|
|
return result;
|
|
}
|
|
|
|
// Attempt to parse a minmax() or fit-content() function.
|
|
if (!GetToken(true)) {
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
if (eCSSToken_Function != mToken.mType) {
|
|
UngetToken();
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
if (mToken.mIdent.LowerCaseEqualsLiteral("fit-content")) {
|
|
nsCSSValue::Array* func = aValue.InitFunction(eCSSKeyword_fit_content, 1);
|
|
if (ParseGridTrackBreadth(func->Item(1)) == CSSParseResult::Ok &&
|
|
func->Item(1).IsLengthPercentCalcUnit() &&
|
|
ExpectSymbol(')', true)) {
|
|
return CSSParseResult::Ok;
|
|
}
|
|
SkipUntil(')');
|
|
return CSSParseResult::Error;
|
|
}
|
|
if (!mToken.mIdent.LowerCaseEqualsLiteral("minmax")) {
|
|
UngetToken();
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
nsCSSValue::Array* func = aValue.InitFunction(eCSSKeyword_minmax, 2);
|
|
if (ParseGridTrackBreadth(func->Item(1)) == CSSParseResult::Ok &&
|
|
ExpectSymbol(',', true) &&
|
|
ParseGridTrackBreadth(func->Item(2)) == CSSParseResult::Ok &&
|
|
ExpectSymbol(')', true)) {
|
|
if (requireFixedSize &&
|
|
!func->Item(1).IsLengthPercentCalcUnit() &&
|
|
!func->Item(2).IsLengthPercentCalcUnit()) {
|
|
return CSSParseResult::Error;
|
|
}
|
|
// Reject 'fr' min-sizing.
|
|
if (func->Item(1).GetUnit() == eCSSUnit_FlexFraction) {
|
|
return CSSParseResult::Error;
|
|
}
|
|
return CSSParseResult::Ok;
|
|
}
|
|
SkipUntil(')');
|
|
return CSSParseResult::Error;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseGridAutoColumnsRows(nsCSSPropertyID aPropID)
|
|
{
|
|
nsCSSValue value;
|
|
if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr) ||
|
|
ParseGridTrackSize(value) == CSSParseResult::Ok) {
|
|
AppendValue(aPropID, value);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseGridTrackListWithFirstLineNames(nsCSSValue& aValue,
|
|
const nsCSSValue& aFirstLineNames,
|
|
GridTrackListFlags aFlags)
|
|
{
|
|
nsCSSValueList* firstLineNamesItem = aValue.SetListValue();
|
|
firstLineNamesItem->mValue = aFirstLineNames;
|
|
|
|
// This function is trying to parse <track-list>, which is
|
|
// [ <line-names>? [ <track-size> | <repeat()> ] ]+ <line-names>?
|
|
// and we're already past the first "<line-names>?".
|
|
// If aFlags contains eExplicitTrackList then <repeat()> is disallowed.
|
|
//
|
|
// Each iteration of the following loop attempts to parse either a
|
|
// repeat() or a <track-size> expression, and then an (optional)
|
|
// <line-names> expression.
|
|
//
|
|
// The only successful exit point from this loop is the ::NotFound
|
|
// case after ParseGridTrackSize(); i.e. we'll greedily parse
|
|
// repeat()/<track-size> until we can't find one.
|
|
nsCSSValueList* item = firstLineNamesItem;
|
|
bool haveRepeatAuto = false;
|
|
for (;;) {
|
|
// First try to parse repeat()
|
|
if (!GetToken(true)) {
|
|
break;
|
|
}
|
|
if (!(aFlags & GridTrackListFlags::eExplicitTrackList) &&
|
|
mToken.mType == eCSSToken_Function &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("repeat")) {
|
|
nsCSSValueList* startOfRepeat = item;
|
|
if (!ParseGridTrackListRepeat(&item)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
auto firstRepeat = startOfRepeat->mNext;
|
|
if (firstRepeat->mValue.GetUnit() == eCSSUnit_Pair) {
|
|
if (haveRepeatAuto) {
|
|
REPORT_UNEXPECTED(PEMoreThanOneGridRepeatAutoFillFitInTrackList);
|
|
return false;
|
|
}
|
|
haveRepeatAuto = true;
|
|
// We're parsing an <auto-track-list>, which requires that all tracks
|
|
// are <fixed-size>, so we need to check the ones we've parsed already.
|
|
for (nsCSSValueList* list = firstLineNamesItem->mNext;
|
|
list != firstRepeat; list = list->mNext) {
|
|
if (list->mValue.GetUnit() == eCSSUnit_Function) {
|
|
nsCSSValue::Array* func = list->mValue.GetArrayValue();
|
|
auto funcName = func->Item(0).GetKeywordValue();
|
|
if (funcName == eCSSKeyword_minmax) {
|
|
if (!func->Item(1).IsLengthPercentCalcUnit() &&
|
|
!func->Item(2).IsLengthPercentCalcUnit()) {
|
|
return false;
|
|
}
|
|
} else {
|
|
MOZ_ASSERT(funcName == eCSSKeyword_fit_content,
|
|
"Expected minmax() or fit-content() function");
|
|
return false; // fit-content() is not a <fixed-size>
|
|
}
|
|
} else if (!list->mValue.IsLengthPercentCalcUnit()) {
|
|
return false;
|
|
}
|
|
list = list->mNext; // skip line names
|
|
}
|
|
}
|
|
} else {
|
|
UngetToken();
|
|
|
|
// Not a repeat() function; try to parse <track-size> | <fixed-size>.
|
|
nsCSSValue trackSize;
|
|
GridTrackSizeFlags flags = haveRepeatAuto
|
|
? GridTrackSizeFlags::eFixedTrackSize
|
|
: GridTrackSizeFlags::eDefaultTrackSize;
|
|
CSSParseResult result = ParseGridTrackSize(trackSize, flags);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
}
|
|
if (result == CSSParseResult::NotFound) {
|
|
// What we've parsed so far is a valid <track-list>
|
|
// (modulo the "at least one <track-size>" check below.)
|
|
// Stop here.
|
|
break;
|
|
}
|
|
item->mNext = new nsCSSValueList;
|
|
item = item->mNext;
|
|
item->mValue = trackSize;
|
|
|
|
item->mNext = new nsCSSValueList;
|
|
item = item->mNext;
|
|
}
|
|
if (ParseGridLineNames(item->mValue) == CSSParseResult::Error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Require at least one <track-size>.
|
|
if (item == firstLineNamesItem) {
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT(aValue.GetListValue() &&
|
|
aValue.GetListValue()->mNext &&
|
|
aValue.GetListValue()->mNext->mNext,
|
|
"<track-list> should have a minimum length of 3");
|
|
return true;
|
|
}
|
|
|
|
// Takes ownership of |aSecond|
|
|
static void
|
|
ConcatLineNames(nsCSSValue& aFirst, nsCSSValue& aSecond)
|
|
{
|
|
if (aSecond.GetUnit() == eCSSUnit_Null) {
|
|
// Nothing to do.
|
|
return;
|
|
}
|
|
if (aFirst.GetUnit() == eCSSUnit_Null) {
|
|
// Empty or omitted <line-names>. Replace it.
|
|
aFirst = aSecond;
|
|
return;
|
|
}
|
|
|
|
// Join the two <line-names> lists.
|
|
nsCSSValueList* source = aSecond.GetListValue();
|
|
nsCSSValueList* target = aFirst.GetListValue();
|
|
// Find the end:
|
|
while (target->mNext) {
|
|
target = target->mNext;
|
|
}
|
|
// Copy the first name. We can't take ownership of it
|
|
// as it'll be destroyed when |aSecond| goes out of scope.
|
|
target->mNext = new nsCSSValueList;
|
|
target = target->mNext;
|
|
target->mValue = source->mValue;
|
|
// Move the rest of the linked list.
|
|
target->mNext = source->mNext;
|
|
source->mNext = nullptr;
|
|
}
|
|
|
|
// Assuming the 'repeat(' function token has already been consumed,
|
|
// parse "repeat( <positive-integer> | auto-fill | auto-fit ,"
|
|
// (or "repeat( <positive-integer> | auto-fill ," when aForSubgrid is true)
|
|
// and stop after the comma. Return true when parsing succeeds,
|
|
// with aRepetitions set to the number of repetitions and aRepeatAutoEnum set
|
|
// to an enum value for auto-fill | auto-fit (it's not set at all when
|
|
// <positive-integer> was parsed).
|
|
bool
|
|
CSSParserImpl::ParseGridTrackRepeatIntro(bool aForSubgrid,
|
|
int32_t* aRepetitions,
|
|
Maybe<int32_t>* aRepeatAutoEnum)
|
|
{
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
if (mToken.mType == eCSSToken_Ident) {
|
|
if (mToken.mIdent.LowerCaseEqualsLiteral("auto-fill")) {
|
|
aRepeatAutoEnum->emplace(NS_STYLE_GRID_REPEAT_AUTO_FILL);
|
|
} else if (!aForSubgrid &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("auto-fit")) {
|
|
aRepeatAutoEnum->emplace(NS_STYLE_GRID_REPEAT_AUTO_FIT);
|
|
} else {
|
|
return false;
|
|
}
|
|
*aRepetitions = 1;
|
|
} else if (mToken.mType == eCSSToken_Number) {
|
|
if (!(mToken.mIntegerValid &&
|
|
mToken.mInteger > 0)) {
|
|
return false;
|
|
}
|
|
*aRepetitions = std::min(mToken.mInteger, GRID_TEMPLATE_MAX_REPETITIONS);
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
if (!ExpectSymbol(',', true)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Assuming the 'repeat(' function token has already been consumed,
|
|
// parse the rest of
|
|
// repeat( <positive-integer> | auto-fill | auto-fit ,
|
|
// [ <line-names>? <track-size> ]+ <line-names>? )
|
|
// Append to the linked list whose end is given by |aTailPtr|,
|
|
// and update |aTailPtr| to point to the new end of the list.
|
|
// Note: only one <track-size> is allowed for auto-fill/fit
|
|
bool
|
|
CSSParserImpl::ParseGridTrackListRepeat(nsCSSValueList** aTailPtr)
|
|
{
|
|
int32_t repetitions;
|
|
Maybe<int32_t> repeatAutoEnum;
|
|
if (!ParseGridTrackRepeatIntro(false, &repetitions, &repeatAutoEnum)) {
|
|
return false;
|
|
}
|
|
|
|
// Parse [ <line-names>? <track-size> ]+ <line-names>?
|
|
// but keep the first and last <line-names> separate
|
|
// because they'll need to be joined.
|
|
// http://dev.w3.org/csswg/css-grid/#repeat-notation
|
|
nsCSSValue firstLineNames;
|
|
nsCSSValue trackSize;
|
|
nsCSSValue lastLineNames;
|
|
// Optional
|
|
if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error) {
|
|
return false;
|
|
}
|
|
// Required
|
|
GridTrackSizeFlags flags = repeatAutoEnum.isSome()
|
|
? GridTrackSizeFlags::eFixedTrackSize
|
|
: GridTrackSizeFlags::eDefaultTrackSize;
|
|
if (ParseGridTrackSize(trackSize, flags) != CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
// Use nsAutoPtr to free the list in case of early return.
|
|
nsAutoPtr<nsCSSValueList> firstTrackSizeItemAuto(new nsCSSValueList);
|
|
firstTrackSizeItemAuto->mValue = trackSize;
|
|
|
|
nsCSSValueList* item = firstTrackSizeItemAuto;
|
|
for (;;) {
|
|
// Optional
|
|
if (ParseGridLineNames(lastLineNames) == CSSParseResult::Error) {
|
|
return false;
|
|
}
|
|
|
|
if (ExpectSymbol(')', true)) {
|
|
break;
|
|
}
|
|
|
|
// <auto-repeat> only accepts a single track size:
|
|
// <line-names>? <fixed-size> <line-names>?
|
|
if (repeatAutoEnum.isSome()) {
|
|
REPORT_UNEXPECTED(PEMoreThanOneGridRepeatTrackSize);
|
|
return false;
|
|
}
|
|
|
|
// Required
|
|
if (ParseGridTrackSize(trackSize) != CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
|
|
item->mNext = new nsCSSValueList;
|
|
item = item->mNext;
|
|
item->mValue = lastLineNames;
|
|
// Do not append to this list at the next iteration.
|
|
lastLineNames.Reset();
|
|
|
|
item->mNext = new nsCSSValueList;
|
|
item = item->mNext;
|
|
item->mValue = trackSize;
|
|
}
|
|
nsCSSValueList* lastTrackSizeItem = item;
|
|
|
|
// [ <line-names>? <track-size> ]+ <line-names>? is now parsed into:
|
|
// * firstLineNames: the first <line-names>
|
|
// * a linked list of odd length >= 1, from firstTrackSizeItem
|
|
// (the first <track-size>) to lastTrackSizeItem (the last),
|
|
// with the <line-names> sublists in between
|
|
// * lastLineNames: the last <line-names>
|
|
|
|
if (repeatAutoEnum.isSome()) {
|
|
// Instead of hooking up this list into the flat track/name list as usual,
|
|
// we create a pair(Int, List) where the first value is the auto-fill/fit
|
|
// keyword and the second is the list to repeat. There are three items
|
|
// in this list, the first is the list of line names before the track size,
|
|
// the second item is the track size, and the last item is the list of line
|
|
// names after the track size. Note that the line names are NOT merged
|
|
// with any line names before/after the repeat() itself.
|
|
nsCSSValue listValue;
|
|
nsCSSValueList* list = listValue.SetListValue();
|
|
list->mValue = firstLineNames;
|
|
list = list->mNext = new nsCSSValueList;
|
|
list->mValue = trackSize;
|
|
list = list->mNext = new nsCSSValueList;
|
|
list->mValue = lastLineNames;
|
|
nsCSSValue kwd;
|
|
kwd.SetIntValue(repeatAutoEnum.value(), eCSSUnit_Enumerated);
|
|
*aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList;
|
|
(*aTailPtr)->mValue.SetPairValue(kwd, listValue);
|
|
// Append an empty list since the caller expects that to represent the names
|
|
// that follows the repeat() function.
|
|
*aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList;
|
|
return true;
|
|
}
|
|
|
|
// Join the last and first <line-names> (in that order.)
|
|
// For example, repeat(3, (a) 100px (b) 200px (c)) results in
|
|
// (a) 100px (b) 200px (c a) 100px (b) 200px (c a) 100px (b) 200px (c)
|
|
// This is (c a).
|
|
// Make deep copies: the originals will be moved.
|
|
nsCSSValue joinerLineNames;
|
|
{
|
|
nsCSSValueList* target = nullptr;
|
|
if (lastLineNames.GetUnit() != eCSSUnit_Null) {
|
|
target = joinerLineNames.SetListValue();
|
|
nsCSSValueList* source = lastLineNames.GetListValue();
|
|
for (;;) {
|
|
target->mValue = source->mValue;
|
|
source = source->mNext;
|
|
if (!source) {
|
|
break;
|
|
}
|
|
target->mNext = new nsCSSValueList;
|
|
target = target->mNext;
|
|
}
|
|
}
|
|
|
|
if (firstLineNames.GetUnit() != eCSSUnit_Null) {
|
|
if (target) {
|
|
target->mNext = new nsCSSValueList;
|
|
target = target->mNext;
|
|
} else {
|
|
target = joinerLineNames.SetListValue();
|
|
}
|
|
nsCSSValueList* source = firstLineNames.GetListValue();
|
|
for (;;) {
|
|
target->mValue = source->mValue;
|
|
source = source->mNext;
|
|
if (!source) {
|
|
break;
|
|
}
|
|
target->mNext = new nsCSSValueList;
|
|
target = target->mNext;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Join our first <line-names> with the one before repeat().
|
|
// (a) repeat(1, (b) 20px) expands to (a b) 20px
|
|
nsCSSValueList* previousItemBeforeRepeat = *aTailPtr;
|
|
ConcatLineNames(previousItemBeforeRepeat->mValue, firstLineNames);
|
|
|
|
// Move our linked list
|
|
// (first to last <track-size>, with the <line-names> sublists in between).
|
|
// This is the first repetition.
|
|
NS_ASSERTION(previousItemBeforeRepeat->mNext == nullptr,
|
|
"Expected the end of a linked list");
|
|
previousItemBeforeRepeat->mNext = firstTrackSizeItemAuto.forget();
|
|
nsCSSValueList* firstTrackSizeItem = previousItemBeforeRepeat->mNext;
|
|
nsCSSValueList* tail = lastTrackSizeItem;
|
|
|
|
// Repeat |repetitions - 1| more times:
|
|
// * the joiner <line-names>
|
|
// * the linked list
|
|
// (first to last <track-size>, with the <line-names> sublists in between)
|
|
MOZ_ASSERT(repetitions > 0, "Expected positive repetitions");
|
|
while (--repetitions) {
|
|
tail->mNext = new nsCSSValueList;
|
|
tail = tail->mNext;
|
|
tail->mValue = joinerLineNames;
|
|
|
|
nsCSSValueList* repeatedItem = firstTrackSizeItem;
|
|
for (;;) {
|
|
tail->mNext = new nsCSSValueList;
|
|
tail = tail->mNext;
|
|
tail->mValue = repeatedItem->mValue;
|
|
if (repeatedItem == lastTrackSizeItem) {
|
|
break;
|
|
}
|
|
repeatedItem = repeatedItem->mNext;
|
|
}
|
|
}
|
|
|
|
// Finally, move our last <line-names>.
|
|
// Any <line-names> immediately after repeat() will append to it.
|
|
tail->mNext = new nsCSSValueList;
|
|
tail = tail->mNext;
|
|
tail->mValue = lastLineNames;
|
|
|
|
*aTailPtr = tail;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseGridTrackList(nsCSSPropertyID aPropID,
|
|
GridTrackListFlags aFlags)
|
|
{
|
|
nsCSSValue value;
|
|
nsCSSValue firstLineNames;
|
|
if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error ||
|
|
!ParseGridTrackListWithFirstLineNames(value, firstLineNames, aFlags)) {
|
|
return false;
|
|
}
|
|
AppendValue(aPropID, value);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseGridTemplateColumnsRows(nsCSSPropertyID aPropID)
|
|
{
|
|
nsCSSValue value;
|
|
if (ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) {
|
|
AppendValue(aPropID, value);
|
|
return true;
|
|
}
|
|
|
|
nsSubstring* ident = NextIdent();
|
|
if (ident) {
|
|
if (ident->LowerCaseEqualsLiteral("subgrid")) {
|
|
if (!nsLayoutUtils::IsGridTemplateSubgridValueEnabled()) {
|
|
REPORT_UNEXPECTED(PESubgridNotSupported);
|
|
return false;
|
|
}
|
|
if (!ParseOptionalLineNameListAfterSubgrid(value)) {
|
|
return false;
|
|
}
|
|
AppendValue(aPropID, value);
|
|
return true;
|
|
}
|
|
UngetToken();
|
|
}
|
|
|
|
return ParseGridTrackList(aPropID);
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseGridTemplateAreasLine(const nsAutoString& aInput,
|
|
css::GridTemplateAreasValue* aAreas,
|
|
nsDataHashtable<nsStringHashKey, uint32_t>& aAreaIndices)
|
|
{
|
|
aAreas->mTemplates.AppendElement(mToken.mIdent);
|
|
|
|
nsCSSGridTemplateAreaScanner scanner(aInput);
|
|
nsCSSGridTemplateAreaToken token;
|
|
css::GridNamedArea* currentArea = nullptr;
|
|
uint32_t row = aAreas->NRows();
|
|
// Column numbers starts at 1, but we might not have any, eg
|
|
// grid-template-areas:""; which will result in mNColumns == 0.
|
|
uint32_t column = 0;
|
|
while (scanner.Next(token)) {
|
|
++column;
|
|
if (token.isTrash) {
|
|
return false;
|
|
}
|
|
if (currentArea) {
|
|
if (token.mName == currentArea->mName) {
|
|
if (currentArea->mRowStart == row) {
|
|
// Next column in the first row of this named area.
|
|
currentArea->mColumnEnd++;
|
|
}
|
|
continue;
|
|
}
|
|
// We're exiting |currentArea|, so currentArea is ending at |column|.
|
|
// Make sure that this is consistent with currentArea on previous rows:
|
|
if (currentArea->mColumnEnd != column) {
|
|
NS_ASSERTION(currentArea->mRowStart != row,
|
|
"Inconsistent column end for the first row of a named area.");
|
|
// Not a rectangle
|
|
return false;
|
|
}
|
|
currentArea = nullptr;
|
|
}
|
|
if (!token.mName.IsEmpty()) {
|
|
// Named cell that doesn't have a cell with the same name on its left.
|
|
|
|
// Check if this is the continuation of an existing named area:
|
|
uint32_t index;
|
|
if (aAreaIndices.Get(token.mName, &index)) {
|
|
MOZ_ASSERT(index < aAreas->mNamedAreas.Length(),
|
|
"Invalid aAreaIndices hash table");
|
|
currentArea = &aAreas->mNamedAreas[index];
|
|
if (currentArea->mColumnStart != column ||
|
|
currentArea->mRowEnd != row) {
|
|
// Existing named area, but not forming a rectangle
|
|
return false;
|
|
}
|
|
// Next row of an existing named area
|
|
currentArea->mRowEnd++;
|
|
} else {
|
|
// New named area
|
|
aAreaIndices.Put(token.mName, aAreas->mNamedAreas.Length());
|
|
currentArea = aAreas->mNamedAreas.AppendElement();
|
|
currentArea->mName = token.mName;
|
|
// For column or row N (starting at 1),
|
|
// the start line is N, the end line is N + 1
|
|
currentArea->mColumnStart = column;
|
|
currentArea->mColumnEnd = column + 1;
|
|
currentArea->mRowStart = row;
|
|
currentArea->mRowEnd = row + 1;
|
|
}
|
|
}
|
|
}
|
|
if (currentArea && currentArea->mColumnEnd != column + 1) {
|
|
NS_ASSERTION(currentArea->mRowStart != row,
|
|
"Inconsistent column end for the first row of a named area.");
|
|
// Not a rectangle
|
|
return false;
|
|
}
|
|
|
|
// On the first row, set the number of columns
|
|
// that grid-template-areas contributes to the explicit grid.
|
|
// On other rows, check that the number of columns is consistent
|
|
// between rows.
|
|
if (row == 1) {
|
|
aAreas->mNColumns = column;
|
|
} else if (aAreas->mNColumns != column) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseGridTemplateAreas()
|
|
{
|
|
nsCSSValue value;
|
|
if (ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) {
|
|
AppendValue(eCSSProperty_grid_template_areas, value);
|
|
return true;
|
|
}
|
|
|
|
RefPtr<css::GridTemplateAreasValue> areas =
|
|
new css::GridTemplateAreasValue();
|
|
nsDataHashtable<nsStringHashKey, uint32_t> areaIndices;
|
|
for (;;) {
|
|
if (!GetToken(true)) {
|
|
break;
|
|
}
|
|
if (eCSSToken_String != mToken.mType) {
|
|
UngetToken();
|
|
break;
|
|
}
|
|
if (!ParseGridTemplateAreasLine(mToken.mIdent, areas, areaIndices)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (areas->NRows() == 0) {
|
|
return false;
|
|
}
|
|
|
|
AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(areas));
|
|
return true;
|
|
}
|
|
|
|
// [ auto-flow && dense? ] <'grid-auto-columns'>? |
|
|
// <'grid-template-columns'>
|
|
bool
|
|
CSSParserImpl::ParseGridTemplateColumnsOrAutoFlow(bool aForGridShorthand)
|
|
{
|
|
if (aForGridShorthand) {
|
|
auto res = ParseGridShorthandAutoProps(NS_STYLE_GRID_AUTO_FLOW_COLUMN);
|
|
if (res == CSSParseResult::Error) {
|
|
return false;
|
|
}
|
|
if (res == CSSParseResult::Ok) {
|
|
nsCSSValue value(eCSSUnit_None);
|
|
AppendValue(eCSSProperty_grid_template_columns, value);
|
|
return true;
|
|
}
|
|
}
|
|
return ParseGridTemplateColumnsRows(eCSSProperty_grid_template_columns);
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseGridTemplate(bool aForGridShorthand)
|
|
{
|
|
// none |
|
|
// subgrid |
|
|
// <'grid-template-rows'> / <'grid-template-columns'> |
|
|
// [ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list>]?
|
|
// or additionally when aForGridShorthand is true:
|
|
// <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>?
|
|
nsCSSValue value;
|
|
if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
|
|
AppendValue(eCSSProperty_grid_template_areas, value);
|
|
AppendValue(eCSSProperty_grid_template_rows, value);
|
|
AppendValue(eCSSProperty_grid_template_columns, value);
|
|
return true;
|
|
}
|
|
|
|
// 'none' can appear either by itself,
|
|
// or as the beginning of <'grid-template-rows'> / <'grid-template-columns'>
|
|
if (ParseSingleTokenVariant(value, VARIANT_NONE, nullptr)) {
|
|
AppendValue(eCSSProperty_grid_template_rows, value);
|
|
AppendValue(eCSSProperty_grid_template_areas, value);
|
|
if (ExpectSymbol('/', true)) {
|
|
return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand);
|
|
}
|
|
AppendValue(eCSSProperty_grid_template_columns, value);
|
|
return true;
|
|
}
|
|
|
|
// 'subgrid' can appear either by itself,
|
|
// or as the beginning of <'grid-template-rows'> / <'grid-template-columns'>
|
|
nsSubstring* ident = NextIdent();
|
|
if (ident) {
|
|
if (ident->LowerCaseEqualsLiteral("subgrid")) {
|
|
if (!nsLayoutUtils::IsGridTemplateSubgridValueEnabled()) {
|
|
REPORT_UNEXPECTED(PESubgridNotSupported);
|
|
return false;
|
|
}
|
|
if (!ParseOptionalLineNameListAfterSubgrid(value)) {
|
|
return false;
|
|
}
|
|
AppendValue(eCSSProperty_grid_template_rows, value);
|
|
AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(eCSSUnit_None));
|
|
if (ExpectSymbol('/', true)) {
|
|
return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand);
|
|
}
|
|
if (value.GetListValue()->mNext) {
|
|
// Non-empty <line-name-list> after 'subgrid'.
|
|
// This is only valid as part of <'grid-template-rows'>,
|
|
// which must be followed by a slash.
|
|
return false;
|
|
}
|
|
// 'subgrid' by itself sets both grid-template-rows/columns.
|
|
AppendValue(eCSSProperty_grid_template_columns, value);
|
|
return true;
|
|
}
|
|
UngetToken();
|
|
}
|
|
|
|
// [ <line-names>? ] here is ambiguous:
|
|
// it can be either the start of a <track-list> (in a <'grid-template-rows'>),
|
|
// or the start of [ <line-names>? <string> <track-size>? <line-names>? ]+
|
|
nsCSSValue firstLineNames;
|
|
if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error ||
|
|
!GetToken(true)) {
|
|
return false;
|
|
}
|
|
if (mToken.mType == eCSSToken_String) {
|
|
// It's the [ <line-names>? <string> <track-size>? <line-names>? ]+ case.
|
|
if (!ParseGridTemplateAfterString(firstLineNames)) {
|
|
return false;
|
|
}
|
|
// Parse an optional [ / <explicit-track-list> ] as the columns value.
|
|
if (ExpectSymbol('/', true)) {
|
|
return ParseGridTrackList(eCSSProperty_grid_template_columns,
|
|
GridTrackListFlags::eExplicitTrackList);
|
|
}
|
|
value.SetNoneValue(); // absent means 'none'
|
|
AppendValue(eCSSProperty_grid_template_columns, value);
|
|
return true;
|
|
}
|
|
UngetToken();
|
|
|
|
// Finish parsing <'grid-template-rows'> with the |firstLineNames| we have,
|
|
// and then parse a mandatory [ / <'grid-template-columns'> ].
|
|
if (!ParseGridTrackListWithFirstLineNames(value, firstLineNames) ||
|
|
!ExpectSymbol('/', true)) {
|
|
return false;
|
|
}
|
|
AppendValue(eCSSProperty_grid_template_rows, value);
|
|
value.SetNoneValue();
|
|
AppendValue(eCSSProperty_grid_template_areas, value);
|
|
return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand);
|
|
}
|
|
|
|
// Helper for parsing the 'grid-template' shorthand:
|
|
// Parse [ <line-names>? <string> <track-size>? <line-names>? ]+
|
|
// with a <line-names>? already consumed, stored in |aFirstLineNames|,
|
|
// and the current token a <string>
|
|
bool
|
|
CSSParserImpl::ParseGridTemplateAfterString(const nsCSSValue& aFirstLineNames)
|
|
{
|
|
MOZ_ASSERT(mToken.mType == eCSSToken_String,
|
|
"ParseGridTemplateAfterString called with a non-string token");
|
|
|
|
nsCSSValue rowsValue;
|
|
RefPtr<css::GridTemplateAreasValue> areas =
|
|
new css::GridTemplateAreasValue();
|
|
nsDataHashtable<nsStringHashKey, uint32_t> areaIndices;
|
|
nsCSSValueList* rowsItem = rowsValue.SetListValue();
|
|
rowsItem->mValue = aFirstLineNames;
|
|
|
|
for (;;) {
|
|
if (!ParseGridTemplateAreasLine(mToken.mIdent, areas, areaIndices)) {
|
|
return false;
|
|
}
|
|
|
|
rowsItem->mNext = new nsCSSValueList;
|
|
rowsItem = rowsItem->mNext;
|
|
CSSParseResult result = ParseGridTrackSize(rowsItem->mValue);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
}
|
|
if (result == CSSParseResult::NotFound) {
|
|
rowsItem->mValue.SetAutoValue();
|
|
}
|
|
|
|
rowsItem->mNext = new nsCSSValueList;
|
|
rowsItem = rowsItem->mNext;
|
|
result = ParseGridLineNames(rowsItem->mValue);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
}
|
|
if (result == CSSParseResult::Ok) {
|
|
// Append to the same list as the previous call to ParseGridLineNames.
|
|
result = ParseGridLineNames(rowsItem->mValue);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
}
|
|
if (result == CSSParseResult::Ok) {
|
|
// Parsed <line-name> twice.
|
|
// The property value can not end here, we expect a string next.
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
if (eCSSToken_String != mToken.mType) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Did not find a <line-names>.
|
|
// Next, we expect either a string or the end of the property value.
|
|
if (!GetToken(true)) {
|
|
break;
|
|
}
|
|
if (eCSSToken_String != mToken.mType) {
|
|
UngetToken();
|
|
break;
|
|
}
|
|
}
|
|
|
|
AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(areas));
|
|
AppendValue(eCSSProperty_grid_template_rows, rowsValue);
|
|
return true;
|
|
}
|
|
|
|
// <'grid-template'> |
|
|
// <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? |
|
|
// [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
|
|
bool
|
|
CSSParserImpl::ParseGrid()
|
|
{
|
|
nsCSSValue value;
|
|
if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
|
|
for (const nsCSSPropertyID* subprops =
|
|
nsCSSProps::SubpropertyEntryFor(eCSSProperty_grid);
|
|
*subprops != eCSSProperty_UNKNOWN; ++subprops) {
|
|
AppendValue(*subprops, value);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// https://drafts.csswg.org/css-grid/#grid-shorthand
|
|
// "Also, the gutter properties are reset by this shorthand,
|
|
// even though they can't be set by it."
|
|
value.SetFloatValue(0.0f, eCSSUnit_Pixel);
|
|
AppendValue(eCSSProperty_grid_row_gap, value);
|
|
AppendValue(eCSSProperty_grid_column_gap, value);
|
|
|
|
// [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
|
|
auto res = ParseGridShorthandAutoProps(NS_STYLE_GRID_AUTO_FLOW_ROW);
|
|
if (res == CSSParseResult::Error) {
|
|
return false;
|
|
}
|
|
if (res == CSSParseResult::Ok) {
|
|
value.SetAutoValue();
|
|
AppendValue(eCSSProperty_grid_auto_columns, value);
|
|
nsCSSValue none(eCSSUnit_None);
|
|
AppendValue(eCSSProperty_grid_template_areas, none);
|
|
AppendValue(eCSSProperty_grid_template_rows, none);
|
|
if (!ExpectSymbol('/', true)) {
|
|
return false;
|
|
}
|
|
return ParseGridTemplateColumnsRows(eCSSProperty_grid_template_columns);
|
|
}
|
|
|
|
// Set remaining subproperties that might not be set by ParseGridTemplate to
|
|
// their initial values and then parse <'grid-template'> |
|
|
// <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? .
|
|
value.SetIntValue(NS_STYLE_GRID_AUTO_FLOW_ROW, eCSSUnit_Enumerated);
|
|
AppendValue(eCSSProperty_grid_auto_flow, value);
|
|
value.SetAutoValue();
|
|
AppendValue(eCSSProperty_grid_auto_rows, value);
|
|
AppendValue(eCSSProperty_grid_auto_columns, value);
|
|
return ParseGridTemplate(true);
|
|
}
|
|
|
|
// Parse [ auto-flow && dense? ] <'grid-auto-[rows|columns]'>? for the 'grid'
|
|
// shorthand. If aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW then we're
|
|
// parsing row values, otherwise column values.
|
|
CSSParseResult
|
|
CSSParserImpl::ParseGridShorthandAutoProps(int32_t aAutoFlowAxis)
|
|
{
|
|
MOZ_ASSERT(aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW ||
|
|
aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_COLUMN);
|
|
if (!GetToken(true)) {
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
// [ auto-flow && dense? ]
|
|
int32_t autoFlowValue = 0;
|
|
if (mToken.mType == eCSSToken_Ident) {
|
|
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
|
|
if (keyword == eCSSKeyword_auto_flow) {
|
|
autoFlowValue = aAutoFlowAxis;
|
|
if (GetToken(true)) {
|
|
if (mToken.mType == eCSSToken_Ident &&
|
|
nsCSSKeywords::LookupKeyword(mToken.mIdent) == eCSSKeyword_dense) {
|
|
autoFlowValue |= NS_STYLE_GRID_AUTO_FLOW_DENSE;
|
|
} else {
|
|
UngetToken();
|
|
}
|
|
}
|
|
} else if (keyword == eCSSKeyword_dense) {
|
|
if (!GetToken(true)) {
|
|
return CSSParseResult::Error;
|
|
}
|
|
if (mToken.mType != eCSSToken_Ident ||
|
|
nsCSSKeywords::LookupKeyword(mToken.mIdent) != eCSSKeyword_auto_flow) {
|
|
UngetToken();
|
|
return CSSParseResult::Error;
|
|
}
|
|
autoFlowValue = aAutoFlowAxis | NS_STYLE_GRID_AUTO_FLOW_DENSE;
|
|
}
|
|
}
|
|
if (autoFlowValue) {
|
|
nsCSSValue value;
|
|
value.SetIntValue(autoFlowValue, eCSSUnit_Enumerated);
|
|
AppendValue(eCSSProperty_grid_auto_flow, value);
|
|
} else {
|
|
UngetToken();
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
|
|
// <'grid-auto-[rows|columns]'>?
|
|
nsCSSValue autoTrackValue;
|
|
CSSParseResult result = ParseGridTrackSize(autoTrackValue);
|
|
if (result == CSSParseResult::Error) {
|
|
return result;
|
|
}
|
|
if (result == CSSParseResult::NotFound) {
|
|
autoTrackValue.SetAutoValue();
|
|
}
|
|
AppendValue(aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW ?
|
|
eCSSProperty_grid_auto_rows : eCSSProperty_grid_auto_columns,
|
|
autoTrackValue);
|
|
return CSSParseResult::Ok;
|
|
}
|
|
|
|
// Parse a <grid-line>.
|
|
// If successful, set aValue to eCSSUnit_Auto,
|
|
// or a eCSSUnit_List containing, in that order:
|
|
//
|
|
// * An optional eCSSUnit_Enumerated marking a "span" keyword.
|
|
// * An optional eCSSUnit_Integer
|
|
// * An optional eCSSUnit_Ident
|
|
//
|
|
// At least one of eCSSUnit_Integer or eCSSUnit_Ident is present.
|
|
bool
|
|
CSSParserImpl::ParseGridLine(nsCSSValue& aValue)
|
|
{
|
|
// <grid-line> =
|
|
// auto |
|
|
// <custom-ident> |
|
|
// [ <integer> && <custom-ident>? ] |
|
|
// [ span && [ <integer> || <custom-ident> ] ]
|
|
//
|
|
// Syntactically, this simplifies to:
|
|
//
|
|
// <grid-line> =
|
|
// auto |
|
|
// [ span? && [ <integer> || <custom-ident> ] ]
|
|
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_AUTO, nullptr)) {
|
|
return true;
|
|
}
|
|
|
|
bool hasSpan = false;
|
|
bool hasIdent = false;
|
|
Maybe<int32_t> integer;
|
|
nsCSSValue ident;
|
|
|
|
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 (!ParseEnum(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 (!ParseEnum(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 (!ParseEnum(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 (!ParseEnum(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;
|
|
}
|
|
|
|
// <color-stop> : <color> [ <percentage> | <length> ]?
|
|
bool
|
|
CSSParserImpl::ParseColorStop(nsCSSValueGradient* aGradient)
|
|
{
|
|
nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement();
|
|
CSSParseResult result = ParseVariant(stop->mColor, VARIANT_COLOR, nullptr);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
} else if (result == CSSParseResult::NotFound) {
|
|
stop->mIsInterpolationHint = true;
|
|
}
|
|
|
|
// Stop positions do not have to fall between the starting-point and
|
|
// ending-point, so we don't use ParseNonNegativeVariant.
|
|
result = ParseVariant(stop->mLocation, VARIANT_LP | VARIANT_CALC, nullptr);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
} else if (result == CSSParseResult::NotFound) {
|
|
if (stop->mIsInterpolationHint) {
|
|
return false;
|
|
}
|
|
stop->mLocation.SetNoneValue();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Helper for ParseLinearGradient -- returns true iff aPosition represents a
|
|
// box-position value which was parsed with only edge keywords.
|
|
// e.g. "left top", or "bottom", but not "left 10px"
|
|
//
|
|
// (NOTE: Even though callers may want to exclude explicit "center", we still
|
|
// need to allow for _CENTER here, because omitted position-values (e.g. the
|
|
// x-component of a value like "top") will have been parsed as being *implicit*
|
|
// center. The correct way to disallow *explicit* center is to pass "false" for
|
|
// ParseBoxPositionValues()'s "aAllowExplicitCenter" parameter, before you
|
|
// call this function.)
|
|
static bool
|
|
IsBoxPositionStrictlyEdgeKeywords(nsCSSValuePair& aPosition)
|
|
{
|
|
const nsCSSValue& xValue = aPosition.mXValue;
|
|
const nsCSSValue& yValue = aPosition.mYValue;
|
|
return (xValue.GetUnit() == eCSSUnit_Enumerated &&
|
|
(xValue.GetIntValue() & (NS_STYLE_IMAGELAYER_POSITION_LEFT |
|
|
NS_STYLE_IMAGELAYER_POSITION_CENTER |
|
|
NS_STYLE_IMAGELAYER_POSITION_RIGHT)) &&
|
|
yValue.GetUnit() == eCSSUnit_Enumerated &&
|
|
(yValue.GetIntValue() & (NS_STYLE_IMAGELAYER_POSITION_TOP |
|
|
NS_STYLE_IMAGELAYER_POSITION_CENTER |
|
|
NS_STYLE_IMAGELAYER_POSITION_BOTTOM)));
|
|
}
|
|
|
|
// <gradient>
|
|
// : linear-gradient( <linear-gradient-line>? <color-stops> ')'
|
|
// | radial-gradient( <radial-gradient-line>? <color-stops> ')'
|
|
//
|
|
// <linear-gradient-line> : [ to [left | right] || [top | bottom] ] ,
|
|
// | <legacy-gradient-line>
|
|
// <radial-gradient-line> : [ <shape> || <size> ] [ at <position> ]? ,
|
|
// | [ at <position> ] ,
|
|
// | <legacy-gradient-line>? <legacy-shape-size>?
|
|
// <shape> : circle | ellipse
|
|
// <size> : closest-side | closest-corner | farthest-side | farthest-corner
|
|
// | <length> | [<length> | <percentage>]{2}
|
|
//
|
|
// <legacy-gradient-line> : [ <position> || <angle>] ,
|
|
//
|
|
// <legacy-shape-size> : [ <shape> || <legacy-size> ] ,
|
|
// <legacy-size> : closest-side | closest-corner | farthest-side
|
|
// | farthest-corner | contain | cover
|
|
//
|
|
// <color-stops> : <color-stop> , <color-stop> [, <color-stop>]*
|
|
bool
|
|
CSSParserImpl::ParseLinearGradient(nsCSSValue& aValue,
|
|
uint8_t aFlags)
|
|
{
|
|
RefPtr<nsCSSValueGradient> cssGradient
|
|
= new nsCSSValueGradient(false, aFlags & eGradient_Repeating);
|
|
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
|
|
// Check for "to" syntax (but not if parsing a -webkit-linear-gradient)
|
|
if (!(aFlags & eGradient_WebkitLegacy) &&
|
|
mToken.mType == eCSSToken_Ident &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("to")) {
|
|
|
|
// "to" syntax doesn't allow explicit "center"
|
|
if (!ParseBoxPositionValues(cssGradient->mBgPos, false, false)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
// [ to [left | right] || [top | bottom] ] ,
|
|
if (!IsBoxPositionStrictlyEdgeKeywords(cssGradient->mBgPos)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
if (!ExpectSymbol(',', true)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
return ParseGradientColorStops(cssGradient, aValue);
|
|
}
|
|
|
|
if (!(aFlags & eGradient_AnyLegacy)) {
|
|
// We're parsing an unprefixed linear-gradient, and we tried & failed to
|
|
// parse a 'to' token above. Put the token back & try to re-parse our
|
|
// expression as <angle>? <color-stop-list>
|
|
UngetToken();
|
|
|
|
// <angle> ,
|
|
if (ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr) &&
|
|
!ExpectSymbol(',', true)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
return ParseGradientColorStops(cssGradient, aValue);
|
|
}
|
|
|
|
// If we get here, we're parsing a prefixed linear-gradient expression. Put
|
|
// back the first token (which we may have checked for "to" above) and try to
|
|
// parse expression as <legacy-gradient-line>? <color-stop-list>
|
|
bool haveGradientLine = IsLegacyGradientLine(mToken.mType, mToken.mIdent);
|
|
UngetToken();
|
|
|
|
if (haveGradientLine) {
|
|
// Parse a <legacy-gradient-line>
|
|
cssGradient->mIsLegacySyntax = true;
|
|
// In -webkit-linear-gradient expressions (handled below), we need to accept
|
|
// unitless 0 for angles, to match WebKit/Blink.
|
|
int32_t angleFlags = (aFlags & eGradient_WebkitLegacy) ?
|
|
VARIANT_ANGLE | VARIANT_ZERO_ANGLE :
|
|
VARIANT_ANGLE;
|
|
|
|
bool haveAngle =
|
|
ParseSingleTokenVariant(cssGradient->mAngle, angleFlags, nullptr);
|
|
|
|
// If we got an angle, we might now have a comma, ending the gradient-line.
|
|
bool haveAngleComma = haveAngle && ExpectSymbol(',', true);
|
|
|
|
// If we're webkit-prefixed & didn't get an angle,
|
|
// OR if we're moz-prefixed & didn't get an angle+comma,
|
|
// then proceed to parse a box-position.
|
|
if (((aFlags & eGradient_WebkitLegacy) && !haveAngle) ||
|
|
((aFlags & eGradient_MozLegacy) && !haveAngleComma)) {
|
|
// (Note: 3rd arg controls whether the "center" keyword is allowed.
|
|
// -moz-linear-gradient allows it; -webkit-linear-gradient does not.)
|
|
if (!ParseBoxPositionValues(cssGradient->mBgPos, false,
|
|
(aFlags & eGradient_MozLegacy))) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
// -webkit-linear-gradient only supports edge keywords here.
|
|
if ((aFlags & eGradient_WebkitLegacy) &&
|
|
!IsBoxPositionStrictlyEdgeKeywords(cssGradient->mBgPos)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
if (!ExpectSymbol(',', true) &&
|
|
// If we didn't already get an angle, and we're not -webkit prefixed,
|
|
// we can parse an angle+comma now. Otherwise it's an error.
|
|
(haveAngle ||
|
|
(aFlags & eGradient_WebkitLegacy) ||
|
|
!ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE,
|
|
nullptr) ||
|
|
// now we better have a comma
|
|
!ExpectSymbol(',', true))) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ParseGradientColorStops(cssGradient, aValue);
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseRadialGradient(nsCSSValue& aValue,
|
|
uint8_t aFlags)
|
|
{
|
|
RefPtr<nsCSSValueGradient> cssGradient
|
|
= new nsCSSValueGradient(true, aFlags & eGradient_Repeating);
|
|
|
|
// [ <shape> || <size> ]
|
|
bool haveShape =
|
|
ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
|
|
nsCSSProps::kRadialGradientShapeKTable);
|
|
|
|
bool haveSize =
|
|
ParseSingleTokenVariant(cssGradient->GetRadialSize(), VARIANT_KEYWORD,
|
|
(aFlags & eGradient_AnyLegacy) ?
|
|
nsCSSProps::kRadialGradientLegacySizeKTable :
|
|
nsCSSProps::kRadialGradientSizeKTable);
|
|
if (haveSize) {
|
|
if (!haveShape) {
|
|
// <size> <shape>
|
|
haveShape =
|
|
ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
|
|
nsCSSProps::kRadialGradientShapeKTable);
|
|
}
|
|
} else if (!(aFlags & eGradient_AnyLegacy)) {
|
|
// Save RadialShape before parsing RadiusX because RadialShape and
|
|
// RadiusX share the storage.
|
|
int32_t shape =
|
|
cssGradient->GetRadialShape().GetUnit() == eCSSUnit_Enumerated ?
|
|
cssGradient->GetRadialShape().GetIntValue() : -1;
|
|
// <length> | [<length> | <percentage>]{2}
|
|
cssGradient->mIsExplicitSize = true;
|
|
haveSize =
|
|
ParseSingleTokenNonNegativeVariant(cssGradient->GetRadiusX(), VARIANT_LP,
|
|
nullptr);
|
|
if (!haveSize) {
|
|
// It was not an explicit size after all.
|
|
// Note that ParseNonNegativeVariant may have put something
|
|
// invalid into our storage, but only in the case where it was
|
|
// rejected only for being negative. Since this means the token
|
|
// was a length or a percentage, we know it's not valid syntax
|
|
// (which must be a comma, the 'at' keyword, or a color), so we
|
|
// know this value will be dropped. This means it doesn't matter
|
|
// that we have something invalid in our storage.
|
|
cssGradient->mIsExplicitSize = false;
|
|
} else {
|
|
// vertical extent is optional
|
|
bool haveYSize =
|
|
ParseSingleTokenNonNegativeVariant(cssGradient->GetRadiusY(),
|
|
VARIANT_LP, nullptr);
|
|
if (!haveShape) {
|
|
nsCSSValue shapeValue;
|
|
haveShape =
|
|
ParseSingleTokenVariant(shapeValue, VARIANT_KEYWORD,
|
|
nsCSSProps::kRadialGradientShapeKTable);
|
|
if (haveShape) {
|
|
shape = shapeValue.GetIntValue();
|
|
}
|
|
}
|
|
if (haveYSize
|
|
? shape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR
|
|
: cssGradient->GetRadiusX().GetUnit() == eCSSUnit_Percent ||
|
|
shape == NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((haveShape || haveSize) && ExpectSymbol(',', true)) {
|
|
// [ <shape> || <size> ] ,
|
|
return ParseGradientColorStops(cssGradient, aValue);
|
|
}
|
|
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
|
|
if (!(aFlags & eGradient_AnyLegacy)) {
|
|
if (mToken.mType == eCSSToken_Ident &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("at")) {
|
|
// [ <shape> || <size> ]? at <position> ,
|
|
if (!ParseBoxPositionValues(cssGradient->mBgPos, false) ||
|
|
!ExpectSymbol(',', true)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
return ParseGradientColorStops(cssGradient, aValue);
|
|
}
|
|
|
|
// <color-stops> only
|
|
UngetToken();
|
|
return ParseGradientColorStops(cssGradient, aValue);
|
|
}
|
|
MOZ_ASSERT(!cssGradient->mIsExplicitSize);
|
|
|
|
nsCSSTokenType ty = mToken.mType;
|
|
nsString id = mToken.mIdent;
|
|
UngetToken();
|
|
|
|
// <legacy-gradient-line>
|
|
bool haveGradientLine = false;
|
|
// if we already encountered a shape or size,
|
|
// we can not have a gradient-line in legacy syntax
|
|
if (!haveShape && !haveSize) {
|
|
haveGradientLine = IsLegacyGradientLine(ty, id);
|
|
}
|
|
if (haveGradientLine) {
|
|
// Note: -webkit-radial-gradient() doesn't accept angles.
|
|
bool haveAngle = (aFlags & eGradient_WebkitLegacy)
|
|
? false
|
|
: ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr);
|
|
|
|
// If we got an angle, we might now have a comma, ending the gradient-line
|
|
if (!haveAngle || !ExpectSymbol(',', true)) {
|
|
if (!ParseBoxPositionValues(cssGradient->mBgPos, false)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
if (!ExpectSymbol(',', true) &&
|
|
// If we didn't already get an angle, and we're not -webkit prefixed,
|
|
// can parse an angle+comma now. Otherwise it's an error.
|
|
(haveAngle ||
|
|
(aFlags & eGradient_WebkitLegacy) ||
|
|
!ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE,
|
|
nullptr) ||
|
|
// now we better have a comma
|
|
!ExpectSymbol(',', true))) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (cssGradient->mAngle.GetUnit() != eCSSUnit_None) {
|
|
cssGradient->mIsLegacySyntax = true;
|
|
}
|
|
}
|
|
|
|
// radial gradients might have a shape and size here for legacy syntax
|
|
if (!haveShape && !haveSize) {
|
|
haveShape =
|
|
ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
|
|
nsCSSProps::kRadialGradientShapeKTable);
|
|
haveSize =
|
|
ParseSingleTokenVariant(cssGradient->GetRadialSize(), VARIANT_KEYWORD,
|
|
nsCSSProps::kRadialGradientLegacySizeKTable);
|
|
|
|
// could be in either order
|
|
if (!haveShape) {
|
|
haveShape =
|
|
ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
|
|
nsCSSProps::kRadialGradientShapeKTable);
|
|
}
|
|
}
|
|
|
|
if ((haveShape || haveSize) && !ExpectSymbol(',', true)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
return ParseGradientColorStops(cssGradient, aValue);
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::IsLegacyGradientLine(const nsCSSTokenType& aType,
|
|
const nsString& aId)
|
|
{
|
|
// N.B. ParseBoxPositionValues is not guaranteed to put back
|
|
// everything it scanned if it fails, so we must only call it
|
|
// if there is no alternative to consuming a <box-position>.
|
|
// ParseVariant, as used here, will either succeed and consume
|
|
// a single token, or fail and consume none, so we can be more
|
|
// cavalier about calling it.
|
|
|
|
bool haveGradientLine = false;
|
|
switch (aType) {
|
|
case eCSSToken_Percentage:
|
|
case eCSSToken_Number:
|
|
case eCSSToken_Dimension:
|
|
haveGradientLine = true;
|
|
break;
|
|
|
|
case eCSSToken_Function:
|
|
if (aId.LowerCaseEqualsLiteral("calc") ||
|
|
aId.LowerCaseEqualsLiteral("-moz-calc")) {
|
|
haveGradientLine = true;
|
|
break;
|
|
}
|
|
MOZ_FALLTHROUGH;
|
|
case eCSSToken_ID:
|
|
case eCSSToken_Hash:
|
|
// this is a color
|
|
break;
|
|
|
|
case eCSSToken_Ident: {
|
|
// This is only a gradient line if it's a box position keyword.
|
|
nsCSSKeyword kw = nsCSSKeywords::LookupKeyword(aId);
|
|
int32_t junk;
|
|
if (kw != eCSSKeyword_UNKNOWN &&
|
|
nsCSSProps::FindKeyword(kw, nsCSSProps::kImageLayerPositionKTable,
|
|
junk)) {
|
|
haveGradientLine = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
// error
|
|
break;
|
|
}
|
|
|
|
return haveGradientLine;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseGradientColorStops(nsCSSValueGradient* aGradient,
|
|
nsCSSValue& aValue)
|
|
{
|
|
// At least two color stops are required
|
|
if (!ParseColorStop(aGradient) ||
|
|
!ExpectSymbol(',', true) ||
|
|
!ParseColorStop(aGradient)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
// Additional color stops
|
|
while (ExpectSymbol(',', true)) {
|
|
if (!ParseColorStop(aGradient)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!ExpectSymbol(')', true)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
// Check if interpolation hints are in the correct location
|
|
bool previousPointWasInterpolationHint = true;
|
|
for (size_t x = 0; x < aGradient->mStops.Length(); x++) {
|
|
bool isInterpolationHint = aGradient->mStops[x].mIsInterpolationHint;
|
|
if (isInterpolationHint && previousPointWasInterpolationHint) {
|
|
return false;
|
|
}
|
|
previousPointWasInterpolationHint = isInterpolationHint;
|
|
}
|
|
|
|
if (previousPointWasInterpolationHint) {
|
|
return false;
|
|
}
|
|
|
|
aValue.SetGradientValue(aGradient);
|
|
return true;
|
|
}
|
|
|
|
// Parses the x or y component of a -webkit-gradient() <point> expression.
|
|
// See ParseWebkitGradientPoint() documentation for more.
|
|
bool
|
|
CSSParserImpl::ParseWebkitGradientPointComponent(nsCSSValue& aComponent,
|
|
bool aIsHorizontal)
|
|
{
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
|
|
// Keyword tables to use for keyword-matching
|
|
// (Keyword order is important; we assume the index can be multiplied by 50%
|
|
// to convert to a percent-valued component.)
|
|
static const nsCSSKeyword kHorizKeywords[] = {
|
|
eCSSKeyword_left, // 0%
|
|
eCSSKeyword_center, // 50%
|
|
eCSSKeyword_right // 100%
|
|
};
|
|
static const nsCSSKeyword kVertKeywords[] = {
|
|
eCSSKeyword_top, // 0%
|
|
eCSSKeyword_center, // 50%
|
|
eCSSKeyword_bottom // 100%
|
|
};
|
|
static const size_t kNumKeywords = MOZ_ARRAY_LENGTH(kHorizKeywords);
|
|
static_assert(kNumKeywords == MOZ_ARRAY_LENGTH(kVertKeywords),
|
|
"Horizontal & vertical keyword tables must have same count");
|
|
|
|
// Try to parse the component as a number, or a percent, or a
|
|
// keyword-converted-to-percent.
|
|
if (mToken.mType == eCSSToken_Number) {
|
|
aComponent.SetFloatValue(mToken.mNumber, eCSSUnit_Pixel);
|
|
} else if (mToken.mType == eCSSToken_Percentage) {
|
|
aComponent.SetPercentValue(mToken.mNumber);
|
|
} else if (mToken.mType == eCSSToken_Ident) {
|
|
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
|
|
if (keyword == eCSSKeyword_UNKNOWN) {
|
|
return false;
|
|
}
|
|
// Choose our keyword table:
|
|
const nsCSSKeyword* kwTable = aIsHorizontal ? kHorizKeywords : kVertKeywords;
|
|
// Convert keyword to percent value (0%, 50%, or 100%)
|
|
bool didAcceptKeyword = false;
|
|
for (size_t i = 0; i < kNumKeywords; i++) {
|
|
if (keyword == kwTable[i]) {
|
|
// 0%, 50%, or 100%:
|
|
aComponent.SetPercentValue(i * 0.5);
|
|
didAcceptKeyword = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!didAcceptKeyword) {
|
|
return false;
|
|
}
|
|
} else {
|
|
// Unrecognized token type. Put it back. (It might be a closing-paren of an
|
|
// invalid -webkit-gradient(...) expression, and we need to be sure caller
|
|
// can see it & stops parsing at that point.)
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT(aComponent.GetUnit() == eCSSUnit_Pixel ||
|
|
aComponent.GetUnit() == eCSSUnit_Percent,
|
|
"If we get here, we should've successfully parsed a number (as a "
|
|
"pixel length), a percent, or a keyword (converted to percent)");
|
|
return true;
|
|
}
|
|
|
|
// This function parses a "<point>" expression for -webkit-gradient(...)
|
|
// Quoting https://www.webkit.org/blog/175/introducing-css-gradients/ :
|
|
// "A point is a pair of space-separated values.
|
|
// The syntax supports numbers, percentages or
|
|
// the keywords top, bottom, left and right
|
|
// for point values."
|
|
//
|
|
// Two additional notes:
|
|
// - WebKit also accepts the "center" keyword (not listed in the text above).
|
|
// - WebKit only accepts horizontal-flavored keywords (left/center/right) in
|
|
// the first ("x") component, and vertical-flavored keywords
|
|
// (top/center/bottom) in the second ("y") component. (This is different
|
|
// from the standard gradient syntax, which accepts both orderings, e.g.
|
|
// "top left" as well as "left top".)
|
|
bool
|
|
CSSParserImpl::ParseWebkitGradientPoint(nsCSSValuePair& aPoint)
|
|
{
|
|
return ParseWebkitGradientPointComponent(aPoint.mXValue, true) &&
|
|
ParseWebkitGradientPointComponent(aPoint.mYValue, false);
|
|
}
|
|
|
|
// Parse the next token as a <number> (for a <radius> in a -webkit-gradient
|
|
// expresison). Returns true on success; returns false & puts back
|
|
// whatever it parsed on failure.
|
|
bool
|
|
CSSParserImpl::ParseWebkitGradientRadius(float& aRadius)
|
|
{
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType != eCSSToken_Number) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
aRadius = mToken.mNumber;
|
|
return true;
|
|
}
|
|
|
|
// Parse one of:
|
|
// color-stop(number|percent, color)
|
|
// from(color)
|
|
// to(color)
|
|
//
|
|
// Quoting https://www.webkit.org/blog/175/introducing-css-gradients/ :
|
|
// A stop is a function, color-stop, that takes two arguments, the stop value
|
|
// (either a percentage or a number between 0 and 1.0), and a color (any
|
|
// valid CSS color). In addition the shorthand functions from and to are
|
|
// supported. These functions only require a color argument and are
|
|
// equivalent to color-stop(0, ...) and color-stop(1.0, …) respectively.
|
|
bool
|
|
CSSParserImpl::ParseWebkitGradientColorStop(nsCSSValueGradient* aGradient)
|
|
{
|
|
MOZ_ASSERT(aGradient, "null gradient");
|
|
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
|
|
// We're expecting color-stop(...), from(...), or to(...) which are all
|
|
// functions. Bail if we got anything else.
|
|
if (mToken.mType != eCSSToken_Function) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement();
|
|
|
|
// Parse color-stop location (or infer it, for shorthands "from"/"to"):
|
|
if (mToken.mIdent.LowerCaseEqualsLiteral("color-stop")) {
|
|
// Parse stop location, followed by comma.
|
|
if (!ParseSingleTokenVariant(stop->mLocation,
|
|
VARIANT_NUMBER | VARIANT_PERCENT,
|
|
nullptr) ||
|
|
!ExpectSymbol(',', true)) {
|
|
SkipUntil(')'); // Skip to end of color-stop(...) expression.
|
|
return false;
|
|
}
|
|
|
|
// If we got a <number>, convert it to percentage for consistency:
|
|
if (stop->mLocation.GetUnit() == eCSSUnit_Number) {
|
|
stop->mLocation.SetPercentValue(stop->mLocation.GetFloatValue());
|
|
}
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("from")) {
|
|
// Shorthand for color-stop(0%, ...)
|
|
stop->mLocation.SetPercentValue(0.0f);
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("to")) {
|
|
// Shorthand for color-stop(100%, ...)
|
|
stop->mLocation.SetPercentValue(1.0f);
|
|
} else {
|
|
// Unrecognized function name (invalid for a -webkit-gradient color stop).
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
CSSParseResult result = ParseVariant(stop->mColor, VARIANT_COLOR, nullptr);
|
|
if (result != CSSParseResult::Ok ||
|
|
(stop->mColor.GetUnit() == eCSSUnit_EnumColor &&
|
|
stop->mColor.GetIntValue() == NS_COLOR_CURRENTCOLOR)) {
|
|
// Parse failure, or parsed "currentColor" which is forbidden in
|
|
// -webkit-gradient for some reason.
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
// Parse color-stop function close-paren
|
|
if (!ExpectSymbol(')', true)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT(stop->mLocation.GetUnit() == eCSSUnit_Percent,
|
|
"Should produce only percent-valued stop-locations. "
|
|
"(Caller depends on this when sorting color stops.)");
|
|
|
|
return true;
|
|
}
|
|
|
|
// Comparatison function to use for sorting -webkit-gradient() stops by
|
|
// location. This function assumes stops have percent-valued locations (and
|
|
// CSSParserImpl::ParseWebkitGradientColorStop should enforce this).
|
|
static bool
|
|
IsColorStopPctLocationLessThan(const nsCSSValueGradientStop& aStop1,
|
|
const nsCSSValueGradientStop& aStop2) {
|
|
return (aStop1.mLocation.GetPercentValue() <
|
|
aStop2.mLocation.GetPercentValue());
|
|
}
|
|
|
|
// This function parses a list of comma-separated color-stops for a
|
|
// -webkit-gradient(...) expression, and then pads & sorts the list as-needed.
|
|
bool
|
|
CSSParserImpl::ParseWebkitGradientColorStops(nsCSSValueGradient* aGradient)
|
|
{
|
|
MOZ_ASSERT(aGradient, "null gradient");
|
|
|
|
// Parse any number of ", <color-stop>" expressions. (0 or more)
|
|
// Note: This is different from unprefixed gradient syntax, which
|
|
// requires at least 2 stops.
|
|
while (ExpectSymbol(',', true)) {
|
|
if (!ParseWebkitGradientColorStop(aGradient)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Pad up to 2 stops as-needed:
|
|
// (Modern gradient expressions are required to have at least 2 stops, so we
|
|
// depend on this internally -- e.g. we have an assertion about this in
|
|
// nsCSSRendering.cpp. -webkit-gradient syntax allows 0 stops or 1 stop,
|
|
// though, so we just pad up to 2 stops in this case).
|
|
|
|
// If we have no stops, pad with transparent-black:
|
|
if (aGradient->mStops.IsEmpty()) {
|
|
nsCSSValueGradientStop* stop1 = aGradient->mStops.AppendElement();
|
|
stop1->mColor.SetIntegerColorValue(NS_RGBA(0, 0, 0, 0),
|
|
eCSSUnit_RGBAColor);
|
|
stop1->mLocation.SetPercentValue(0.0f);
|
|
|
|
nsCSSValueGradientStop* stop2 = aGradient->mStops.AppendElement();
|
|
stop2->mColor.SetIntegerColorValue(NS_RGBA(0, 0, 0, 0),
|
|
eCSSUnit_RGBAColor);
|
|
stop2->mLocation.SetPercentValue(1.0f);
|
|
} else if (aGradient->mStops.Length() == 1) {
|
|
// Copy whatever the author provided in the first stop:
|
|
nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement();
|
|
*stop = aGradient->mStops[0];
|
|
} else {
|
|
// We have >2 stops. Sort them in order of increasing location.
|
|
std::stable_sort(aGradient->mStops.begin(),
|
|
aGradient->mStops.end(),
|
|
IsColorStopPctLocationLessThan);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Compares aStartCoord to aEndCoord, and returns true iff they share the same
|
|
// unit (both pixel, or both percent) and aStartCoord is larger.
|
|
static bool
|
|
IsWebkitGradientCoordLarger(const nsCSSValue& aStartCoord,
|
|
const nsCSSValue& aEndCoord)
|
|
{
|
|
if (aStartCoord.GetUnit() == eCSSUnit_Percent &&
|
|
aEndCoord.GetUnit() == eCSSUnit_Percent) {
|
|
return aStartCoord.GetPercentValue() > aEndCoord.GetPercentValue();
|
|
}
|
|
|
|
if (aStartCoord.GetUnit() == eCSSUnit_Pixel &&
|
|
aEndCoord.GetUnit() == eCSSUnit_Pixel) {
|
|
return aStartCoord.GetFloatValue() > aEndCoord.GetFloatValue();
|
|
}
|
|
|
|
// We can't compare them, since their units differ. Returning false suggests
|
|
// that aEndCoord is larger, which is probably a decent guess anyway.
|
|
return false;
|
|
}
|
|
|
|
// Finalize our internal representation of a -webkit-gradient(linear, ...)
|
|
// expression, given the parsed points. (The parsed color stops
|
|
// should already be hanging off of the passed-in nsCSSValueGradient.)
|
|
//
|
|
// Note: linear gradients progress along a line between two points. The
|
|
// -webkit-gradient(linear, ...) syntax lets the author precisely specify the
|
|
// starting and ending point. However, our internal gradient structures
|
|
// only store one point, and the other point is implicitly its reflection
|
|
// across the painted area's center. (The legacy -moz-linear-gradient syntax
|
|
// also lets us store an angle.)
|
|
//
|
|
// In this function, we try to go from the two-point representation to an
|
|
// equivalent or approximately-equivalent one-point representation.
|
|
void
|
|
CSSParserImpl::FinalizeLinearWebkitGradient(nsCSSValueGradient* aGradient,
|
|
const nsCSSValuePair& aStartPoint,
|
|
const nsCSSValuePair& aEndPoint)
|
|
{
|
|
MOZ_ASSERT(!aGradient->mIsRadial, "passed-in gradient must be linear");
|
|
|
|
// If the start & end points have the same Y-coordinate, then we can treat
|
|
// this as a horizontal gradient progressing towards the center of the left
|
|
// or right side.
|
|
if (aStartPoint.mYValue == aEndPoint.mYValue) {
|
|
aGradient->mBgPos.mYValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_CENTER,
|
|
eCSSUnit_Enumerated);
|
|
if (IsWebkitGradientCoordLarger(aStartPoint.mXValue, aEndPoint.mXValue)) {
|
|
aGradient->mBgPos.mXValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_LEFT,
|
|
eCSSUnit_Enumerated);
|
|
} else {
|
|
aGradient->mBgPos.mXValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_RIGHT,
|
|
eCSSUnit_Enumerated);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// If the start & end points have the same X-coordinate, then we can treat
|
|
// this as a horizontal gradient progressing towards the center of the top
|
|
// or bottom side.
|
|
if (aStartPoint.mXValue == aEndPoint.mXValue) {
|
|
aGradient->mBgPos.mXValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_CENTER,
|
|
eCSSUnit_Enumerated);
|
|
if (IsWebkitGradientCoordLarger(aStartPoint.mYValue, aEndPoint.mYValue)) {
|
|
aGradient->mBgPos.mYValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_TOP,
|
|
eCSSUnit_Enumerated);
|
|
} else {
|
|
aGradient->mBgPos.mYValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_BOTTOM,
|
|
eCSSUnit_Enumerated);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// OK, the gradient is angled, which means we likely can't represent it
|
|
// exactly in |aGradient|, without doing analysis on the two points to
|
|
// extract an angle (which we might not be able to do depending on the units
|
|
// used). For now, we'll just do something really basic -- just use the
|
|
// first point as if it were the starting point in a legacy
|
|
// -moz-linear-gradient() expression. That way, the rendered gradient will
|
|
// progress from this first point, towards the center of the covered element,
|
|
// to a reflected end point on the far side. Note that we have to use
|
|
// mIsLegacySyntax=true for this to work, because standardized (non-legacy)
|
|
// gradients place some restrictions on the reference point [namely, that it
|
|
// use percent units & be on the border of the element].
|
|
aGradient->mIsLegacySyntax = true;
|
|
aGradient->mBgPos = aStartPoint;
|
|
}
|
|
|
|
// Finalize our internal representation of a -webkit-gradient(radial, ...)
|
|
// expression, given the parsed points & radii. (The parsed color-stops
|
|
// should already be hanging off of the passed-in nsCSSValueGradient).
|
|
void
|
|
CSSParserImpl::FinalizeRadialWebkitGradient(nsCSSValueGradient* aGradient,
|
|
const nsCSSValuePair& aFirstCenter,
|
|
const nsCSSValuePair& aSecondCenter,
|
|
const float aFirstRadius,
|
|
const float aSecondRadius)
|
|
{
|
|
MOZ_ASSERT(aGradient->mIsRadial, "passed-in gradient must be radial");
|
|
|
|
// NOTE: -webkit-gradient(radial, ...) has *two arbitrary circles*, with the
|
|
// gradient stretching between the circles' edges. In contrast, the standard
|
|
// syntax (and hence our data structures) can only represent *one* circle,
|
|
// with the gradient going from its center to its edge. To bridge this gap
|
|
// in expressiveness, we'll just see which of our two circles is smaller, and
|
|
// we'll treat that circle as if it were zero-sized and located at the center
|
|
// of the larger circle. Then, we'll be able to use the same data structures
|
|
// that we use for the standard radial-gradient syntax.
|
|
if (aSecondRadius >= aFirstRadius) {
|
|
// Second circle is larger.
|
|
aGradient->mBgPos = aSecondCenter;
|
|
aGradient->mIsExplicitSize = true;
|
|
aGradient->GetRadiusX().SetFloatValue(aSecondRadius, eCSSUnit_Pixel);
|
|
return;
|
|
}
|
|
|
|
// First circle is larger, so we'll have it be the outer circle.
|
|
aGradient->mBgPos = aFirstCenter;
|
|
aGradient->mIsExplicitSize = true;
|
|
aGradient->GetRadiusX().SetFloatValue(aFirstRadius, eCSSUnit_Pixel);
|
|
|
|
// For this to work properly (with the earlier color stops attached to the
|
|
// first circle), we need to also reverse the color-stop list, so that
|
|
// e.g. the author's "from" color is attached to the outer edge (the first
|
|
// circle), rather than attached to the center (the collapsed second circle).
|
|
std::reverse(aGradient->mStops.begin(), aGradient->mStops.end());
|
|
|
|
// And now invert the stop locations:
|
|
for (nsCSSValueGradientStop& colorStop : aGradient->mStops) {
|
|
float origLocation = colorStop.mLocation.GetPercentValue();
|
|
colorStop.mLocation.SetPercentValue(1.0f - origLocation);
|
|
}
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseWebkitGradient(nsCSSValue& aValue)
|
|
{
|
|
// Parse type of gradient
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType != eCSSToken_Ident) {
|
|
UngetToken(); // Important; the token might be ")", which we're about to
|
|
// seek to.
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
bool isRadial;
|
|
if (mToken.mIdent.LowerCaseEqualsLiteral("radial")) {
|
|
isRadial = true;
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("linear")) {
|
|
isRadial = false;
|
|
} else {
|
|
// Unrecognized gradient type.
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
// Parse a comma + first point:
|
|
nsCSSValuePair firstPoint;
|
|
if (!ExpectSymbol(',', true) ||
|
|
!ParseWebkitGradientPoint(firstPoint)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
// If radial, parse comma + first radius:
|
|
float firstRadius;
|
|
if (isRadial) {
|
|
if (!ExpectSymbol(',', true) ||
|
|
!ParseWebkitGradientRadius(firstRadius)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Parse a comma + second point:
|
|
nsCSSValuePair secondPoint;
|
|
if (!ExpectSymbol(',', true) ||
|
|
!ParseWebkitGradientPoint(secondPoint)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
// If radial, parse comma + second radius:
|
|
float secondRadius;
|
|
if (isRadial) {
|
|
if (!ExpectSymbol(',', true) ||
|
|
!ParseWebkitGradientRadius(secondRadius)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Construct a nsCSSValueGradient object, and parse color stops into it:
|
|
RefPtr<nsCSSValueGradient> cssGradient =
|
|
new nsCSSValueGradient(isRadial, false /* aIsRepeating */);
|
|
|
|
if (!ParseWebkitGradientColorStops(cssGradient) ||
|
|
!ExpectSymbol(')', true)) {
|
|
// Failed to parse color-stops, or found trailing junk between them & ')'.
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
// Finish building cssGradient, based on our parsed positioning/sizing info:
|
|
if (isRadial) {
|
|
FinalizeRadialWebkitGradient(cssGradient, firstPoint, secondPoint,
|
|
firstRadius, secondRadius);
|
|
} else {
|
|
FinalizeLinearWebkitGradient(cssGradient, firstPoint, secondPoint);
|
|
}
|
|
|
|
aValue.SetGradientValue(cssGradient);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseWebkitTextStroke()
|
|
{
|
|
static const nsCSSPropertyID kWebkitTextStrokeIDs[] = {
|
|
eCSSProperty__webkit_text_stroke_width,
|
|
eCSSProperty__webkit_text_stroke_color
|
|
};
|
|
|
|
const size_t numProps = MOZ_ARRAY_LENGTH(kWebkitTextStrokeIDs);
|
|
nsCSSValue values[numProps];
|
|
|
|
int32_t found = ParseChoice(values, kWebkitTextStrokeIDs, numProps);
|
|
if (found < 1) {
|
|
return false;
|
|
}
|
|
|
|
if (!(found & 1)) { // Provide default -webkit-text-stroke-width
|
|
values[0].SetFloatValue(0, eCSSUnit_Pixel);
|
|
}
|
|
|
|
if (!(found & 2)) { // Provide default -webkit-text-stroke-color
|
|
values[1].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
|
|
}
|
|
|
|
for (size_t index = 0; index < numProps; ++index) {
|
|
AppendValue(kWebkitTextStrokeIDs[index], values[index]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int32_t
|
|
CSSParserImpl::ParseChoice(nsCSSValue aValues[],
|
|
const nsCSSPropertyID aPropIDs[], int32_t aNumIDs)
|
|
{
|
|
int32_t found = 0;
|
|
nsAutoParseCompoundProperty compound(this);
|
|
|
|
int32_t loop;
|
|
for (loop = 0; loop < aNumIDs; loop++) {
|
|
// Try each property parser in order
|
|
int32_t hadFound = found;
|
|
int32_t index;
|
|
for (index = 0; index < aNumIDs; index++) {
|
|
int32_t bit = 1 << index;
|
|
if ((found & bit) == 0) {
|
|
CSSParseResult result =
|
|
ParseSingleValueProperty(aValues[index], aPropIDs[index]);
|
|
if (result == CSSParseResult::Error) {
|
|
return -1;
|
|
}
|
|
if (result == CSSParseResult::Ok) {
|
|
found |= bit;
|
|
// It's more efficient to break since it will reset |hadFound|
|
|
// to |found|. Furthermore, ParseListStyle depends on our going
|
|
// through the properties in order for each value..
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (found == hadFound) { // found nothing new
|
|
break;
|
|
}
|
|
}
|
|
if (0 < found) {
|
|
if (1 == found) { // only first property
|
|
if (eCSSUnit_Inherit == aValues[0].GetUnit()) { // one inherit, all inherit
|
|
for (loop = 1; loop < aNumIDs; loop++) {
|
|
aValues[loop].SetInheritValue();
|
|
}
|
|
found = ((1 << aNumIDs) - 1);
|
|
}
|
|
else if (eCSSUnit_Initial == aValues[0].GetUnit()) { // one initial, all initial
|
|
for (loop = 1; loop < aNumIDs; loop++) {
|
|
aValues[loop].SetInitialValue();
|
|
}
|
|
found = ((1 << aNumIDs) - 1);
|
|
}
|
|
else if (eCSSUnit_Unset == aValues[0].GetUnit()) { // one unset, all unset
|
|
for (loop = 1; loop < aNumIDs; loop++) {
|
|
aValues[loop].SetUnsetValue();
|
|
}
|
|
found = ((1 << aNumIDs) - 1);
|
|
}
|
|
}
|
|
else { // more than one value, verify no inherits, initials or unsets
|
|
for (loop = 0; loop < aNumIDs; loop++) {
|
|
if (eCSSUnit_Inherit == aValues[loop].GetUnit()) {
|
|
found = -1;
|
|
break;
|
|
}
|
|
else if (eCSSUnit_Initial == aValues[loop].GetUnit()) {
|
|
found = -1;
|
|
break;
|
|
}
|
|
else if (eCSSUnit_Unset == aValues[loop].GetUnit()) {
|
|
found = -1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::AppendValue(nsCSSPropertyID aPropID, const nsCSSValue& aValue)
|
|
{
|
|
mTempData.AddLonghandProperty(aPropID, aValue);
|
|
}
|
|
|
|
/**
|
|
* Parse a "box" property. Box properties have 1 to 4 values. When less
|
|
* than 4 values are provided a standard mapping is used to replicate
|
|
* existing values.
|
|
*/
|
|
bool
|
|
CSSParserImpl::ParseBoxProperties(const nsCSSPropertyID aPropIDs[])
|
|
{
|
|
// Get up to four values for the property
|
|
int32_t count = 0;
|
|
nsCSSRect result;
|
|
NS_FOR_CSS_SIDES (index) {
|
|
CSSParseResult parseResult =
|
|
ParseBoxProperty(result.*(nsCSSRect::sides[index]), aPropIDs[index]);
|
|
if (parseResult == CSSParseResult::NotFound) {
|
|
break;
|
|
}
|
|
if (parseResult == CSSParseResult::Error) {
|
|
return false;
|
|
}
|
|
count++;
|
|
}
|
|
if (count == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (1 < count) { // verify no more than single inherit, initial or unset
|
|
NS_FOR_CSS_SIDES (index) {
|
|
nsCSSUnit unit = (result.*(nsCSSRect::sides[index])).GetUnit();
|
|
if (eCSSUnit_Inherit == unit ||
|
|
eCSSUnit_Initial == unit ||
|
|
eCSSUnit_Unset == unit) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Provide missing values by replicating some of the values found
|
|
switch (count) {
|
|
case 1: // Make right == top
|
|
result.mRight = result.mTop;
|
|
MOZ_FALLTHROUGH;
|
|
case 2: // Make bottom == top
|
|
result.mBottom = result.mTop;
|
|
MOZ_FALLTHROUGH;
|
|
case 3: // Make left == right
|
|
result.mLeft = result.mRight;
|
|
}
|
|
|
|
NS_FOR_CSS_SIDES (index) {
|
|
AppendValue(aPropIDs[index], result.*(nsCSSRect::sides[index]));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Similar to ParseBoxProperties, except there is only one property
|
|
// with the result as its value, not four. Requires values be nonnegative.
|
|
bool
|
|
CSSParserImpl::ParseGroupedBoxProperty(int32_t aVariantMask,
|
|
/** outparam */ nsCSSValue& aValue)
|
|
{
|
|
nsCSSRect& result = aValue.SetRectValue();
|
|
|
|
int32_t count = 0;
|
|
NS_FOR_CSS_SIDES (index) {
|
|
CSSParseResult parseResult =
|
|
ParseVariantWithRestrictions(result.*(nsCSSRect::sides[index]),
|
|
aVariantMask, nullptr,
|
|
CSS_PROPERTY_VALUE_NONNEGATIVE);
|
|
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_background:
|
|
return ParseImageLayers(nsStyleImageLayers::kBackgroundLayerTable);
|
|
case eCSSProperty_background_repeat:
|
|
return ParseImageLayerRepeat(eCSSProperty_background_repeat);
|
|
case eCSSProperty_background_position:
|
|
return ParseImageLayerPosition(nsStyleImageLayers::kBackgroundLayerTable);
|
|
case eCSSProperty_background_position_x:
|
|
case eCSSProperty_background_position_y:
|
|
return ParseImageLayerPositionCoord(aPropID,
|
|
aPropID == eCSSProperty_background_position_x);
|
|
case eCSSProperty_background_size:
|
|
return ParseImageLayerSize(eCSSProperty_background_size);
|
|
case eCSSProperty_border:
|
|
return ParseBorderSide(kBorderTopIDs, true);
|
|
case eCSSProperty_border_color:
|
|
return ParseBorderColor();
|
|
case eCSSProperty_border_spacing:
|
|
return ParseBorderSpacing();
|
|
case eCSSProperty_border_style:
|
|
return ParseBorderStyle();
|
|
case eCSSProperty_border_block_end:
|
|
return ParseBorderSide(kBorderBlockEndIDs, false);
|
|
case eCSSProperty_border_block_start:
|
|
return ParseBorderSide(kBorderBlockStartIDs, false);
|
|
case eCSSProperty_border_bottom:
|
|
return ParseBorderSide(kBorderBottomIDs, false);
|
|
case eCSSProperty_border_inline_end:
|
|
return ParseBorderSide(kBorderInlineEndIDs, false);
|
|
case eCSSProperty_border_inline_start:
|
|
return ParseBorderSide(kBorderInlineStartIDs, false);
|
|
case eCSSProperty_border_left:
|
|
return ParseBorderSide(kBorderLeftIDs, false);
|
|
case eCSSProperty_border_right:
|
|
return ParseBorderSide(kBorderRightIDs, false);
|
|
case eCSSProperty_border_top:
|
|
return ParseBorderSide(kBorderTopIDs, false);
|
|
case eCSSProperty_border_bottom_colors:
|
|
case eCSSProperty_border_left_colors:
|
|
case eCSSProperty_border_right_colors:
|
|
case eCSSProperty_border_top_colors:
|
|
return ParseBorderColors(aPropID);
|
|
case eCSSProperty_border_image_slice:
|
|
return ParseBorderImageSlice(true, nullptr);
|
|
case eCSSProperty_border_image_width:
|
|
return ParseBorderImageWidth(true);
|
|
case eCSSProperty_border_image_outset:
|
|
return ParseBorderImageOutset(true);
|
|
case eCSSProperty_border_image_repeat:
|
|
return ParseBorderImageRepeat(true);
|
|
case eCSSProperty_border_image:
|
|
return ParseBorderImage();
|
|
case eCSSProperty_border_width:
|
|
return ParseBorderWidth();
|
|
case eCSSProperty_border_radius:
|
|
return ParseBoxCornerRadii(kBorderRadiusIDs);
|
|
case eCSSProperty__moz_outline_radius:
|
|
return ParseBoxCornerRadii(kOutlineRadiusIDs);
|
|
|
|
case eCSSProperty_border_top_left_radius:
|
|
case eCSSProperty_border_top_right_radius:
|
|
case eCSSProperty_border_bottom_right_radius:
|
|
case eCSSProperty_border_bottom_left_radius:
|
|
case eCSSProperty__moz_outline_radius_topLeft:
|
|
case eCSSProperty__moz_outline_radius_topRight:
|
|
case eCSSProperty__moz_outline_radius_bottomRight:
|
|
case eCSSProperty__moz_outline_radius_bottomLeft:
|
|
return ParseBoxCornerRadius(aPropID);
|
|
|
|
case eCSSProperty_box_shadow:
|
|
case eCSSProperty_text_shadow:
|
|
return ParseShadowList(aPropID);
|
|
|
|
case eCSSProperty_clip:
|
|
return ParseRect(eCSSProperty_clip);
|
|
case eCSSProperty_columns:
|
|
return ParseColumns();
|
|
case eCSSProperty_column_rule:
|
|
return ParseBorderSide(kColumnRuleIDs, false);
|
|
case eCSSProperty_content:
|
|
return ParseContent();
|
|
case eCSSProperty_counter_increment:
|
|
case eCSSProperty_counter_reset:
|
|
return ParseCounterData(aPropID);
|
|
case eCSSProperty_cursor:
|
|
return ParseCursor();
|
|
case eCSSProperty_filter:
|
|
return ParseFilter();
|
|
case eCSSProperty_flex:
|
|
return ParseFlex();
|
|
case eCSSProperty_flex_flow:
|
|
return ParseFlexFlow();
|
|
case eCSSProperty_font:
|
|
return ParseFont();
|
|
case eCSSProperty_font_variant:
|
|
return ParseFontVariant();
|
|
case eCSSProperty_grid_auto_flow:
|
|
return ParseGridAutoFlow();
|
|
case eCSSProperty_grid_auto_columns:
|
|
case eCSSProperty_grid_auto_rows:
|
|
return ParseGridAutoColumnsRows(aPropID);
|
|
case eCSSProperty_grid_template_areas:
|
|
return ParseGridTemplateAreas();
|
|
case eCSSProperty_grid_template_columns:
|
|
case eCSSProperty_grid_template_rows:
|
|
return ParseGridTemplateColumnsRows(aPropID);
|
|
case eCSSProperty_grid_template:
|
|
return ParseGridTemplate();
|
|
case eCSSProperty_grid:
|
|
return ParseGrid();
|
|
case eCSSProperty_grid_column_start:
|
|
case eCSSProperty_grid_column_end:
|
|
case eCSSProperty_grid_row_start:
|
|
case eCSSProperty_grid_row_end:
|
|
return ParseGridColumnRowStartEnd(aPropID);
|
|
case eCSSProperty_grid_column:
|
|
return ParseGridColumnRow(eCSSProperty_grid_column_start,
|
|
eCSSProperty_grid_column_end);
|
|
case eCSSProperty_grid_row:
|
|
return ParseGridColumnRow(eCSSProperty_grid_row_start,
|
|
eCSSProperty_grid_row_end);
|
|
case eCSSProperty_grid_area:
|
|
return ParseGridArea();
|
|
case eCSSProperty_grid_gap:
|
|
return ParseGridGap();
|
|
case eCSSProperty_image_region:
|
|
return ParseRect(eCSSProperty_image_region);
|
|
case eCSSProperty_align_content:
|
|
case eCSSProperty_justify_content:
|
|
return ParseAlignJustifyContent(aPropID);
|
|
case eCSSProperty_align_items:
|
|
return ParseAlignItems();
|
|
case eCSSProperty_align_self:
|
|
case eCSSProperty_justify_self:
|
|
return ParseAlignJustifySelf(aPropID);
|
|
case eCSSProperty_initial_letter:
|
|
return ParseInitialLetter();
|
|
case eCSSProperty_justify_items:
|
|
return ParseJustifyItems();
|
|
case eCSSProperty_list_style:
|
|
return ParseListStyle();
|
|
case eCSSProperty_margin:
|
|
return ParseMargin();
|
|
case eCSSProperty_object_position:
|
|
return ParseObjectPosition();
|
|
case eCSSProperty_outline:
|
|
return ParseOutline();
|
|
case eCSSProperty_overflow:
|
|
return ParseOverflow();
|
|
case eCSSProperty_padding:
|
|
return ParsePadding();
|
|
case eCSSProperty_quotes:
|
|
return ParseQuotes();
|
|
case eCSSProperty_text_decoration:
|
|
return ParseTextDecoration();
|
|
case eCSSProperty_text_emphasis:
|
|
return ParseTextEmphasis();
|
|
case eCSSProperty_will_change:
|
|
return ParseWillChange();
|
|
case eCSSProperty_transform:
|
|
return ParseTransform(false);
|
|
case eCSSProperty__moz_transform:
|
|
return ParseTransform(true);
|
|
case eCSSProperty_transform_origin:
|
|
return ParseTransformOrigin(false);
|
|
case eCSSProperty_perspective_origin:
|
|
return ParseTransformOrigin(true);
|
|
case eCSSProperty_transition:
|
|
return ParseTransition();
|
|
case eCSSProperty_animation:
|
|
return ParseAnimation();
|
|
case eCSSProperty_transition_property:
|
|
return ParseTransitionProperty();
|
|
case eCSSProperty_fill:
|
|
case eCSSProperty_stroke:
|
|
return ParsePaint(aPropID);
|
|
case eCSSProperty_stroke_dasharray:
|
|
return ParseDasharray();
|
|
case eCSSProperty_marker:
|
|
return ParseMarker();
|
|
case eCSSProperty_paint_order:
|
|
return ParsePaintOrder();
|
|
case eCSSProperty_scroll_snap_type:
|
|
return ParseScrollSnapType();
|
|
#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
|
|
case eCSSProperty_mask:
|
|
return ParseImageLayers(nsStyleImageLayers::kMaskLayerTable);
|
|
case eCSSProperty_mask_repeat:
|
|
return ParseImageLayerRepeat(eCSSProperty_mask_repeat);
|
|
case eCSSProperty_mask_position:
|
|
return ParseImageLayerPosition(nsStyleImageLayers::kMaskLayerTable);
|
|
case eCSSProperty_mask_position_x:
|
|
case eCSSProperty_mask_position_y:
|
|
return ParseImageLayerPositionCoord(aPropID,
|
|
aPropID == eCSSProperty_mask_position_x);
|
|
case eCSSProperty_mask_size:
|
|
return ParseImageLayerSize(eCSSProperty_mask_size);
|
|
#endif
|
|
case eCSSProperty__webkit_text_stroke:
|
|
return ParseWebkitTextStroke();
|
|
case eCSSProperty_all:
|
|
return ParseAll();
|
|
default:
|
|
MOZ_ASSERT(false, "should not be called");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Bits used in determining which background position info we have
|
|
#define BG_CENTER NS_STYLE_IMAGELAYER_POSITION_CENTER
|
|
#define BG_TOP NS_STYLE_IMAGELAYER_POSITION_TOP
|
|
#define BG_BOTTOM NS_STYLE_IMAGELAYER_POSITION_BOTTOM
|
|
#define BG_LEFT NS_STYLE_IMAGELAYER_POSITION_LEFT
|
|
#define BG_RIGHT NS_STYLE_IMAGELAYER_POSITION_RIGHT
|
|
#define BG_CTB (BG_CENTER | BG_TOP | BG_BOTTOM)
|
|
#define BG_TB (BG_TOP | BG_BOTTOM)
|
|
#define BG_CLR (BG_CENTER | BG_LEFT | BG_RIGHT)
|
|
#define BG_LR (BG_LEFT | BG_RIGHT)
|
|
|
|
CSSParseResult
|
|
CSSParserImpl::ParseBoxProperty(nsCSSValue& aValue,
|
|
nsCSSPropertyID aPropID)
|
|
{
|
|
if (aPropID < 0 || aPropID >= eCSSProperty_COUNT_no_shorthands) {
|
|
MOZ_ASSERT(false, "must only be called for longhand properties");
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
|
|
MOZ_ASSERT(!nsCSSProps::PropHasFlags(aPropID,
|
|
CSS_PROPERTY_VALUE_PARSER_FUNCTION),
|
|
"must only be called for non-function-parsed properties");
|
|
|
|
uint32_t variant = nsCSSProps::ParserVariant(aPropID);
|
|
if (variant == 0) {
|
|
MOZ_ASSERT(false, "must only be called for variant-parsed properties");
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
|
|
if (variant & ~(VARIANT_AHKLP | VARIANT_COLOR | VARIANT_CALC)) {
|
|
MOZ_ASSERT(false, "must only be called for properties that take certain "
|
|
"variants");
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
|
|
const KTableEntry* kwtable = nsCSSProps::kKeywordTableTable[aPropID];
|
|
uint32_t restrictions = nsCSSProps::ValueRestrictions(aPropID);
|
|
|
|
return ParseVariantWithRestrictions(aValue, variant, kwtable, restrictions);
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseSingleValuePropertyByFunction(nsCSSValue& aValue,
|
|
nsCSSPropertyID aPropID)
|
|
{
|
|
switch (aPropID) {
|
|
case eCSSProperty_clip_path:
|
|
return ParseClipPath(aValue);
|
|
case eCSSProperty_contain:
|
|
return ParseContain(aValue);
|
|
case eCSSProperty_font_family:
|
|
return ParseFamily(aValue);
|
|
case eCSSProperty_font_synthesis:
|
|
return ParseFontSynthesis(aValue);
|
|
case eCSSProperty_font_variant_alternates:
|
|
return ParseFontVariantAlternates(aValue);
|
|
case eCSSProperty_font_variant_east_asian:
|
|
return ParseFontVariantEastAsian(aValue);
|
|
case eCSSProperty_font_variant_ligatures:
|
|
return ParseFontVariantLigatures(aValue);
|
|
case eCSSProperty_font_variant_numeric:
|
|
return ParseFontVariantNumeric(aValue);
|
|
case eCSSProperty_font_feature_settings:
|
|
return ParseFontFeatureSettings(aValue);
|
|
case eCSSProperty_font_weight:
|
|
return ParseFontWeight(aValue);
|
|
case eCSSProperty_image_orientation:
|
|
return ParseImageOrientation(aValue);
|
|
case eCSSProperty_list_style_type:
|
|
return ParseListStyleType(aValue);
|
|
case eCSSProperty_scroll_snap_points_x:
|
|
return ParseScrollSnapPoints(aValue, eCSSProperty_scroll_snap_points_x);
|
|
case eCSSProperty_scroll_snap_points_y:
|
|
return ParseScrollSnapPoints(aValue, eCSSProperty_scroll_snap_points_y);
|
|
case eCSSProperty_scroll_snap_destination:
|
|
return ParseScrollSnapDestination(aValue);
|
|
case eCSSProperty_scroll_snap_coordinate:
|
|
return ParseScrollSnapCoordinate(aValue);
|
|
case eCSSProperty_shape_outside:
|
|
return ParseShapeOutside(aValue);
|
|
case eCSSProperty_text_align:
|
|
return ParseTextAlign(aValue);
|
|
case eCSSProperty_text_align_last:
|
|
return ParseTextAlignLast(aValue);
|
|
case eCSSProperty_text_decoration_line:
|
|
return ParseTextDecorationLine(aValue);
|
|
case eCSSProperty_text_combine_upright:
|
|
return ParseTextCombineUpright(aValue);
|
|
case eCSSProperty_text_emphasis_position:
|
|
return ParseTextEmphasisPosition(aValue);
|
|
case eCSSProperty_text_emphasis_style:
|
|
return ParseTextEmphasisStyle(aValue);
|
|
case eCSSProperty_text_overflow:
|
|
return ParseTextOverflow(aValue);
|
|
case eCSSProperty_touch_action:
|
|
return ParseTouchAction(aValue);
|
|
default:
|
|
MOZ_ASSERT(false, "should not reach here");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CSSParseResult
|
|
CSSParserImpl::ParseSingleValueProperty(nsCSSValue& aValue,
|
|
nsCSSPropertyID aPropID)
|
|
{
|
|
if (aPropID == eCSSPropertyExtra_x_none_value) {
|
|
return ParseVariant(aValue, VARIANT_NONE | VARIANT_INHERIT, nullptr);
|
|
}
|
|
|
|
if (aPropID == eCSSPropertyExtra_x_auto_value) {
|
|
return ParseVariant(aValue, VARIANT_AUTO | VARIANT_INHERIT, nullptr);
|
|
}
|
|
|
|
if (aPropID < 0 || aPropID >= eCSSProperty_COUNT_no_shorthands) {
|
|
MOZ_ASSERT(false, "not a single value property");
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
|
|
if (nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_VALUE_PARSER_FUNCTION)) {
|
|
uint32_t lineBefore, colBefore;
|
|
if (!GetNextTokenLocation(true, &lineBefore, &colBefore)) {
|
|
// We're at EOF before parsing.
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
|
|
if (ParseSingleValuePropertyByFunction(aValue, aPropID)) {
|
|
return CSSParseResult::Ok;
|
|
}
|
|
|
|
uint32_t lineAfter, colAfter;
|
|
if (!GetNextTokenLocation(true, &lineAfter, &colAfter) ||
|
|
lineAfter != lineBefore ||
|
|
colAfter != colBefore) {
|
|
// Any single token value that was invalid will have been pushed back,
|
|
// so GetNextTokenLocation encountering EOF means we failed while
|
|
// parsing a multi-token value.
|
|
return CSSParseResult::Error;
|
|
}
|
|
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
|
|
uint32_t variant = nsCSSProps::ParserVariant(aPropID);
|
|
if (variant == 0) {
|
|
MOZ_ASSERT(false, "not a single value property");
|
|
return CSSParseResult::NotFound;
|
|
}
|
|
|
|
const KTableEntry* kwtable = nsCSSProps::kKeywordTableTable[aPropID];
|
|
uint32_t restrictions = nsCSSProps::ValueRestrictions(aPropID);
|
|
return ParseVariantWithRestrictions(aValue, variant, kwtable, restrictions);
|
|
}
|
|
|
|
// font-descriptor: descriptor ':' value ';'
|
|
// caller has advanced mToken to point at the descriptor
|
|
bool
|
|
CSSParserImpl::ParseFontDescriptorValue(nsCSSFontDesc aDescID,
|
|
nsCSSValue& aValue)
|
|
{
|
|
switch (aDescID) {
|
|
// These four are similar to the properties of the same name,
|
|
// possibly with more restrictions on the values they can take.
|
|
case eCSSFontDesc_Family: {
|
|
nsCSSValue value;
|
|
if (!ParseFamily(value) ||
|
|
value.GetUnit() != eCSSUnit_FontFamilyList)
|
|
return false;
|
|
|
|
// name can only be a single, non-generic name
|
|
const FontFamilyList* f = value.GetFontFamilyListValue();
|
|
const nsTArray<FontFamilyName>& fontlist = f->GetFontlist();
|
|
|
|
if (fontlist.Length() != 1 || !fontlist[0].IsNamed()) {
|
|
return false;
|
|
}
|
|
|
|
aValue.SetStringValue(fontlist[0].mName, eCSSUnit_String);
|
|
return true;
|
|
}
|
|
|
|
case eCSSFontDesc_Style:
|
|
// property is VARIANT_HMK|VARIANT_SYSFONT
|
|
return ParseSingleTokenVariant(aValue, VARIANT_KEYWORD | VARIANT_NORMAL,
|
|
nsCSSProps::kFontStyleKTable);
|
|
|
|
case eCSSFontDesc_Display:
|
|
return ParseSingleTokenVariant(aValue, VARIANT_KEYWORD,
|
|
nsCSSProps::kFontDisplayKTable);
|
|
|
|
case eCSSFontDesc_Weight:
|
|
return (ParseFontWeight(aValue) &&
|
|
aValue.GetUnit() != eCSSUnit_Inherit &&
|
|
aValue.GetUnit() != eCSSUnit_Initial &&
|
|
aValue.GetUnit() != eCSSUnit_Unset &&
|
|
(aValue.GetUnit() != eCSSUnit_Enumerated ||
|
|
(aValue.GetIntValue() != NS_STYLE_FONT_WEIGHT_BOLDER &&
|
|
aValue.GetIntValue() != NS_STYLE_FONT_WEIGHT_LIGHTER)));
|
|
|
|
case eCSSFontDesc_Stretch:
|
|
// property is VARIANT_HK|VARIANT_SYSFONT
|
|
return ParseSingleTokenVariant(aValue, VARIANT_KEYWORD,
|
|
nsCSSProps::kFontStretchKTable);
|
|
|
|
// These two are unique to @font-face and have their own special grammar.
|
|
case eCSSFontDesc_Src:
|
|
return ParseFontSrc(aValue);
|
|
|
|
case eCSSFontDesc_UnicodeRange:
|
|
return ParseFontRanges(aValue);
|
|
|
|
case eCSSFontDesc_FontFeatureSettings:
|
|
return ParseFontFeatureSettings(aValue);
|
|
|
|
case eCSSFontDesc_FontLanguageOverride:
|
|
return ParseSingleTokenVariant(aValue, VARIANT_NORMAL | VARIANT_STRING,
|
|
nullptr);
|
|
|
|
case eCSSFontDesc_UNKNOWN:
|
|
case eCSSFontDesc_COUNT:
|
|
NS_NOTREACHED("bad nsCSSFontDesc code");
|
|
}
|
|
// explicitly do NOT have a default case to let the compiler
|
|
// help find missing descriptors
|
|
return false;
|
|
}
|
|
|
|
static nsCSSValue
|
|
BoxPositionMaskToCSSValue(int32_t aMask, bool isX)
|
|
{
|
|
int32_t val = NS_STYLE_IMAGELAYER_POSITION_CENTER;
|
|
if (isX) {
|
|
if (aMask & BG_LEFT) {
|
|
val = NS_STYLE_IMAGELAYER_POSITION_LEFT;
|
|
}
|
|
else if (aMask & BG_RIGHT) {
|
|
val = NS_STYLE_IMAGELAYER_POSITION_RIGHT;
|
|
}
|
|
}
|
|
else {
|
|
if (aMask & BG_TOP) {
|
|
val = NS_STYLE_IMAGELAYER_POSITION_TOP;
|
|
}
|
|
else if (aMask & BG_BOTTOM) {
|
|
val = NS_STYLE_IMAGELAYER_POSITION_BOTTOM;
|
|
}
|
|
}
|
|
|
|
return nsCSSValue(val, eCSSUnit_Enumerated);
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseImageLayers(const nsCSSPropertyID aTable[])
|
|
{
|
|
nsAutoParseCompoundProperty compound(this);
|
|
|
|
// background-color can only be set once, so it's not a list.
|
|
nsCSSValue color;
|
|
|
|
// Check first for inherit/initial/unset.
|
|
if (ParseSingleTokenVariant(color, VARIANT_INHERIT, nullptr)) {
|
|
// must be alone
|
|
for (const nsCSSPropertyID* subprops =
|
|
nsCSSProps::SubpropertyEntryFor(aTable[nsStyleImageLayers::shorthand]);
|
|
*subprops != eCSSProperty_UNKNOWN; ++subprops) {
|
|
AppendValue(*subprops, color);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
nsCSSValue image, repeat, attachment, clip, origin, positionX, positionY, size,
|
|
composite, maskMode;
|
|
ImageLayersShorthandParseState state(color, image.SetListValue(),
|
|
repeat.SetPairListValue(),
|
|
attachment.SetListValue(), clip.SetListValue(),
|
|
origin.SetListValue(),
|
|
positionX.SetListValue(), positionY.SetListValue(),
|
|
size.SetPairListValue(), composite.SetListValue(),
|
|
maskMode.SetListValue());
|
|
|
|
for (;;) {
|
|
if (!ParseImageLayersItem(state, aTable)) {
|
|
return false;
|
|
}
|
|
|
|
// If we saw a color, this must be the last item.
|
|
if (color.GetUnit() != eCSSUnit_Null) {
|
|
MOZ_ASSERT(aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN);
|
|
break;
|
|
}
|
|
|
|
// If there's a comma, expect another item.
|
|
if (!ExpectSymbol(',', true)) {
|
|
break;
|
|
}
|
|
|
|
#define APPENDNEXT(propID_, propMember_, propType_) \
|
|
if (aTable[propID_] != eCSSProperty_UNKNOWN) { \
|
|
propMember_->mNext = new propType_; \
|
|
propMember_ = propMember_->mNext; \
|
|
}
|
|
// Chain another entry on all the lists.
|
|
APPENDNEXT(nsStyleImageLayers::image, state.mImage,
|
|
nsCSSValueList);
|
|
APPENDNEXT(nsStyleImageLayers::repeat, state.mRepeat,
|
|
nsCSSValuePairList);
|
|
APPENDNEXT(nsStyleImageLayers::clip, state.mClip,
|
|
nsCSSValueList);
|
|
APPENDNEXT(nsStyleImageLayers::origin, state.mOrigin,
|
|
nsCSSValueList);
|
|
APPENDNEXT(nsStyleImageLayers::positionX, state.mPositionX,
|
|
nsCSSValueList);
|
|
APPENDNEXT(nsStyleImageLayers::positionY, state.mPositionY,
|
|
nsCSSValueList);
|
|
APPENDNEXT(nsStyleImageLayers::size, state.mSize,
|
|
nsCSSValuePairList);
|
|
APPENDNEXT(nsStyleImageLayers::attachment, state.mAttachment,
|
|
nsCSSValueList);
|
|
APPENDNEXT(nsStyleImageLayers::maskMode, state.mMode,
|
|
nsCSSValueList);
|
|
APPENDNEXT(nsStyleImageLayers::composite, state.mComposite,
|
|
nsCSSValueList);
|
|
#undef APPENDNEXT
|
|
}
|
|
|
|
// If we get to this point without seeing a color, provide a default.
|
|
if (aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) {
|
|
if (color.GetUnit() == eCSSUnit_Null) {
|
|
color.SetIntegerColorValue(NS_RGBA(0,0,0,0), eCSSUnit_RGBAColor);
|
|
}
|
|
}
|
|
|
|
#define APPENDVALUE(propID_, propValue_) \
|
|
if (propID_ != eCSSProperty_UNKNOWN) { \
|
|
AppendValue(propID_, propValue_); \
|
|
}
|
|
|
|
APPENDVALUE(aTable[nsStyleImageLayers::image], image);
|
|
APPENDVALUE(aTable[nsStyleImageLayers::repeat], repeat);
|
|
APPENDVALUE(aTable[nsStyleImageLayers::clip], clip);
|
|
APPENDVALUE(aTable[nsStyleImageLayers::origin], origin);
|
|
APPENDVALUE(aTable[nsStyleImageLayers::positionX], positionX);
|
|
APPENDVALUE(aTable[nsStyleImageLayers::positionY], positionY);
|
|
APPENDVALUE(aTable[nsStyleImageLayers::size], size);
|
|
APPENDVALUE(aTable[nsStyleImageLayers::color], color);
|
|
APPENDVALUE(aTable[nsStyleImageLayers::attachment], attachment);
|
|
APPENDVALUE(aTable[nsStyleImageLayers::maskMode], maskMode);
|
|
APPENDVALUE(aTable[nsStyleImageLayers::composite], composite);
|
|
|
|
#undef APPENDVALUE
|
|
|
|
return true;
|
|
}
|
|
|
|
// Helper for ParseImageLayersItem. Returns true if the passed-in nsCSSToken is
|
|
// a function which is accepted for background-image.
|
|
bool
|
|
CSSParserImpl::IsFunctionTokenValidForImageLayerImage(
|
|
const nsCSSToken& aToken) const
|
|
{
|
|
MOZ_ASSERT(aToken.mType == eCSSToken_Function,
|
|
"Should only be called for function-typed tokens");
|
|
|
|
const nsAString& funcName = aToken.mIdent;
|
|
|
|
return funcName.LowerCaseEqualsLiteral("linear-gradient") ||
|
|
funcName.LowerCaseEqualsLiteral("radial-gradient") ||
|
|
funcName.LowerCaseEqualsLiteral("repeating-linear-gradient") ||
|
|
funcName.LowerCaseEqualsLiteral("repeating-radial-gradient") ||
|
|
funcName.LowerCaseEqualsLiteral("-moz-linear-gradient") ||
|
|
funcName.LowerCaseEqualsLiteral("-moz-radial-gradient") ||
|
|
funcName.LowerCaseEqualsLiteral("-moz-repeating-linear-gradient") ||
|
|
funcName.LowerCaseEqualsLiteral("-moz-repeating-radial-gradient") ||
|
|
funcName.LowerCaseEqualsLiteral("-moz-image-rect") ||
|
|
funcName.LowerCaseEqualsLiteral("-moz-element") ||
|
|
((sWebkitPrefixedAliasesEnabled || ShouldUseUnprefixingService()) &&
|
|
(funcName.LowerCaseEqualsLiteral("-webkit-gradient") ||
|
|
funcName.LowerCaseEqualsLiteral("-webkit-linear-gradient") ||
|
|
funcName.LowerCaseEqualsLiteral("-webkit-radial-gradient") ||
|
|
funcName.LowerCaseEqualsLiteral("-webkit-repeating-linear-gradient") ||
|
|
funcName.LowerCaseEqualsLiteral("-webkit-repeating-radial-gradient")));
|
|
}
|
|
|
|
// Parse one item of the background shorthand property.
|
|
bool
|
|
CSSParserImpl::ParseImageLayersItem(
|
|
CSSParserImpl::ImageLayersShorthandParseState& aState,
|
|
const nsCSSPropertyID aTable[])
|
|
{
|
|
// Fill in the values that the shorthand will set if we don't find
|
|
// other values.
|
|
aState.mImage->mValue.SetNoneValue();
|
|
aState.mAttachment->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ATTACHMENT_SCROLL,
|
|
eCSSUnit_Enumerated);
|
|
aState.mClip->mValue.SetIntValue(NS_STYLE_IMAGELAYER_CLIP_BORDER,
|
|
eCSSUnit_Enumerated);
|
|
|
|
aState.mRepeat->mXValue.SetIntValue(NS_STYLE_IMAGELAYER_REPEAT_REPEAT,
|
|
eCSSUnit_Enumerated);
|
|
aState.mRepeat->mYValue.Reset();
|
|
|
|
RefPtr<nsCSSValue::Array> positionXArr = nsCSSValue::Array::Create(2);
|
|
RefPtr<nsCSSValue::Array> positionYArr = nsCSSValue::Array::Create(2);
|
|
aState.mPositionX->mValue.SetArrayValue(positionXArr, eCSSUnit_Array);
|
|
aState.mPositionY->mValue.SetArrayValue(positionYArr, eCSSUnit_Array);
|
|
|
|
if (eCSSProperty_mask == aTable[nsStyleImageLayers::shorthand]) {
|
|
aState.mOrigin->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ORIGIN_BORDER,
|
|
eCSSUnit_Enumerated);
|
|
aState.mRepeat->mXValue.SetIntValue(NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT,
|
|
eCSSUnit_Enumerated);
|
|
|
|
positionXArr->Item(1).SetPercentValue(0.5f);
|
|
positionYArr->Item(1).SetPercentValue(0.5f);
|
|
} else {
|
|
aState.mOrigin->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ORIGIN_PADDING,
|
|
eCSSUnit_Enumerated);
|
|
aState.mRepeat->mXValue.SetIntValue(NS_STYLE_IMAGELAYER_REPEAT_REPEAT,
|
|
eCSSUnit_Enumerated);
|
|
|
|
positionXArr->Item(1).SetPercentValue(0.0f);
|
|
positionYArr->Item(1).SetPercentValue(0.0f);
|
|
}
|
|
|
|
aState.mSize->mXValue.SetAutoValue();
|
|
aState.mSize->mYValue.SetAutoValue();
|
|
aState.mComposite->mValue.SetIntValue(NS_STYLE_MASK_COMPOSITE_ADD,
|
|
eCSSUnit_Enumerated);
|
|
aState.mMode->mValue.SetIntValue(NS_STYLE_MASK_MODE_MATCH_SOURCE,
|
|
eCSSUnit_Enumerated);
|
|
bool haveColor = false,
|
|
haveImage = false,
|
|
haveRepeat = false,
|
|
haveAttach = false,
|
|
havePositionAndSize = false,
|
|
haveOrigin = false,
|
|
haveComposite = false,
|
|
haveMode = false,
|
|
haveSomething = false;
|
|
|
|
while (GetToken(true)) {
|
|
nsCSSTokenType tt = mToken.mType;
|
|
UngetToken(); // ...but we'll still cheat and use mToken
|
|
if (tt == eCSSToken_Symbol) {
|
|
// ExpectEndProperty only looks for symbols, and nothing else will
|
|
// show up as one.
|
|
break;
|
|
}
|
|
|
|
if (tt == eCSSToken_Ident) {
|
|
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
|
|
int32_t dummy;
|
|
if (keyword == eCSSKeyword_inherit ||
|
|
keyword == eCSSKeyword_initial ||
|
|
keyword == eCSSKeyword_unset) {
|
|
return false;
|
|
} else if (keyword == eCSSKeyword_none) {
|
|
if (haveImage)
|
|
return false;
|
|
haveImage = true;
|
|
if (ParseSingleValueProperty(aState.mImage->mValue,
|
|
aTable[nsStyleImageLayers::image]) !=
|
|
CSSParseResult::Ok) {
|
|
NS_NOTREACHED("should be able to parse");
|
|
return false;
|
|
}
|
|
} else if (aTable[nsStyleImageLayers::attachment] !=
|
|
eCSSProperty_UNKNOWN &&
|
|
nsCSSProps::FindKeyword(keyword,
|
|
nsCSSProps::kImageLayerAttachmentKTable, dummy)) {
|
|
if (haveAttach)
|
|
return false;
|
|
haveAttach = true;
|
|
if (ParseSingleValueProperty(aState.mAttachment->mValue,
|
|
aTable[nsStyleImageLayers::attachment]) !=
|
|
CSSParseResult::Ok) {
|
|
NS_NOTREACHED("should be able to parse");
|
|
return false;
|
|
}
|
|
} else if (nsCSSProps::FindKeyword(keyword,
|
|
nsCSSProps::kImageLayerRepeatKTable, dummy)) {
|
|
if (haveRepeat)
|
|
return false;
|
|
haveRepeat = true;
|
|
nsCSSValuePair scratch;
|
|
if (!ParseImageLayerRepeatValues(scratch)) {
|
|
NS_NOTREACHED("should be able to parse");
|
|
return false;
|
|
}
|
|
aState.mRepeat->mXValue = scratch.mXValue;
|
|
aState.mRepeat->mYValue = scratch.mYValue;
|
|
} else if (nsCSSProps::FindKeyword(keyword,
|
|
nsCSSProps::kImageLayerPositionKTable, dummy)) {
|
|
if (havePositionAndSize)
|
|
return false;
|
|
havePositionAndSize = true;
|
|
|
|
if (!ParsePositionValueSeparateCoords(aState.mPositionX->mValue,
|
|
aState.mPositionY->mValue)) {
|
|
return false;
|
|
}
|
|
if (ExpectSymbol('/', true)) {
|
|
nsCSSValuePair scratch;
|
|
if (!ParseImageLayerSizeValues(scratch)) {
|
|
return false;
|
|
}
|
|
aState.mSize->mXValue = scratch.mXValue;
|
|
aState.mSize->mYValue = scratch.mYValue;
|
|
}
|
|
} else if (nsCSSProps::FindKeyword(keyword,
|
|
nsCSSProps::kImageLayerOriginKTable, dummy)) {
|
|
if (haveOrigin)
|
|
return false;
|
|
haveOrigin = true;
|
|
if (ParseSingleValueProperty(aState.mOrigin->mValue,
|
|
aTable[nsStyleImageLayers::origin]) !=
|
|
CSSParseResult::Ok) {
|
|
NS_NOTREACHED("should be able to parse");
|
|
return false;
|
|
}
|
|
|
|
// The spec allows a second box value (for background-clip),
|
|
// immediately following the first one (for background-origin).
|
|
|
|
#ifdef DEBUG
|
|
for (size_t i = 0; nsCSSProps::kImageLayerOriginKTable[i].mValue != -1; i++) {
|
|
// For each keyword & value in kOriginKTable, ensure that
|
|
// kBackgroundKTable has a matching entry at the same position.
|
|
MOZ_ASSERT(nsCSSProps::kImageLayerOriginKTable[i].mKeyword ==
|
|
nsCSSProps::kBackgroundClipKTable[i].mKeyword);
|
|
MOZ_ASSERT(nsCSSProps::kImageLayerOriginKTable[i].mValue ==
|
|
nsCSSProps::kBackgroundClipKTable[i].mValue);
|
|
}
|
|
#endif
|
|
static_assert(NS_STYLE_IMAGELAYER_CLIP_BORDER ==
|
|
NS_STYLE_IMAGELAYER_ORIGIN_BORDER &&
|
|
NS_STYLE_IMAGELAYER_CLIP_PADDING ==
|
|
NS_STYLE_IMAGELAYER_ORIGIN_PADDING &&
|
|
NS_STYLE_IMAGELAYER_CLIP_CONTENT ==
|
|
NS_STYLE_IMAGELAYER_ORIGIN_CONTENT,
|
|
"bg-clip and bg-origin style constants must agree");
|
|
|
|
CSSParseResult result =
|
|
ParseSingleValueProperty(aState.mClip->mValue,
|
|
aTable[nsStyleImageLayers::clip]);
|
|
MOZ_ASSERT(result != CSSParseResult::Error,
|
|
"how can failing to parse a single background-clip value "
|
|
"consume tokens?");
|
|
if (result == CSSParseResult::NotFound) {
|
|
// When exactly one <box> value is set, it is used for both
|
|
// 'background-origin' and 'background-clip'.
|
|
// See assertions above showing these values are compatible.
|
|
aState.mClip->mValue = aState.mOrigin->mValue;
|
|
}
|
|
} else if (aTable[nsStyleImageLayers::composite] != eCSSProperty_UNKNOWN &&
|
|
nsCSSProps::FindKeyword(keyword,
|
|
nsCSSProps::kImageLayerCompositeKTable, dummy)) {
|
|
if (haveComposite)
|
|
return false;
|
|
haveComposite = true;
|
|
if (ParseSingleValueProperty(aState.mComposite->mValue,
|
|
aTable[nsStyleImageLayers::composite]) !=
|
|
CSSParseResult::Ok) {
|
|
NS_NOTREACHED("should be able to parse");
|
|
return false;
|
|
}
|
|
} else if (aTable[nsStyleImageLayers::maskMode] != eCSSProperty_UNKNOWN &&
|
|
nsCSSProps::FindKeyword(keyword,
|
|
nsCSSProps::kImageLayerModeKTable, dummy)) {
|
|
if (haveMode)
|
|
return false;
|
|
haveMode = true;
|
|
if (ParseSingleValueProperty(aState.mMode->mValue,
|
|
aTable[nsStyleImageLayers::maskMode]) !=
|
|
CSSParseResult::Ok) {
|
|
NS_NOTREACHED("should be able to parse");
|
|
return false;
|
|
}
|
|
} else if (aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) {
|
|
if (haveColor)
|
|
return false;
|
|
haveColor = true;
|
|
if (ParseSingleValueProperty(aState.mColor,
|
|
aTable[nsStyleImageLayers::color]) !=
|
|
CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
} else if (tt == eCSSToken_URL ||
|
|
(tt == eCSSToken_Function &&
|
|
IsFunctionTokenValidForImageLayerImage(mToken))) {
|
|
if (haveImage)
|
|
return false;
|
|
haveImage = true;
|
|
if (ParseSingleValueProperty(aState.mImage->mValue,
|
|
aTable[nsStyleImageLayers::image]) !=
|
|
CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
} else if (tt == eCSSToken_Dimension ||
|
|
tt == eCSSToken_Number ||
|
|
tt == eCSSToken_Percentage ||
|
|
(tt == eCSSToken_Function &&
|
|
(mToken.mIdent.LowerCaseEqualsLiteral("calc") ||
|
|
mToken.mIdent.LowerCaseEqualsLiteral("-moz-calc")))) {
|
|
if (havePositionAndSize)
|
|
return false;
|
|
havePositionAndSize = true;
|
|
if (!ParsePositionValueSeparateCoords(aState.mPositionX->mValue,
|
|
aState.mPositionY->mValue)) {
|
|
return false;
|
|
}
|
|
if (ExpectSymbol('/', true)) {
|
|
nsCSSValuePair scratch;
|
|
if (!ParseImageLayerSizeValues(scratch)) {
|
|
return false;
|
|
}
|
|
aState.mSize->mXValue = scratch.mXValue;
|
|
aState.mSize->mYValue = scratch.mYValue;
|
|
}
|
|
} else if (aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) {
|
|
if (haveColor)
|
|
return false;
|
|
haveColor = true;
|
|
// Note: This parses 'inherit', 'initial' and 'unset', but
|
|
// we've already checked for them, so it's ok.
|
|
if (ParseSingleValueProperty(aState.mColor,
|
|
aTable[nsStyleImageLayers::color]) !=
|
|
CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
haveSomething = true;
|
|
}
|
|
|
|
return haveSomething;
|
|
}
|
|
|
|
// This function is very similar to ParseScrollSnapCoordinate,
|
|
// ParseImageLayerPosition, and ParseImageLayersSize.
|
|
bool
|
|
CSSParserImpl::ParseValueList(nsCSSPropertyID aPropID)
|
|
{
|
|
// aPropID is a single value prop-id
|
|
nsCSSValue value;
|
|
// 'initial', 'inherit' and 'unset' stand alone, no list permitted.
|
|
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
|
|
nsCSSValueList* item = value.SetListValue();
|
|
for (;;) {
|
|
if (ParseSingleValueProperty(item->mValue, aPropID) !=
|
|
CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
if (!ExpectSymbol(',', true)) {
|
|
break;
|
|
}
|
|
item->mNext = new nsCSSValueList;
|
|
item = item->mNext;
|
|
}
|
|
}
|
|
AppendValue(aPropID, value);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseImageLayerRepeat(nsCSSPropertyID aPropID)
|
|
{
|
|
nsCSSValue value;
|
|
// 'initial', 'inherit' and 'unset' stand alone, no list permitted.
|
|
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
|
|
nsCSSValuePair valuePair;
|
|
if (!ParseImageLayerRepeatValues(valuePair)) {
|
|
return false;
|
|
}
|
|
nsCSSValuePairList* item = value.SetPairListValue();
|
|
for (;;) {
|
|
item->mXValue = valuePair.mXValue;
|
|
item->mYValue = valuePair.mYValue;
|
|
if (!ExpectSymbol(',', true)) {
|
|
break;
|
|
}
|
|
if (!ParseImageLayerRepeatValues(valuePair)) {
|
|
return false;
|
|
}
|
|
item->mNext = new nsCSSValuePairList;
|
|
item = item->mNext;
|
|
}
|
|
}
|
|
|
|
AppendValue(aPropID, value);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseImageLayerRepeatValues(nsCSSValuePair& aValue)
|
|
{
|
|
nsCSSValue& xValue = aValue.mXValue;
|
|
nsCSSValue& yValue = aValue.mYValue;
|
|
|
|
if (ParseEnum(xValue, nsCSSProps::kImageLayerRepeatKTable)) {
|
|
int32_t value = xValue.GetIntValue();
|
|
// For single values set yValue as eCSSUnit_Null.
|
|
if (value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X ||
|
|
value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y ||
|
|
!ParseEnum(yValue, nsCSSProps::kImageLayerRepeatPartKTable)) {
|
|
// the caller will fail cases like "repeat-x no-repeat"
|
|
// by expecting a list separator or an end property.
|
|
yValue.Reset();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseImageLayerPosition(const nsCSSPropertyID aTable[])
|
|
{
|
|
// 'initial', 'inherit' and 'unset' stand alone, no list permitted.
|
|
nsCSSValue position;
|
|
if (ParseSingleTokenVariant(position, VARIANT_INHERIT, nullptr)) {
|
|
AppendValue(aTable[nsStyleImageLayers::positionX], position);
|
|
AppendValue(aTable[nsStyleImageLayers::positionY], position);
|
|
return true;
|
|
}
|
|
|
|
nsCSSValue itemValueX;
|
|
nsCSSValue itemValueY;
|
|
if (!ParsePositionValueSeparateCoords(itemValueX, itemValueY)) {
|
|
return false;
|
|
}
|
|
|
|
nsCSSValue valueX;
|
|
nsCSSValue valueY;
|
|
nsCSSValueList* itemX = valueX.SetListValue();
|
|
nsCSSValueList* itemY = valueY.SetListValue();
|
|
for (;;) {
|
|
itemX->mValue = itemValueX;
|
|
itemY->mValue = itemValueY;
|
|
if (!ExpectSymbol(',', true)) {
|
|
break;
|
|
}
|
|
if (!ParsePositionValueSeparateCoords(itemValueX, itemValueY)) {
|
|
return false;
|
|
}
|
|
itemX->mNext = new nsCSSValueList;
|
|
itemY->mNext = new nsCSSValueList;
|
|
itemX = itemX->mNext;
|
|
itemY = itemY->mNext;
|
|
}
|
|
AppendValue(aTable[nsStyleImageLayers::positionX], valueX);
|
|
AppendValue(aTable[nsStyleImageLayers::positionY], valueY);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseImageLayerPositionCoord(nsCSSPropertyID aPropID, bool aIsHorizontal)
|
|
{
|
|
nsCSSValue value;
|
|
// 'initial', 'inherit' and 'unset' stand alone, no list permitted.
|
|
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
|
|
nsCSSValue itemValue;
|
|
if (!ParseImageLayerPositionCoordItem(itemValue, aIsHorizontal)) {
|
|
return false;
|
|
}
|
|
nsCSSValueList* item = value.SetListValue();
|
|
for (;;) {
|
|
item->mValue = itemValue;
|
|
if (!ExpectSymbol(',', true)) {
|
|
break;
|
|
}
|
|
if (!ParseImageLayerPositionCoordItem(itemValue, aIsHorizontal)) {
|
|
return false;
|
|
}
|
|
item->mNext = new nsCSSValueList;
|
|
item = item->mNext;
|
|
}
|
|
}
|
|
AppendValue(aPropID, value);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* BoxPositionMaskToCSSValue and ParseBoxPositionValues are used
|
|
* for parsing the CSS 2.1 background-position syntax (which has at
|
|
* most two values). (Compare to the css3-background syntax which
|
|
* takes up to four values.) Some current CSS specifications that
|
|
* use background-position-like syntax still use this old syntax.
|
|
**
|
|
* Parses two values that correspond to positions in a box. These can be
|
|
* values corresponding to percentages of the box, raw offsets, or keywords
|
|
* like "top," "left center," etc.
|
|
*
|
|
* @param aOut The nsCSSValuePair in which to place the result.
|
|
* @param aAcceptsInherit If true, 'inherit', 'initial' and 'unset' are
|
|
* legal values
|
|
* @param aAllowExplicitCenter If true, 'center' is a legal value
|
|
* @return Whether or not the operation succeeded.
|
|
*/
|
|
bool CSSParserImpl::ParseBoxPositionValues(nsCSSValuePair &aOut,
|
|
bool aAcceptsInherit,
|
|
bool aAllowExplicitCenter)
|
|
{
|
|
// First try a percentage or a length value
|
|
nsCSSValue &xValue = aOut.mXValue,
|
|
&yValue = aOut.mYValue;
|
|
int32_t variantMask =
|
|
(aAcceptsInherit ? VARIANT_INHERIT : 0) | VARIANT_LP | VARIANT_CALC;
|
|
CSSParseResult result = ParseVariant(xValue, variantMask, nullptr);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
} else if (result == CSSParseResult::Ok) {
|
|
if (eCSSUnit_Inherit == xValue.GetUnit() ||
|
|
eCSSUnit_Initial == xValue.GetUnit() ||
|
|
eCSSUnit_Unset == xValue.GetUnit()) { // both are inherit, initial or unset
|
|
yValue = xValue;
|
|
return true;
|
|
}
|
|
// We have one percentage/length/calc. Get the optional second
|
|
// percentage/length/calc/keyword.
|
|
result = ParseVariant(yValue, VARIANT_LP | VARIANT_CALC, nullptr);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
} else if (result == CSSParseResult::Ok) {
|
|
// We have two numbers
|
|
return true;
|
|
}
|
|
|
|
if (ParseEnum(yValue, nsCSSProps::kImageLayerPositionKTable)) {
|
|
int32_t yVal = yValue.GetIntValue();
|
|
if (!(yVal & BG_CTB)) {
|
|
// The second keyword can only be 'center', 'top', or 'bottom'
|
|
return false;
|
|
}
|
|
yValue = BoxPositionMaskToCSSValue(yVal, false);
|
|
return true;
|
|
}
|
|
|
|
// If only one percentage or length value is given, it sets the
|
|
// horizontal position only, and the vertical position will be 50%.
|
|
yValue.SetPercentValue(0.5f);
|
|
return true;
|
|
}
|
|
|
|
// Now try keywords. We do this manually to allow for the first
|
|
// appearance of "center" to apply to the either the x or y
|
|
// position (it's ambiguous so we have to disambiguate). Each
|
|
// allowed keyword value is assigned it's own bit. We don't allow
|
|
// any duplicate keywords other than center. We try to get two
|
|
// keywords but it's okay if there is only one.
|
|
int32_t mask = 0;
|
|
if (ParseEnum(xValue, nsCSSProps::kImageLayerPositionKTable)) {
|
|
int32_t bit = xValue.GetIntValue();
|
|
mask |= bit;
|
|
if (ParseEnum(xValue, nsCSSProps::kImageLayerPositionKTable)) {
|
|
bit = xValue.GetIntValue();
|
|
if (mask & (bit & ~BG_CENTER)) {
|
|
// Only the 'center' keyword can be duplicated.
|
|
return false;
|
|
}
|
|
mask |= bit;
|
|
}
|
|
else {
|
|
// Only one keyword. See if we have a length, percentage, or calc.
|
|
result = ParseVariant(yValue, VARIANT_LP | VARIANT_CALC, nullptr);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
} else if (result == CSSParseResult::Ok) {
|
|
if (!(mask & BG_CLR)) {
|
|
// The first keyword can only be 'center', 'left', or 'right'
|
|
return false;
|
|
}
|
|
|
|
xValue = BoxPositionMaskToCSSValue(mask, true);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for bad input. Bad input consists of no matching keywords,
|
|
// or pairs of x keywords or pairs of y keywords.
|
|
if ((mask == 0) || (mask == (BG_TOP | BG_BOTTOM)) ||
|
|
(mask == (BG_LEFT | BG_RIGHT)) ||
|
|
(!aAllowExplicitCenter && (mask & BG_CENTER))) {
|
|
return false;
|
|
}
|
|
|
|
// Create style values
|
|
xValue = BoxPositionMaskToCSSValue(mask, true);
|
|
yValue = BoxPositionMaskToCSSValue(mask, false);
|
|
return true;
|
|
}
|
|
|
|
// Parses a CSS <position> value, for e.g. the 'background-position' property.
|
|
// Spec reference: http://www.w3.org/TR/css3-background/#ltpositiongt
|
|
// Invariants:
|
|
// - Always produces a four-value array on a successful parse.
|
|
// - The values are: X edge, X offset, Y edge, Y offset.
|
|
// - Edges are always keywords or null.
|
|
// - A |center| edge will not have an offset.
|
|
bool
|
|
CSSParserImpl::ParsePositionValue(nsCSSValue& aOut)
|
|
{
|
|
RefPtr<nsCSSValue::Array> value = nsCSSValue::Array::Create(4);
|
|
aOut.SetArrayValue(value, eCSSUnit_Array);
|
|
|
|
// The following clarifies organisation of the array.
|
|
nsCSSValue &xEdge = value->Item(0),
|
|
&xOffset = value->Item(1),
|
|
&yEdge = value->Item(2),
|
|
&yOffset = value->Item(3);
|
|
|
|
// Parse all the values into the array.
|
|
uint32_t valueCount = 0;
|
|
for (int32_t i = 0; i < 4; i++) {
|
|
CSSParseResult result =
|
|
ParseVariant(value->Item(i), VARIANT_LPCALC | VARIANT_KEYWORD,
|
|
nsCSSProps::kImageLayerPositionKTable);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
} else if (result == CSSParseResult::NotFound) {
|
|
break;
|
|
}
|
|
++valueCount;
|
|
}
|
|
|
|
switch (valueCount) {
|
|
case 4:
|
|
// "If three or four values are given, then each <percentage> or <length>
|
|
// represents an offset and must be preceded by a keyword, which specifies
|
|
// from which edge the offset is given."
|
|
if (eCSSUnit_Enumerated != xEdge.GetUnit() ||
|
|
BG_CENTER == xEdge.GetIntValue() ||
|
|
eCSSUnit_Enumerated == xOffset.GetUnit() ||
|
|
eCSSUnit_Enumerated != yEdge.GetUnit() ||
|
|
BG_CENTER == yEdge.GetIntValue() ||
|
|
eCSSUnit_Enumerated == yOffset.GetUnit()) {
|
|
return false;
|
|
}
|
|
break;
|
|
case 3:
|
|
// "If three or four values are given, then each <percentage> or<length>
|
|
// represents an offset and must be preceded by a keyword, which specifies
|
|
// from which edge the offset is given." ... "If three values are given,
|
|
// the missing offset is assumed to be zero."
|
|
if (eCSSUnit_Enumerated != value->Item(1).GetUnit()) {
|
|
// keyword offset keyword
|
|
// Second value is non-keyword, thus first value must be a non-center
|
|
// keyword.
|
|
if (eCSSUnit_Enumerated != value->Item(0).GetUnit() ||
|
|
BG_CENTER == value->Item(0).GetIntValue()) {
|
|
return false;
|
|
}
|
|
|
|
// Remaining value must be a keyword.
|
|
if (eCSSUnit_Enumerated != value->Item(2).GetUnit()) {
|
|
return false;
|
|
}
|
|
|
|
yOffset.Reset(); // Everything else is in the correct position.
|
|
} else if (eCSSUnit_Enumerated != value->Item(2).GetUnit()) {
|
|
// keyword keyword offset
|
|
// Third value is non-keyword, thus second value must be non-center
|
|
// keyword.
|
|
if (BG_CENTER == value->Item(1).GetIntValue()) {
|
|
return false;
|
|
}
|
|
|
|
// Remaining value must be a keyword.
|
|
if (eCSSUnit_Enumerated != value->Item(0).GetUnit()) {
|
|
return false;
|
|
}
|
|
|
|
// Move the values to the correct position in the array.
|
|
value->Item(3) = value->Item(2); // yOffset
|
|
value->Item(2) = value->Item(1); // yEdge
|
|
value->Item(1).Reset(); // xOffset
|
|
} else {
|
|
return false;
|
|
}
|
|
break;
|
|
case 2:
|
|
// "If two values are given and at least one value is not a keyword, then
|
|
// the first value represents the horizontal position (or offset) and the
|
|
// second represents the vertical position (or offset)"
|
|
if (eCSSUnit_Enumerated == value->Item(0).GetUnit()) {
|
|
if (eCSSUnit_Enumerated == value->Item(1).GetUnit()) {
|
|
// keyword keyword
|
|
value->Item(2) = value->Item(1); // move yEdge to correct position
|
|
xOffset.Reset();
|
|
yOffset.Reset();
|
|
} else {
|
|
// keyword offset
|
|
// First value must represent horizontal position.
|
|
if ((BG_TOP | BG_BOTTOM) & value->Item(0).GetIntValue()) {
|
|
return false;
|
|
}
|
|
value->Item(3) = value->Item(1); // move yOffset to correct position
|
|
xOffset.Reset();
|
|
yEdge.Reset();
|
|
}
|
|
} else {
|
|
if (eCSSUnit_Enumerated == value->Item(1).GetUnit()) {
|
|
// offset keyword
|
|
// Second value must represent vertical position.
|
|
if ((BG_LEFT | BG_RIGHT) & value->Item(1).GetIntValue()) {
|
|
return false;
|
|
}
|
|
value->Item(2) = value->Item(1); // move yEdge to correct position
|
|
value->Item(1) = value->Item(0); // move xOffset to correct position
|
|
xEdge.Reset();
|
|
yOffset.Reset();
|
|
} else {
|
|
// offset offset
|
|
value->Item(3) = value->Item(1); // move yOffset to correct position
|
|
value->Item(1) = value->Item(0); // move xOffset to correct position
|
|
xEdge.Reset();
|
|
yEdge.Reset();
|
|
}
|
|
}
|
|
break;
|
|
case 1:
|
|
// "If only one value is specified, the second value is assumed to be
|
|
// center."
|
|
if (eCSSUnit_Enumerated == value->Item(0).GetUnit()) {
|
|
xOffset.Reset();
|
|
} else {
|
|
value->Item(1) = value->Item(0); // move xOffset to correct position
|
|
xEdge.Reset();
|
|
}
|
|
yEdge.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_CENTER, eCSSUnit_Enumerated);
|
|
yOffset.Reset();
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
// For compatibility with CSS2.1 code the edges can be unspecified.
|
|
// Unspecified edges are recorded as nullptr.
|
|
NS_ASSERTION((eCSSUnit_Enumerated == xEdge.GetUnit() ||
|
|
eCSSUnit_Null == xEdge.GetUnit()) &&
|
|
(eCSSUnit_Enumerated == yEdge.GetUnit() ||
|
|
eCSSUnit_Null == yEdge.GetUnit()) &&
|
|
eCSSUnit_Enumerated != xOffset.GetUnit() &&
|
|
eCSSUnit_Enumerated != yOffset.GetUnit(),
|
|
"Unexpected units");
|
|
|
|
// Keywords in first and second pairs can not both be vertical or
|
|
// horizontal keywords. (eg. left right, bottom top). Additionally,
|
|
// non-center keyword can not be duplicated (eg. left left).
|
|
int32_t xEdgeEnum =
|
|
xEdge.GetUnit() == eCSSUnit_Enumerated ? xEdge.GetIntValue() : 0;
|
|
int32_t yEdgeEnum =
|
|
yEdge.GetUnit() == eCSSUnit_Enumerated ? yEdge.GetIntValue() : 0;
|
|
if ((xEdgeEnum | yEdgeEnum) == (BG_LEFT | BG_RIGHT) ||
|
|
(xEdgeEnum | yEdgeEnum) == (BG_TOP | BG_BOTTOM) ||
|
|
(xEdgeEnum & yEdgeEnum & ~BG_CENTER)) {
|
|
return false;
|
|
}
|
|
|
|
// The values could be in an order that is different than expected.
|
|
// eg. x contains vertical information, y contains horizontal information.
|
|
// Swap if incorrect order.
|
|
if (xEdgeEnum & (BG_TOP | BG_BOTTOM) ||
|
|
yEdgeEnum & (BG_LEFT | BG_RIGHT)) {
|
|
nsCSSValue swapEdge = xEdge;
|
|
nsCSSValue swapOffset = xOffset;
|
|
xEdge = yEdge;
|
|
xOffset = yOffset;
|
|
yEdge = swapEdge;
|
|
yOffset = swapOffset;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
AdjustEdgeOffsetPairForBasicShape(nsCSSValue& aEdge,
|
|
nsCSSValue& aOffset,
|
|
uint8_t aDefaultEdge)
|
|
{
|
|
// 0 length offsets are 0%
|
|
if (aOffset.IsLengthUnit() && aOffset.GetFloatValue() == 0.0) {
|
|
aOffset.SetPercentValue(0);
|
|
}
|
|
|
|
// Default edge is top/left in the 4-value case
|
|
// In case of 1 or 0 values, the default is center,
|
|
// but ParsePositionValue already handles this case
|
|
if (eCSSUnit_Null == aEdge.GetUnit()) {
|
|
aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
|
|
}
|
|
// Default offset is 0%
|
|
if (eCSSUnit_Null == aOffset.GetUnit()) {
|
|
aOffset.SetPercentValue(0.0);
|
|
}
|
|
if (eCSSUnit_Enumerated == aEdge.GetUnit() &&
|
|
eCSSUnit_Percent == aOffset.GetUnit()) {
|
|
switch (aEdge.GetIntValue()) {
|
|
case NS_STYLE_IMAGELAYER_POSITION_CENTER:
|
|
aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
|
|
MOZ_ASSERT(aOffset.GetPercentValue() == 0.0,
|
|
"center cannot be used with an offset");
|
|
aOffset.SetPercentValue(0.5);
|
|
break;
|
|
case NS_STYLE_IMAGELAYER_POSITION_BOTTOM:
|
|
MOZ_ASSERT(aDefaultEdge == NS_STYLE_IMAGELAYER_POSITION_TOP);
|
|
aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
|
|
aOffset.SetPercentValue(1 - aOffset.GetPercentValue());
|
|
break;
|
|
case NS_STYLE_IMAGELAYER_POSITION_RIGHT:
|
|
MOZ_ASSERT(aDefaultEdge == NS_STYLE_IMAGELAYER_POSITION_LEFT);
|
|
aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
|
|
aOffset.SetPercentValue(1 - aOffset.GetPercentValue());
|
|
}
|
|
}
|
|
}
|
|
|
|
// https://drafts.csswg.org/css-shapes/#basic-shape-serialization
|
|
// We set values to defaults while parsing for basic shapes
|
|
// Invariants:
|
|
// - Always produces a four-value array on a successful parse.
|
|
// - The values are: X edge, X offset, Y edge, Y offset
|
|
// - Edges are always keywords (not including center)
|
|
// - Offsets are nonnull
|
|
// - Percentage offsets have keywords folded into them,
|
|
// so "bottom 40%" or "right 20%" will not exist.
|
|
bool
|
|
CSSParserImpl::ParsePositionValueForBasicShape(nsCSSValue& aOut)
|
|
{
|
|
if (!ParsePositionValue(aOut)) {
|
|
return false;
|
|
}
|
|
nsCSSValue::Array* value = aOut.GetArrayValue();
|
|
nsCSSValue& xEdge = value->Item(0);
|
|
nsCSSValue& xOffset = value->Item(1);
|
|
nsCSSValue& yEdge = value->Item(2);
|
|
nsCSSValue& yOffset = value->Item(3);
|
|
// A keyword edge + percent offset pair can be contracted
|
|
// into the percentage with the default value in the edge.
|
|
// Offset lengths which are 0 can also be rewritten as 0%
|
|
AdjustEdgeOffsetPairForBasicShape(xEdge, xOffset,
|
|
NS_STYLE_IMAGELAYER_POSITION_LEFT);
|
|
AdjustEdgeOffsetPairForBasicShape(yEdge, yOffset,
|
|
NS_STYLE_IMAGELAYER_POSITION_TOP);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParsePositionValueSeparateCoords(nsCSSValue& aOutX, nsCSSValue& aOutY)
|
|
{
|
|
nsCSSValue scratch;
|
|
if (!ParsePositionValue(scratch)) {
|
|
return false;
|
|
}
|
|
|
|
// Separate the four values into two pairs of two values for X and Y.
|
|
RefPtr<nsCSSValue::Array> valueX = nsCSSValue::Array::Create(2);
|
|
RefPtr<nsCSSValue::Array> valueY = nsCSSValue::Array::Create(2);
|
|
aOutX.SetArrayValue(valueX, eCSSUnit_Array);
|
|
aOutY.SetArrayValue(valueY, eCSSUnit_Array);
|
|
|
|
RefPtr<nsCSSValue::Array> value = scratch.GetArrayValue();
|
|
valueX->Item(0) = value->Item(0);
|
|
valueX->Item(1) = value->Item(1);
|
|
valueY->Item(0) = value->Item(2);
|
|
valueY->Item(1) = value->Item(3);
|
|
return true;
|
|
}
|
|
|
|
// Parses one item in a list of values for the 'background-position-x' or
|
|
// 'background-position-y' property. Does not support the start/end keywords.
|
|
// Spec reference: https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-x
|
|
bool
|
|
CSSParserImpl::ParseImageLayerPositionCoordItem(nsCSSValue& aOut, bool aIsHorizontal)
|
|
{
|
|
RefPtr<nsCSSValue::Array> value = nsCSSValue::Array::Create(2);
|
|
aOut.SetArrayValue(value, eCSSUnit_Array);
|
|
|
|
nsCSSValue &edge = value->Item(0),
|
|
&offset = value->Item(1);
|
|
|
|
nsCSSValue edgeOrOffset;
|
|
CSSParseResult result =
|
|
ParseVariant(edgeOrOffset, VARIANT_LPCALC | VARIANT_KEYWORD,
|
|
nsCSSProps::kImageLayerPositionKTable);
|
|
if (result != CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
|
|
if (edgeOrOffset.GetUnit() == eCSSUnit_Enumerated) {
|
|
edge = edgeOrOffset;
|
|
|
|
// The edge can be followed by an optional offset.
|
|
result = ParseVariant(offset, VARIANT_LPCALC, nullptr);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
}
|
|
} else {
|
|
offset = edgeOrOffset;
|
|
}
|
|
|
|
// Keywords for horizontal properties cannot be vertical keywords, and
|
|
// keywords for vertical properties cannot be horizontal keywords.
|
|
// Also, if an offset is specified, the edge cannot be center.
|
|
int32_t edgeEnum =
|
|
edge.GetUnit() == eCSSUnit_Enumerated ? edge.GetIntValue() : 0;
|
|
int32_t allowedKeywords =
|
|
(aIsHorizontal ? (BG_LEFT | BG_RIGHT) : (BG_TOP | BG_BOTTOM)) |
|
|
(offset.GetUnit() == eCSSUnit_Null ? BG_CENTER : 0);
|
|
if (edgeEnum & ~allowedKeywords) {
|
|
return false;
|
|
}
|
|
|
|
NS_ASSERTION((eCSSUnit_Enumerated == edge.GetUnit() ||
|
|
eCSSUnit_Null == edge.GetUnit()) &&
|
|
eCSSUnit_Enumerated != offset.GetUnit(),
|
|
"Unexpected units");
|
|
|
|
return true;
|
|
}
|
|
|
|
// This function is very similar to ParseScrollSnapCoordinate,
|
|
// ParseImageLayers, and ParseImageLayerPosition.
|
|
bool
|
|
CSSParserImpl::ParseImageLayerSize(nsCSSPropertyID aPropID)
|
|
{
|
|
nsCSSValue value;
|
|
// 'initial', 'inherit' and 'unset' stand alone, no list permitted.
|
|
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
|
|
nsCSSValuePair valuePair;
|
|
if (!ParseImageLayerSizeValues(valuePair)) {
|
|
return false;
|
|
}
|
|
nsCSSValuePairList* item = value.SetPairListValue();
|
|
for (;;) {
|
|
item->mXValue = valuePair.mXValue;
|
|
item->mYValue = valuePair.mYValue;
|
|
if (!ExpectSymbol(',', true)) {
|
|
break;
|
|
}
|
|
if (!ParseImageLayerSizeValues(valuePair)) {
|
|
return false;
|
|
}
|
|
item->mNext = new nsCSSValuePairList;
|
|
item = item->mNext;
|
|
}
|
|
}
|
|
AppendValue(aPropID, value);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Parses two values that correspond to lengths for the background-size
|
|
* property. These can be one or two lengths (or the 'auto' keyword) or
|
|
* percentages corresponding to the element's dimensions or the single keywords
|
|
* 'contain' or 'cover'. 'initial', 'inherit' and 'unset' must be handled by
|
|
* the caller if desired.
|
|
*
|
|
* @param aOut The nsCSSValuePair in which to place the result.
|
|
* @return Whether or not the operation succeeded.
|
|
*/
|
|
#define BG_SIZE_VARIANT (VARIANT_LP | VARIANT_AUTO | VARIANT_CALC)
|
|
bool CSSParserImpl::ParseImageLayerSizeValues(nsCSSValuePair &aOut)
|
|
{
|
|
// First try a percentage or a length value
|
|
nsCSSValue &xValue = aOut.mXValue,
|
|
&yValue = aOut.mYValue;
|
|
CSSParseResult result =
|
|
ParseNonNegativeVariant(xValue, BG_SIZE_VARIANT, nullptr);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
} else if (result == CSSParseResult::Ok) {
|
|
// We have one percentage/length/calc/auto. Get the optional second
|
|
// percentage/length/calc/keyword.
|
|
result = ParseNonNegativeVariant(yValue, BG_SIZE_VARIANT, nullptr);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
} else if (result == CSSParseResult::Ok) {
|
|
// We have a second percentage/length/calc/auto.
|
|
return true;
|
|
}
|
|
|
|
// If only one percentage or length value is given, it sets the
|
|
// horizontal size only, and the vertical size will be as if by 'auto'.
|
|
yValue.SetAutoValue();
|
|
return true;
|
|
}
|
|
|
|
// Now address 'contain' and 'cover'.
|
|
if (!ParseEnum(xValue, nsCSSProps::kImageLayerSizeKTable))
|
|
return false;
|
|
yValue.Reset();
|
|
return true;
|
|
}
|
|
|
|
#undef BG_SIZE_VARIANT
|
|
|
|
bool
|
|
CSSParserImpl::ParseBorderColor()
|
|
{
|
|
return ParseBoxProperties(kBorderColorIDs);
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::SetBorderImageInitialValues()
|
|
{
|
|
// border-image-source: none
|
|
nsCSSValue source;
|
|
source.SetNoneValue();
|
|
AppendValue(eCSSProperty_border_image_source, source);
|
|
|
|
// border-image-slice: 100%
|
|
nsCSSValue sliceBoxValue;
|
|
nsCSSRect& sliceBox = sliceBoxValue.SetRectValue();
|
|
sliceBox.SetAllSidesTo(nsCSSValue(1.0f, eCSSUnit_Percent));
|
|
nsCSSValue slice;
|
|
nsCSSValueList* sliceList = slice.SetListValue();
|
|
sliceList->mValue = sliceBoxValue;
|
|
AppendValue(eCSSProperty_border_image_slice, slice);
|
|
|
|
// border-image-width: 1
|
|
nsCSSValue width;
|
|
nsCSSRect& widthBox = width.SetRectValue();
|
|
widthBox.SetAllSidesTo(nsCSSValue(1.0f, eCSSUnit_Number));
|
|
AppendValue(eCSSProperty_border_image_width, width);
|
|
|
|
// border-image-outset: 0
|
|
nsCSSValue outset;
|
|
nsCSSRect& outsetBox = outset.SetRectValue();
|
|
outsetBox.SetAllSidesTo(nsCSSValue(0.0f, eCSSUnit_Number));
|
|
AppendValue(eCSSProperty_border_image_outset, outset);
|
|
|
|
// border-image-repeat: repeat
|
|
nsCSSValue repeat;
|
|
nsCSSValuePair repeatPair;
|
|
repeatPair.SetBothValuesTo(nsCSSValue(NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH,
|
|
eCSSUnit_Enumerated));
|
|
repeat.SetPairValue(&repeatPair);
|
|
AppendValue(eCSSProperty_border_image_repeat, repeat);
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseBorderImageSlice(bool aAcceptsInherit,
|
|
bool* aConsumedTokens)
|
|
{
|
|
// border-image-slice: initial | [<number>|<percentage>]{1,4} && fill?
|
|
nsCSSValue value;
|
|
|
|
if (aConsumedTokens) {
|
|
*aConsumedTokens = true;
|
|
}
|
|
|
|
if (aAcceptsInherit &&
|
|
ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
|
|
// Keywords "inherit", "initial" and "unset" can not be mixed, so we
|
|
// are done.
|
|
AppendValue(eCSSProperty_border_image_slice, value);
|
|
return true;
|
|
}
|
|
|
|
// Try parsing "fill" value.
|
|
nsCSSValue imageSliceFillValue;
|
|
bool hasFill = ParseEnum(imageSliceFillValue,
|
|
nsCSSProps::kBorderImageSliceKTable);
|
|
|
|
// Parse the box dimensions.
|
|
nsCSSValue imageSliceBoxValue;
|
|
if (!ParseGroupedBoxProperty(VARIANT_PN, imageSliceBoxValue)) {
|
|
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)) {
|
|
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)) {
|
|
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_STYLE_COLOR_MOZ_USE_TEXT_COLOR, eCSSUnit_Enumerated);
|
|
}
|
|
|
|
if (aSetAllSides) {
|
|
// Parsing "border" shorthand; set all four sides to the same thing
|
|
for (int32_t index = 0; index < 4; index++) {
|
|
NS_ASSERTION(numProps == 3, "This code needs updating");
|
|
AppendValue(kBorderWidthIDs[index], values[0]);
|
|
AppendValue(kBorderStyleIDs[index], values[1]);
|
|
AppendValue(kBorderColorIDs[index], values[2]);
|
|
}
|
|
|
|
static const nsCSSPropertyID kBorderColorsProps[] = {
|
|
eCSSProperty_border_top_colors,
|
|
eCSSProperty_border_right_colors,
|
|
eCSSProperty_border_bottom_colors,
|
|
eCSSProperty_border_left_colors
|
|
};
|
|
|
|
// Set the other properties that the border shorthand sets to their
|
|
// initial values.
|
|
nsCSSValue extraValue;
|
|
switch (values[0].GetUnit()) {
|
|
case eCSSUnit_Inherit:
|
|
case eCSSUnit_Initial:
|
|
case eCSSUnit_Unset:
|
|
extraValue = values[0];
|
|
// Set value of border-image properties to initial/inherit/unset
|
|
AppendValue(eCSSProperty_border_image_source, extraValue);
|
|
AppendValue(eCSSProperty_border_image_slice, extraValue);
|
|
AppendValue(eCSSProperty_border_image_width, extraValue);
|
|
AppendValue(eCSSProperty_border_image_outset, extraValue);
|
|
AppendValue(eCSSProperty_border_image_repeat, extraValue);
|
|
break;
|
|
default:
|
|
extraValue.SetNoneValue();
|
|
SetBorderImageInitialValues();
|
|
break;
|
|
}
|
|
NS_FOR_CSS_SIDES(side) {
|
|
AppendValue(kBorderColorsProps[side], extraValue);
|
|
}
|
|
}
|
|
else {
|
|
// Just set our one side
|
|
for (int32_t index = 0; index < numProps; index++) {
|
|
AppendValue(aPropIDs[index], values[index]);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseBorderStyle()
|
|
{
|
|
return ParseBoxProperties(kBorderStyleIDs);
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseBorderWidth()
|
|
{
|
|
return ParseBoxProperties(kBorderWidthIDs);
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseBorderColors(nsCSSPropertyID aProperty)
|
|
{
|
|
nsCSSValue value;
|
|
// 'inherit', 'initial', 'unset' and 'none' are only allowed on their own
|
|
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
|
|
nullptr)) {
|
|
nsCSSValueList *cur = value.SetListValue();
|
|
for (;;) {
|
|
if (ParseVariant(cur->mValue, VARIANT_COLOR | VARIANT_KEYWORD,
|
|
nsCSSProps::kBorderColorKTable) != CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
if (CheckEndProperty()) {
|
|
break;
|
|
}
|
|
cur->mNext = new nsCSSValueList;
|
|
cur = cur->mNext;
|
|
}
|
|
}
|
|
AppendValue(aProperty, value);
|
|
return true;
|
|
}
|
|
|
|
// Parse the top level of a calc() expression.
|
|
bool
|
|
CSSParserImpl::ParseCalc(nsCSSValue &aValue, uint32_t aVariantMask)
|
|
{
|
|
// Parsing calc expressions requires, in a number of cases, looking
|
|
// for a token that is *either* a value of the property or a number.
|
|
// This can be done without lookahead when we assume that the property
|
|
// values cannot themselves be numbers.
|
|
MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask");
|
|
|
|
bool oldUnitlessLengthQuirk = mUnitlessLengthQuirk;
|
|
mUnitlessLengthQuirk = false;
|
|
|
|
// One-iteration loop so we can break to the error-handling case.
|
|
do {
|
|
// The toplevel of a calc() is always an nsCSSValue::Array of length 1.
|
|
RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(1);
|
|
|
|
if (!ParseCalcAdditiveExpression(arr->Item(0), aVariantMask))
|
|
break;
|
|
|
|
if (!ExpectSymbol(')', true))
|
|
break;
|
|
|
|
aValue.SetArrayValue(arr, eCSSUnit_Calc);
|
|
mUnitlessLengthQuirk = oldUnitlessLengthQuirk;
|
|
return true;
|
|
} while (false);
|
|
|
|
SkipUntil(')');
|
|
mUnitlessLengthQuirk = oldUnitlessLengthQuirk;
|
|
return false;
|
|
}
|
|
|
|
// We optimize away the <value-expression> production given that
|
|
// ParseVariant consumes initial whitespace and we call
|
|
// ExpectSymbol(')') with true for aSkipWS.
|
|
// * If aVariantMask is VARIANT_NUMBER, this function parses the
|
|
// <number-additive-expression> production.
|
|
// * If aVariantMask does not contain VARIANT_NUMBER, this function
|
|
// parses the <value-additive-expression> production.
|
|
// * Otherwise (VARIANT_NUMBER and other bits) this function parses
|
|
// whichever one of the productions matches ***and modifies
|
|
// aVariantMask*** to reflect which one it has parsed by either
|
|
// removing VARIANT_NUMBER or removing all other bits.
|
|
// It does so iteratively, but builds the correct recursive
|
|
// data structure.
|
|
bool
|
|
CSSParserImpl::ParseCalcAdditiveExpression(nsCSSValue& aValue,
|
|
uint32_t& aVariantMask)
|
|
{
|
|
MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask");
|
|
nsCSSValue *storage = &aValue;
|
|
for (;;) {
|
|
bool haveWS;
|
|
if (!ParseCalcMultiplicativeExpression(*storage, aVariantMask, &haveWS))
|
|
return false;
|
|
|
|
if (!haveWS || !GetToken(false))
|
|
return true;
|
|
nsCSSUnit unit;
|
|
if (mToken.IsSymbol('+')) {
|
|
unit = eCSSUnit_Calc_Plus;
|
|
} else if (mToken.IsSymbol('-')) {
|
|
unit = eCSSUnit_Calc_Minus;
|
|
} else {
|
|
UngetToken();
|
|
return true;
|
|
}
|
|
if (!RequireWhitespace())
|
|
return false;
|
|
|
|
RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(2);
|
|
arr->Item(0) = aValue;
|
|
storage = &arr->Item(1);
|
|
aValue.SetArrayValue(arr, unit);
|
|
}
|
|
}
|
|
|
|
struct ReduceNumberCalcOps : public mozilla::css::BasicFloatCalcOps,
|
|
public mozilla::css::CSSValueInputCalcOps
|
|
{
|
|
result_type ComputeLeafValue(const nsCSSValue& aValue)
|
|
{
|
|
MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Number, "unexpected unit");
|
|
return aValue.GetFloatValue();
|
|
}
|
|
|
|
float ComputeNumber(const nsCSSValue& aValue)
|
|
{
|
|
return mozilla::css::ComputeCalc(aValue, *this);
|
|
}
|
|
};
|
|
|
|
// * If aVariantMask is VARIANT_NUMBER, this function parses the
|
|
// <number-multiplicative-expression> production.
|
|
// * If aVariantMask does not contain VARIANT_NUMBER, this function
|
|
// parses the <value-multiplicative-expression> production.
|
|
// * Otherwise (VARIANT_NUMBER and other bits) this function parses
|
|
// whichever one of the productions matches ***and modifies
|
|
// aVariantMask*** to reflect which one it has parsed by either
|
|
// removing VARIANT_NUMBER or removing all other bits.
|
|
// It does so iteratively, but builds the correct recursive data
|
|
// structure.
|
|
// This function always consumes *trailing* whitespace when it returns
|
|
// true; whether there was any such whitespace is returned in the
|
|
// aHadFinalWS parameter.
|
|
bool
|
|
CSSParserImpl::ParseCalcMultiplicativeExpression(nsCSSValue& aValue,
|
|
uint32_t& aVariantMask,
|
|
bool *aHadFinalWS)
|
|
{
|
|
MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask");
|
|
bool gotValue = false; // already got the part with the unit
|
|
bool afterDivision = false;
|
|
|
|
nsCSSValue *storage = &aValue;
|
|
for (;;) {
|
|
uint32_t variantMask;
|
|
if (afterDivision || gotValue) {
|
|
variantMask = VARIANT_NUMBER;
|
|
} else {
|
|
variantMask = aVariantMask | VARIANT_NUMBER;
|
|
}
|
|
if (!ParseCalcTerm(*storage, variantMask))
|
|
return false;
|
|
MOZ_ASSERT(variantMask != 0,
|
|
"ParseCalcTerm did not set variantMask appropriately");
|
|
MOZ_ASSERT(!(variantMask & VARIANT_NUMBER) ||
|
|
!(variantMask & ~int32_t(VARIANT_NUMBER)),
|
|
"ParseCalcTerm did not set variantMask appropriately");
|
|
|
|
if (variantMask & VARIANT_NUMBER) {
|
|
// Simplify the value immediately so we can check for division by
|
|
// zero.
|
|
ReduceNumberCalcOps ops;
|
|
float number = mozilla::css::ComputeCalc(*storage, ops);
|
|
if (number == 0.0 && afterDivision)
|
|
return false;
|
|
storage->SetFloatValue(number, eCSSUnit_Number);
|
|
} else {
|
|
gotValue = true;
|
|
|
|
if (storage != &aValue) {
|
|
// Simplify any numbers in the Times_L position (which are
|
|
// not simplified by the check above).
|
|
MOZ_ASSERT(storage == &aValue.GetArrayValue()->Item(1),
|
|
"unexpected relationship to current storage");
|
|
nsCSSValue &leftValue = aValue.GetArrayValue()->Item(0);
|
|
ReduceNumberCalcOps ops;
|
|
float number = mozilla::css::ComputeCalc(leftValue, ops);
|
|
leftValue.SetFloatValue(number, eCSSUnit_Number);
|
|
}
|
|
}
|
|
|
|
bool hadWS = RequireWhitespace();
|
|
if (!GetToken(false)) {
|
|
*aHadFinalWS = hadWS;
|
|
break;
|
|
}
|
|
nsCSSUnit unit;
|
|
if (mToken.IsSymbol('*')) {
|
|
unit = gotValue ? eCSSUnit_Calc_Times_R : eCSSUnit_Calc_Times_L;
|
|
afterDivision = false;
|
|
} else if (mToken.IsSymbol('/')) {
|
|
unit = eCSSUnit_Calc_Divided;
|
|
afterDivision = true;
|
|
} else {
|
|
UngetToken();
|
|
*aHadFinalWS = hadWS;
|
|
break;
|
|
}
|
|
|
|
RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(2);
|
|
arr->Item(0) = aValue;
|
|
storage = &arr->Item(1);
|
|
aValue.SetArrayValue(arr, unit);
|
|
}
|
|
|
|
// Adjust aVariantMask (see comments above function) to reflect which
|
|
// option we took.
|
|
if (aVariantMask & VARIANT_NUMBER) {
|
|
if (gotValue) {
|
|
aVariantMask &= ~int32_t(VARIANT_NUMBER);
|
|
} else {
|
|
aVariantMask = VARIANT_NUMBER;
|
|
}
|
|
} else {
|
|
if (!gotValue) {
|
|
// We had to find a value, but we didn't.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// * If aVariantMask is VARIANT_NUMBER, this function parses the
|
|
// <number-term> production.
|
|
// * If aVariantMask does not contain VARIANT_NUMBER, this function
|
|
// parses the <value-term> production.
|
|
// * Otherwise (VARIANT_NUMBER and other bits) this function parses
|
|
// whichever one of the productions matches ***and modifies
|
|
// aVariantMask*** to reflect which one it has parsed by either
|
|
// removing VARIANT_NUMBER or removing all other bits.
|
|
bool
|
|
CSSParserImpl::ParseCalcTerm(nsCSSValue& aValue, uint32_t& aVariantMask)
|
|
{
|
|
MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask");
|
|
if (!GetToken(true))
|
|
return false;
|
|
// Either an additive expression in parentheses...
|
|
if (mToken.IsSymbol('(') ||
|
|
// Treat nested calc() as plain parenthesis.
|
|
IsCSSTokenCalcFunction(mToken)) {
|
|
if (!ParseCalcAdditiveExpression(aValue, aVariantMask) ||
|
|
!ExpectSymbol(')', true)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
// ... or just a value
|
|
UngetToken();
|
|
// Always pass VARIANT_NUMBER to ParseVariant so that unitless zero
|
|
// always gets picked up
|
|
if (ParseVariant(aValue, aVariantMask | VARIANT_NUMBER, nullptr) !=
|
|
CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
// ...and do the VARIANT_NUMBER check ourselves.
|
|
if (!(aVariantMask & VARIANT_NUMBER) && aValue.GetUnit() == eCSSUnit_Number) {
|
|
return false;
|
|
}
|
|
// If we did the value parsing, we need to adjust aVariantMask to
|
|
// reflect which option we took (see above).
|
|
if (aVariantMask & VARIANT_NUMBER) {
|
|
if (aValue.GetUnit() == eCSSUnit_Number) {
|
|
aVariantMask = VARIANT_NUMBER;
|
|
} else {
|
|
aVariantMask &= ~int32_t(VARIANT_NUMBER);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// This function consumes all consecutive whitespace and returns whether
|
|
// there was any.
|
|
bool
|
|
CSSParserImpl::RequireWhitespace()
|
|
{
|
|
if (!GetToken(false))
|
|
return false;
|
|
if (mToken.mType != eCSSToken_Whitespace) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
// Skip any additional whitespace tokens.
|
|
if (GetToken(true)) {
|
|
UngetToken();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseRect(nsCSSPropertyID aPropID)
|
|
{
|
|
nsCSSValue val;
|
|
if (ParseSingleTokenVariant(val, VARIANT_INHERIT | VARIANT_AUTO, nullptr)) {
|
|
AppendValue(aPropID, val);
|
|
return true;
|
|
}
|
|
|
|
if (! GetToken(true)) {
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType == eCSSToken_Function &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("rect")) {
|
|
nsCSSRect& rect = val.SetRectValue();
|
|
bool useCommas;
|
|
NS_FOR_CSS_SIDES(side) {
|
|
if (!ParseSingleTokenVariant(rect.*(nsCSSRect::sides[side]),
|
|
VARIANT_AL, nullptr)) {
|
|
return false;
|
|
}
|
|
if (side == 0) {
|
|
useCommas = ExpectSymbol(',', true);
|
|
} else if (useCommas && side < 3) {
|
|
// Skip optional commas between elements, but only if the first
|
|
// separator was a comma.
|
|
if (!ExpectSymbol(',', true)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (!ExpectSymbol(')', true)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
AppendValue(aPropID, val);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseColumns()
|
|
{
|
|
// We use a similar "fake value" hack to ParseListStyle, because
|
|
// "auto" is acceptable for both column-count and column-width.
|
|
// If the fake "auto" value is found, and one of the real values isn't,
|
|
// that means the fake auto value is meant for the real value we didn't
|
|
// find.
|
|
static const nsCSSPropertyID columnIDs[] = {
|
|
eCSSPropertyExtra_x_auto_value,
|
|
eCSSProperty_column_count,
|
|
eCSSProperty_column_width
|
|
};
|
|
const int32_t numProps = MOZ_ARRAY_LENGTH(columnIDs);
|
|
|
|
nsCSSValue values[numProps];
|
|
int32_t found = ParseChoice(values, columnIDs, numProps);
|
|
if (found < 1) {
|
|
return false;
|
|
}
|
|
if ((found & (1|2|4)) == (1|2|4) &&
|
|
values[0].GetUnit() == eCSSUnit_Auto) {
|
|
// We filled all 3 values, which is invalid
|
|
return false;
|
|
}
|
|
|
|
if ((found & 2) == 0) {
|
|
// Provide auto column-count
|
|
values[1].SetAutoValue();
|
|
}
|
|
if ((found & 4) == 0) {
|
|
// Provide auto column-width
|
|
values[2].SetAutoValue();
|
|
}
|
|
|
|
// Start at index 1 to skip the fake auto value.
|
|
for (int32_t index = 1; index < numProps; index++) {
|
|
AppendValue(columnIDs[index], values[index]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#define VARIANT_CONTENT (VARIANT_STRING | VARIANT_URL | VARIANT_COUNTER | VARIANT_ATTR | \
|
|
VARIANT_KEYWORD)
|
|
bool
|
|
CSSParserImpl::ParseContent()
|
|
{
|
|
// We need to divide the 'content' keywords into two classes for
|
|
// ParseVariant's sake, so we can't just use nsCSSProps::kContentKTable.
|
|
static const KTableEntry kContentListKWs[] = {
|
|
{ eCSSKeyword_open_quote, NS_STYLE_CONTENT_OPEN_QUOTE },
|
|
{ eCSSKeyword_close_quote, NS_STYLE_CONTENT_CLOSE_QUOTE },
|
|
{ eCSSKeyword_no_open_quote, NS_STYLE_CONTENT_NO_OPEN_QUOTE },
|
|
{ eCSSKeyword_no_close_quote, NS_STYLE_CONTENT_NO_CLOSE_QUOTE },
|
|
{ eCSSKeyword_UNKNOWN, -1 }
|
|
};
|
|
|
|
static const KTableEntry kContentSolitaryKWs[] = {
|
|
{ eCSSKeyword__moz_alt_content, NS_STYLE_CONTENT_ALT_CONTENT },
|
|
{ eCSSKeyword_UNKNOWN, -1 }
|
|
};
|
|
|
|
// Verify that these two lists add up to the size of
|
|
// nsCSSProps::kContentKTable.
|
|
MOZ_ASSERT(nsCSSProps::kContentKTable[
|
|
ArrayLength(kContentListKWs) +
|
|
ArrayLength(kContentSolitaryKWs) - 2].mKeyword ==
|
|
eCSSKeyword_UNKNOWN &&
|
|
nsCSSProps::kContentKTable[
|
|
ArrayLength(kContentListKWs) +
|
|
ArrayLength(kContentSolitaryKWs) - 2].mValue == -1,
|
|
"content keyword tables out of sync");
|
|
|
|
nsCSSValue value;
|
|
// 'inherit', 'initial', 'unset', 'normal', 'none', and 'alt-content' must
|
|
// be alone
|
|
if (!ParseSingleTokenVariant(value, VARIANT_HMK | VARIANT_NONE,
|
|
kContentSolitaryKWs)) {
|
|
nsCSSValueList* cur = value.SetListValue();
|
|
for (;;) {
|
|
if (ParseVariant(cur->mValue, VARIANT_CONTENT, kContentListKWs) !=
|
|
CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
if (CheckEndProperty()) {
|
|
break;
|
|
}
|
|
cur->mNext = new nsCSSValueList;
|
|
cur = cur->mNext;
|
|
}
|
|
}
|
|
AppendValue(eCSSProperty_content, value);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseCounterData(nsCSSPropertyID aPropID)
|
|
{
|
|
static const nsCSSKeyword kCounterDataKTable[] = {
|
|
eCSSKeyword_none,
|
|
eCSSKeyword_UNKNOWN
|
|
};
|
|
nsCSSValue value;
|
|
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
|
|
nullptr)) {
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
if (mToken.mType != eCSSToken_Ident) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
nsCSSValuePairList *cur = value.SetPairListValue();
|
|
for (;;) {
|
|
if (!ParseCustomIdent(cur->mXValue, mToken.mIdent, kCounterDataKTable)) {
|
|
return false;
|
|
}
|
|
if (!GetToken(true)) {
|
|
break;
|
|
}
|
|
if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid) {
|
|
cur->mYValue.SetIntValue(mToken.mInteger, eCSSUnit_Integer);
|
|
} else {
|
|
UngetToken();
|
|
}
|
|
if (!GetToken(true)) {
|
|
break;
|
|
}
|
|
if (mToken.mType != eCSSToken_Ident) {
|
|
UngetToken();
|
|
break;
|
|
}
|
|
cur->mNext = new nsCSSValuePairList;
|
|
cur = cur->mNext;
|
|
}
|
|
}
|
|
AppendValue(aPropID, value);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseCursor()
|
|
{
|
|
nsCSSValue value;
|
|
// 'inherit', 'initial' and 'unset' must be alone
|
|
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
|
|
nsCSSValueList* cur = value.SetListValue();
|
|
for (;;) {
|
|
if (!ParseSingleTokenVariant(cur->mValue, VARIANT_UK,
|
|
nsCSSProps::kCursorKTable)) {
|
|
return false;
|
|
}
|
|
if (cur->mValue.GetUnit() != eCSSUnit_URL) { // keyword must be last
|
|
break;
|
|
}
|
|
|
|
// We have a URL, so make a value array with three values.
|
|
RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(3);
|
|
val->Item(0) = cur->mValue;
|
|
|
|
// Parse optional x and y position of cursor hotspot (css3-ui).
|
|
if (ParseSingleTokenVariant(val->Item(1), VARIANT_NUMBER, nullptr)) {
|
|
// If we have one number, we must have two.
|
|
if (!ParseSingleTokenVariant(val->Item(2), VARIANT_NUMBER, nullptr)) {
|
|
return false;
|
|
}
|
|
}
|
|
cur->mValue.SetArrayValue(val, eCSSUnit_Array);
|
|
|
|
if (!ExpectSymbol(',', true)) { // url must not be last
|
|
return false;
|
|
}
|
|
cur->mNext = new nsCSSValueList;
|
|
cur = cur->mNext;
|
|
}
|
|
}
|
|
AppendValue(eCSSProperty_cursor, value);
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
CSSParserImpl::ParseFont()
|
|
{
|
|
nsCSSValue family;
|
|
if (ParseSingleTokenVariant(family, VARIANT_HK, nsCSSProps::kFontKTable)) {
|
|
if (eCSSUnit_Inherit == family.GetUnit() ||
|
|
eCSSUnit_Initial == family.GetUnit() ||
|
|
eCSSUnit_Unset == family.GetUnit()) {
|
|
AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None));
|
|
AppendValue(eCSSProperty_font_family, family);
|
|
AppendValue(eCSSProperty_font_style, family);
|
|
AppendValue(eCSSProperty_font_weight, family);
|
|
AppendValue(eCSSProperty_font_size, family);
|
|
AppendValue(eCSSProperty_line_height, family);
|
|
AppendValue(eCSSProperty_font_stretch, family);
|
|
AppendValue(eCSSProperty_font_size_adjust, family);
|
|
AppendValue(eCSSProperty_font_feature_settings, family);
|
|
AppendValue(eCSSProperty_font_language_override, family);
|
|
AppendValue(eCSSProperty_font_kerning, family);
|
|
AppendValue(eCSSProperty_font_synthesis, family);
|
|
AppendValue(eCSSProperty_font_variant_alternates, family);
|
|
AppendValue(eCSSProperty_font_variant_caps, family);
|
|
AppendValue(eCSSProperty_font_variant_east_asian, family);
|
|
AppendValue(eCSSProperty_font_variant_ligatures, family);
|
|
AppendValue(eCSSProperty_font_variant_numeric, family);
|
|
AppendValue(eCSSProperty_font_variant_position, family);
|
|
}
|
|
else {
|
|
AppendValue(eCSSProperty__x_system_font, family);
|
|
nsCSSValue systemFont(eCSSUnit_System_Font);
|
|
AppendValue(eCSSProperty_font_family, systemFont);
|
|
AppendValue(eCSSProperty_font_style, systemFont);
|
|
AppendValue(eCSSProperty_font_weight, systemFont);
|
|
AppendValue(eCSSProperty_font_size, systemFont);
|
|
AppendValue(eCSSProperty_line_height, systemFont);
|
|
AppendValue(eCSSProperty_font_stretch, systemFont);
|
|
AppendValue(eCSSProperty_font_size_adjust, systemFont);
|
|
AppendValue(eCSSProperty_font_feature_settings, systemFont);
|
|
AppendValue(eCSSProperty_font_language_override, systemFont);
|
|
AppendValue(eCSSProperty_font_kerning, systemFont);
|
|
AppendValue(eCSSProperty_font_synthesis, systemFont);
|
|
AppendValue(eCSSProperty_font_variant_alternates, systemFont);
|
|
AppendValue(eCSSProperty_font_variant_caps, systemFont);
|
|
AppendValue(eCSSProperty_font_variant_east_asian, systemFont);
|
|
AppendValue(eCSSProperty_font_variant_ligatures, systemFont);
|
|
AppendValue(eCSSProperty_font_variant_numeric, systemFont);
|
|
AppendValue(eCSSProperty_font_variant_position, systemFont);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Get optional font-style, font-variant, font-weight, font-stretch
|
|
// (in any order)
|
|
|
|
// Indexes into fontIDs[] and values[] arrays.
|
|
const int kFontStyleIndex = 0;
|
|
const int kFontVariantIndex = 1;
|
|
const int kFontWeightIndex = 2;
|
|
const int kFontStretchIndex = 3;
|
|
|
|
// The order of the initializers here must match the order of the indexes
|
|
// defined above!
|
|
static const nsCSSPropertyID fontIDs[] = {
|
|
eCSSProperty_font_style,
|
|
eCSSProperty_font_variant_caps,
|
|
eCSSProperty_font_weight,
|
|
eCSSProperty_font_stretch
|
|
};
|
|
|
|
const int32_t numProps = MOZ_ARRAY_LENGTH(fontIDs);
|
|
nsCSSValue values[numProps];
|
|
int32_t found = ParseChoice(values, fontIDs, numProps);
|
|
if (found < 0 ||
|
|
eCSSUnit_Inherit == values[kFontStyleIndex].GetUnit() ||
|
|
eCSSUnit_Initial == values[kFontStyleIndex].GetUnit() ||
|
|
eCSSUnit_Unset == values[kFontStyleIndex].GetUnit()) { // illegal data
|
|
return false;
|
|
}
|
|
if ((found & (1 << kFontStyleIndex)) == 0) {
|
|
// Provide default font-style
|
|
values[kFontStyleIndex].SetIntValue(NS_FONT_STYLE_NORMAL,
|
|
eCSSUnit_Enumerated);
|
|
}
|
|
if ((found & (1 << kFontVariantIndex)) == 0) {
|
|
// Provide default font-variant
|
|
values[kFontVariantIndex].SetNormalValue();
|
|
} else {
|
|
if (values[kFontVariantIndex].GetUnit() == eCSSUnit_Enumerated &&
|
|
values[kFontVariantIndex].GetIntValue() !=
|
|
NS_FONT_VARIANT_CAPS_SMALLCAPS) {
|
|
// only normal or small-caps is allowed in font shorthand
|
|
// this also assumes other values for font-variant-caps never overlap
|
|
// possible values for style or weight
|
|
return false;
|
|
}
|
|
}
|
|
if ((found & (1 << kFontWeightIndex)) == 0) {
|
|
// Provide default font-weight
|
|
values[kFontWeightIndex].SetIntValue(NS_FONT_WEIGHT_NORMAL,
|
|
eCSSUnit_Enumerated);
|
|
}
|
|
if ((found & (1 << kFontStretchIndex)) == 0) {
|
|
// Provide default font-stretch
|
|
values[kFontStretchIndex].SetIntValue(NS_FONT_STRETCH_NORMAL,
|
|
eCSSUnit_Enumerated);
|
|
}
|
|
|
|
// Get mandatory font-size
|
|
nsCSSValue size;
|
|
if (!ParseSingleTokenNonNegativeVariant(size, VARIANT_KEYWORD | VARIANT_LP,
|
|
nsCSSProps::kFontSizeKTable)) {
|
|
return false;
|
|
}
|
|
|
|
// Get optional "/" line-height
|
|
nsCSSValue lineHeight;
|
|
if (ExpectSymbol('/', true)) {
|
|
if (ParseNonNegativeVariant(lineHeight,
|
|
VARIANT_NUMBER | VARIANT_LP |
|
|
VARIANT_NORMAL | VARIANT_CALC,
|
|
nullptr) != CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
lineHeight.SetNormalValue();
|
|
}
|
|
|
|
// Get final mandatory font-family
|
|
nsAutoParseCompoundProperty compound(this);
|
|
if (ParseFamily(family)) {
|
|
if (eCSSUnit_Inherit != family.GetUnit() &&
|
|
eCSSUnit_Initial != family.GetUnit() &&
|
|
eCSSUnit_Unset != family.GetUnit()) {
|
|
AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None));
|
|
AppendValue(eCSSProperty_font_family, family);
|
|
AppendValue(eCSSProperty_font_style, values[kFontStyleIndex]);
|
|
AppendValue(eCSSProperty_font_variant_caps, values[kFontVariantIndex]);
|
|
AppendValue(eCSSProperty_font_weight, values[kFontWeightIndex]);
|
|
AppendValue(eCSSProperty_font_size, size);
|
|
AppendValue(eCSSProperty_line_height, lineHeight);
|
|
AppendValue(eCSSProperty_font_stretch, values[kFontStretchIndex]);
|
|
AppendValue(eCSSProperty_font_size_adjust, nsCSSValue(eCSSUnit_None));
|
|
AppendValue(eCSSProperty_font_feature_settings, nsCSSValue(eCSSUnit_Normal));
|
|
AppendValue(eCSSProperty_font_language_override, nsCSSValue(eCSSUnit_Normal));
|
|
AppendValue(eCSSProperty_font_kerning,
|
|
nsCSSValue(NS_FONT_KERNING_AUTO, eCSSUnit_Enumerated));
|
|
AppendValue(eCSSProperty_font_synthesis,
|
|
nsCSSValue(NS_FONT_SYNTHESIS_WEIGHT | NS_FONT_SYNTHESIS_STYLE,
|
|
eCSSUnit_Enumerated));
|
|
AppendValue(eCSSProperty_font_variant_alternates,
|
|
nsCSSValue(eCSSUnit_Normal));
|
|
AppendValue(eCSSProperty_font_variant_east_asian,
|
|
nsCSSValue(eCSSUnit_Normal));
|
|
AppendValue(eCSSProperty_font_variant_ligatures,
|
|
nsCSSValue(eCSSUnit_Normal));
|
|
AppendValue(eCSSProperty_font_variant_numeric,
|
|
nsCSSValue(eCSSUnit_Normal));
|
|
AppendValue(eCSSProperty_font_variant_position,
|
|
nsCSSValue(eCSSUnit_Normal));
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseFontSynthesis(nsCSSValue& aValue)
|
|
{
|
|
if (!ParseSingleTokenVariant(aValue, VARIANT_HK | VARIANT_NONE,
|
|
nsCSSProps::kFontSynthesisKTable)) {
|
|
return false;
|
|
}
|
|
|
|
// first value 'none' ==> done
|
|
if (eCSSUnit_None == aValue.GetUnit() ||
|
|
eCSSUnit_Initial == aValue.GetUnit() ||
|
|
eCSSUnit_Inherit == aValue.GetUnit() ||
|
|
eCSSUnit_Unset == aValue.GetUnit())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// look for a second value
|
|
int32_t intValue = aValue.GetIntValue();
|
|
nsCSSValue nextValue;
|
|
|
|
if (ParseEnum(nextValue, nsCSSProps::kFontSynthesisKTable)) {
|
|
int32_t nextIntValue = nextValue.GetIntValue();
|
|
if (nextIntValue & intValue) {
|
|
return false;
|
|
}
|
|
aValue.SetIntValue(nextIntValue | intValue, eCSSUnit_Enumerated);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// font-variant-alternates allows for a combination of multiple
|
|
// simple enumerated values and functional values. Functional values have
|
|
// parameter lists with one or more idents which are later resolved
|
|
// based on values defined in @font-feature-value rules.
|
|
//
|
|
// font-variant-alternates: swash(flowing) historical-forms styleset(alt-g, alt-m);
|
|
//
|
|
// So for this the nsCSSValue is set to a pair value, with one
|
|
// value for a bitmask of both simple and functional property values
|
|
// and another value containing a ValuePairList with lists of idents
|
|
// for each functional property value.
|
|
//
|
|
// pairValue
|
|
// o intValue
|
|
// NS_FONT_VARIANT_ALTERNATES_SWASH |
|
|
// NS_FONT_VARIANT_ALTERNATES_STYLESET
|
|
// o valuePairList, each element with
|
|
// - intValue - indicates which alternate
|
|
// - string or valueList of strings
|
|
//
|
|
// Note: when only 'historical-forms' is specified, there are no
|
|
// functional values to store, in which case the valuePairList is a
|
|
// single element dummy list. In all other cases, the length of the
|
|
// list will match the number of functional values.
|
|
|
|
#define MAX_ALLOWED_FEATURES 512
|
|
|
|
static uint16_t
|
|
MaxElementsForAlternateType(nsCSSKeyword keyword)
|
|
{
|
|
uint16_t maxElems = 1;
|
|
if (keyword == eCSSKeyword_styleset ||
|
|
keyword == eCSSKeyword_character_variant) {
|
|
maxElems = MAX_ALLOWED_FEATURES;
|
|
}
|
|
return maxElems;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseSingleAlternate(int32_t& aWhichFeature,
|
|
nsCSSValue& aValue)
|
|
{
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
|
|
bool isIdent = (mToken.mType == eCSSToken_Ident);
|
|
if (mToken.mType != eCSSToken_Function && !isIdent) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
// ident ==> simple enumerated prop val (e.g. historical-forms)
|
|
// function ==> e.g. swash(flowing) styleset(alt-g, alt-m)
|
|
|
|
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
|
|
if (!(eCSSKeyword_UNKNOWN < keyword &&
|
|
nsCSSProps::FindKeyword(keyword,
|
|
(isIdent ?
|
|
nsCSSProps::kFontVariantAlternatesKTable :
|
|
nsCSSProps::kFontVariantAlternatesFuncsKTable),
|
|
aWhichFeature)))
|
|
{
|
|
// failed, pop token
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
if (isIdent) {
|
|
aValue.SetIntValue(aWhichFeature, eCSSUnit_Enumerated);
|
|
return true;
|
|
}
|
|
|
|
return ParseFunction(keyword, nullptr, VARIANT_IDENTIFIER,
|
|
1, MaxElementsForAlternateType(keyword), aValue);
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseFontVariantAlternates(nsCSSValue& aValue)
|
|
{
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
|
|
nullptr)) {
|
|
return true;
|
|
}
|
|
|
|
// iterate through parameters
|
|
nsCSSValue listValue;
|
|
int32_t feature, featureFlags = 0;
|
|
|
|
// if no functional values, this may be a list with a single, unused element
|
|
listValue.SetListValue();
|
|
|
|
nsCSSValueList* list = nullptr;
|
|
nsCSSValue value;
|
|
while (ParseSingleAlternate(feature, value)) {
|
|
|
|
// check to make sure value not already set
|
|
if (feature == 0 ||
|
|
feature & featureFlags) {
|
|
return false;
|
|
}
|
|
|
|
featureFlags |= feature;
|
|
|
|
// if function, need to add to the list of functions
|
|
if (value.GetUnit() == eCSSUnit_Function) {
|
|
if (!list) {
|
|
list = listValue.GetListValue();
|
|
} else {
|
|
list->mNext = new nsCSSValueList;
|
|
list = list->mNext;
|
|
}
|
|
list->mValue = value;
|
|
}
|
|
}
|
|
|
|
if (featureFlags == 0) {
|
|
// ParseSingleAlternate failed the first time through the loop.
|
|
return false;
|
|
}
|
|
|
|
nsCSSValue featureValue;
|
|
featureValue.SetIntValue(featureFlags, eCSSUnit_Enumerated);
|
|
aValue.SetPairValue(featureValue, listValue);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::MergeBitmaskValue(int32_t aNewValue,
|
|
const int32_t aMasks[],
|
|
int32_t& aMergedValue)
|
|
{
|
|
// check to make sure value not already set
|
|
if (aNewValue & aMergedValue) {
|
|
return false;
|
|
}
|
|
|
|
const int32_t *m = aMasks;
|
|
int32_t c = 0;
|
|
|
|
while (*m != MASK_END_VALUE) {
|
|
if (*m & aNewValue) {
|
|
c = aMergedValue & *m;
|
|
break;
|
|
}
|
|
m++;
|
|
}
|
|
|
|
if (c) {
|
|
return false;
|
|
}
|
|
|
|
aMergedValue |= aNewValue;
|
|
return true;
|
|
}
|
|
|
|
// aMasks - array of masks for mutually-exclusive property values,
|
|
// e.g. proportial-nums, tabular-nums
|
|
|
|
bool
|
|
CSSParserImpl::ParseBitmaskValues(nsCSSValue& aValue,
|
|
const KTableEntry aKeywordTable[],
|
|
const int32_t aMasks[])
|
|
{
|
|
// Parse at least one keyword
|
|
if (!ParseEnum(aValue, aKeywordTable)) {
|
|
return false;
|
|
}
|
|
|
|
// look for more values
|
|
nsCSSValue nextValue;
|
|
int32_t mergedValue = aValue.GetIntValue();
|
|
|
|
while (ParseEnum(nextValue, aKeywordTable))
|
|
{
|
|
if (!MergeBitmaskValue(nextValue.GetIntValue(), aMasks, mergedValue)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
aValue.SetIntValue(mergedValue, eCSSUnit_Enumerated);
|
|
|
|
return true;
|
|
}
|
|
|
|
static const int32_t maskEastAsian[] = {
|
|
NS_FONT_VARIANT_EAST_ASIAN_VARIANT_MASK,
|
|
NS_FONT_VARIANT_EAST_ASIAN_WIDTH_MASK,
|
|
MASK_END_VALUE
|
|
};
|
|
|
|
bool
|
|
CSSParserImpl::ParseFontVariantEastAsian(nsCSSValue& aValue)
|
|
{
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
|
|
nullptr)) {
|
|
return true;
|
|
}
|
|
|
|
NS_ASSERTION(maskEastAsian[ArrayLength(maskEastAsian) - 1] ==
|
|
MASK_END_VALUE,
|
|
"incorrectly terminated array");
|
|
|
|
return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantEastAsianKTable,
|
|
maskEastAsian);
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseContain(nsCSSValue& aValue)
|
|
{
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NONE,
|
|
nullptr)) {
|
|
return true;
|
|
}
|
|
static const int32_t maskContain[] = { MASK_END_VALUE };
|
|
if (!ParseBitmaskValues(aValue, nsCSSProps::kContainKTable, maskContain)) {
|
|
return false;
|
|
}
|
|
if (aValue.GetIntValue() & NS_STYLE_CONTAIN_STRICT) {
|
|
if (aValue.GetIntValue() != NS_STYLE_CONTAIN_STRICT) {
|
|
// Disallow any other keywords in combination with 'strict'.
|
|
return false;
|
|
}
|
|
// Strict implies layout, style, and paint.
|
|
// However, for serialization purposes, we keep the strict bit around.
|
|
aValue.SetIntValue(NS_STYLE_CONTAIN_STRICT |
|
|
NS_STYLE_CONTAIN_ALL_BITS, eCSSUnit_Enumerated);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static const int32_t maskLigatures[] = {
|
|
NS_FONT_VARIANT_LIGATURES_COMMON_MASK,
|
|
NS_FONT_VARIANT_LIGATURES_DISCRETIONARY_MASK,
|
|
NS_FONT_VARIANT_LIGATURES_HISTORICAL_MASK,
|
|
NS_FONT_VARIANT_LIGATURES_CONTEXTUAL_MASK,
|
|
MASK_END_VALUE
|
|
};
|
|
|
|
bool
|
|
CSSParserImpl::ParseFontVariantLigatures(nsCSSValue& aValue)
|
|
{
|
|
if (ParseSingleTokenVariant(aValue,
|
|
VARIANT_INHERIT | VARIANT_NORMAL | VARIANT_NONE,
|
|
nullptr)) {
|
|
return true;
|
|
}
|
|
|
|
NS_ASSERTION(maskLigatures[ArrayLength(maskLigatures) - 1] ==
|
|
MASK_END_VALUE,
|
|
"incorrectly terminated array");
|
|
|
|
return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantLigaturesKTable,
|
|
maskLigatures);
|
|
}
|
|
|
|
static const int32_t maskNumeric[] = {
|
|
NS_FONT_VARIANT_NUMERIC_FIGURE_MASK,
|
|
NS_FONT_VARIANT_NUMERIC_SPACING_MASK,
|
|
NS_FONT_VARIANT_NUMERIC_FRACTION_MASK,
|
|
MASK_END_VALUE
|
|
};
|
|
|
|
bool
|
|
CSSParserImpl::ParseFontVariantNumeric(nsCSSValue& aValue)
|
|
{
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
|
|
nullptr)) {
|
|
return true;
|
|
}
|
|
|
|
NS_ASSERTION(maskNumeric[ArrayLength(maskNumeric) - 1] ==
|
|
MASK_END_VALUE,
|
|
"incorrectly terminated array");
|
|
|
|
return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantNumericKTable,
|
|
maskNumeric);
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseFontVariant()
|
|
{
|
|
// parse single values - normal/inherit/none
|
|
nsCSSValue value;
|
|
nsCSSValue normal(eCSSUnit_Normal);
|
|
|
|
if (ParseSingleTokenVariant(value,
|
|
VARIANT_INHERIT | VARIANT_NORMAL | VARIANT_NONE,
|
|
nullptr)) {
|
|
AppendValue(eCSSProperty_font_variant_ligatures, value);
|
|
if (eCSSUnit_None == value.GetUnit()) {
|
|
// 'none' applies the value 'normal' to all properties other
|
|
// than 'font-variant-ligatures'
|
|
value.SetNormalValue();
|
|
}
|
|
AppendValue(eCSSProperty_font_variant_alternates, value);
|
|
AppendValue(eCSSProperty_font_variant_caps, value);
|
|
AppendValue(eCSSProperty_font_variant_east_asian, value);
|
|
AppendValue(eCSSProperty_font_variant_numeric, value);
|
|
AppendValue(eCSSProperty_font_variant_position, value);
|
|
return true;
|
|
}
|
|
|
|
// set each of the individual subproperties
|
|
int32_t altFeatures = 0, capsFeatures = 0, eastAsianFeatures = 0,
|
|
ligFeatures = 0, numericFeatures = 0, posFeatures = 0;
|
|
nsCSSValue altListValue;
|
|
nsCSSValueList* altList = nullptr;
|
|
|
|
// if no functional values, this may be a list with a single, unused element
|
|
altListValue.SetListValue();
|
|
|
|
bool foundValid = false; // found at least one proper value
|
|
while (GetToken(true)) {
|
|
// only an ident or a function at this point
|
|
bool isFunction = (mToken.mType == eCSSToken_Function);
|
|
if (mToken.mType != eCSSToken_Ident && !isFunction) {
|
|
UngetToken();
|
|
break;
|
|
}
|
|
|
|
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
|
|
if (keyword == eCSSKeyword_UNKNOWN) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
int32_t feature;
|
|
|
|
// function? ==> font-variant-alternates
|
|
if (isFunction) {
|
|
if (!nsCSSProps::FindKeyword(keyword,
|
|
nsCSSProps::kFontVariantAlternatesFuncsKTable,
|
|
feature) ||
|
|
(feature & altFeatures)) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
altFeatures |= feature;
|
|
nsCSSValue funcValue;
|
|
if (!ParseFunction(keyword, nullptr, VARIANT_IDENTIFIER, 1,
|
|
MaxElementsForAlternateType(keyword), funcValue) ||
|
|
funcValue.GetUnit() != eCSSUnit_Function) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
if (!altList) {
|
|
altList = altListValue.GetListValue();
|
|
} else {
|
|
altList->mNext = new nsCSSValueList;
|
|
altList = altList->mNext;
|
|
}
|
|
altList->mValue = funcValue;
|
|
} else if (nsCSSProps::FindKeyword(keyword,
|
|
nsCSSProps::kFontVariantCapsKTable,
|
|
feature)) {
|
|
if (capsFeatures != 0) {
|
|
// multiple values for font-variant-caps
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
capsFeatures = feature;
|
|
} else if (nsCSSProps::FindKeyword(keyword,
|
|
nsCSSProps::kFontVariantAlternatesKTable,
|
|
feature)) {
|
|
if (feature & altFeatures) {
|
|
// same value repeated
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
altFeatures |= feature;
|
|
} else if (nsCSSProps::FindKeyword(keyword,
|
|
nsCSSProps::kFontVariantEastAsianKTable,
|
|
feature)) {
|
|
if (!MergeBitmaskValue(feature, maskEastAsian, eastAsianFeatures)) {
|
|
// multiple mutually exclusive values
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
} else if (nsCSSProps::FindKeyword(keyword,
|
|
nsCSSProps::kFontVariantLigaturesKTable,
|
|
feature)) {
|
|
if (keyword == eCSSKeyword_none ||
|
|
!MergeBitmaskValue(feature, maskLigatures, ligFeatures)) {
|
|
// none or multiple mutually exclusive values
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
} else if (nsCSSProps::FindKeyword(keyword,
|
|
nsCSSProps::kFontVariantNumericKTable,
|
|
feature)) {
|
|
if (!MergeBitmaskValue(feature, maskNumeric, numericFeatures)) {
|
|
// multiple mutually exclusive values
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
} else if (nsCSSProps::FindKeyword(keyword,
|
|
nsCSSProps::kFontVariantPositionKTable,
|
|
feature)) {
|
|
if (posFeatures != 0) {
|
|
// multiple values for font-variant-caps
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
posFeatures = feature;
|
|
} else {
|
|
// bogus keyword, bail...
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
foundValid = true;
|
|
}
|
|
|
|
if (!foundValid) {
|
|
return false;
|
|
}
|
|
|
|
if (altFeatures) {
|
|
nsCSSValue featureValue;
|
|
featureValue.SetIntValue(altFeatures, eCSSUnit_Enumerated);
|
|
value.SetPairValue(featureValue, altListValue);
|
|
AppendValue(eCSSProperty_font_variant_alternates, value);
|
|
} else {
|
|
AppendValue(eCSSProperty_font_variant_alternates, normal);
|
|
}
|
|
|
|
if (capsFeatures) {
|
|
value.SetIntValue(capsFeatures, eCSSUnit_Enumerated);
|
|
AppendValue(eCSSProperty_font_variant_caps, value);
|
|
} else {
|
|
AppendValue(eCSSProperty_font_variant_caps, normal);
|
|
}
|
|
|
|
if (eastAsianFeatures) {
|
|
value.SetIntValue(eastAsianFeatures, eCSSUnit_Enumerated);
|
|
AppendValue(eCSSProperty_font_variant_east_asian, value);
|
|
} else {
|
|
AppendValue(eCSSProperty_font_variant_east_asian, normal);
|
|
}
|
|
|
|
if (ligFeatures) {
|
|
value.SetIntValue(ligFeatures, eCSSUnit_Enumerated);
|
|
AppendValue(eCSSProperty_font_variant_ligatures, value);
|
|
} else {
|
|
AppendValue(eCSSProperty_font_variant_ligatures, normal);
|
|
}
|
|
|
|
if (numericFeatures) {
|
|
value.SetIntValue(numericFeatures, eCSSUnit_Enumerated);
|
|
AppendValue(eCSSProperty_font_variant_numeric, value);
|
|
} else {
|
|
AppendValue(eCSSProperty_font_variant_numeric, normal);
|
|
}
|
|
|
|
if (posFeatures) {
|
|
value.SetIntValue(posFeatures, eCSSUnit_Enumerated);
|
|
AppendValue(eCSSProperty_font_variant_position, value);
|
|
} else {
|
|
AppendValue(eCSSProperty_font_variant_position, normal);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseFontWeight(nsCSSValue& aValue)
|
|
{
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_HKI | VARIANT_SYSFONT,
|
|
nsCSSProps::kFontWeightKTable)) {
|
|
if (eCSSUnit_Integer == aValue.GetUnit()) { // ensure unit value
|
|
int32_t intValue = aValue.GetIntValue();
|
|
if ((100 <= intValue) &&
|
|
(intValue <= 900) &&
|
|
(0 == (intValue % 100))) {
|
|
return true;
|
|
} else {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseOneFamily(nsAString& aFamily,
|
|
bool& aOneKeyword,
|
|
bool& aQuoted)
|
|
{
|
|
if (!GetToken(true))
|
|
return false;
|
|
|
|
nsCSSToken* tk = &mToken;
|
|
|
|
aOneKeyword = false;
|
|
aQuoted = false;
|
|
if (eCSSToken_Ident == tk->mType) {
|
|
aOneKeyword = true;
|
|
aFamily.Append(tk->mIdent);
|
|
for (;;) {
|
|
if (!GetToken(false))
|
|
break;
|
|
|
|
if (eCSSToken_Ident == tk->mType) {
|
|
aOneKeyword = false;
|
|
// We had at least another keyword before.
|
|
// "If a sequence of identifiers is given as a font family name,
|
|
// the computed value is the name converted to a string by joining
|
|
// all the identifiers in the sequence by single spaces."
|
|
// -- CSS 2.1, section 15.3
|
|
// Whitespace tokens do not actually matter,
|
|
// identifier tokens can be separated by comments.
|
|
aFamily.Append(char16_t(' '));
|
|
aFamily.Append(tk->mIdent);
|
|
} else if (eCSSToken_Whitespace != tk->mType) {
|
|
UngetToken();
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
|
|
} else if (eCSSToken_String == tk->mType) {
|
|
aQuoted = true;
|
|
aFamily.Append(tk->mIdent); // XXX What if it had escaped quotes?
|
|
return true;
|
|
|
|
} else {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
static bool
|
|
AppendGeneric(nsCSSKeyword aKeyword, FontFamilyList *aFamilyList)
|
|
{
|
|
switch (aKeyword) {
|
|
case eCSSKeyword_serif:
|
|
aFamilyList->Append(FontFamilyName(eFamily_serif));
|
|
return true;
|
|
case eCSSKeyword_sans_serif:
|
|
aFamilyList->Append(FontFamilyName(eFamily_sans_serif));
|
|
return true;
|
|
case eCSSKeyword_monospace:
|
|
aFamilyList->Append(FontFamilyName(eFamily_monospace));
|
|
return true;
|
|
case eCSSKeyword_cursive:
|
|
aFamilyList->Append(FontFamilyName(eFamily_cursive));
|
|
return true;
|
|
case eCSSKeyword_fantasy:
|
|
aFamilyList->Append(FontFamilyName(eFamily_fantasy));
|
|
return true;
|
|
case eCSSKeyword__moz_fixed:
|
|
aFamilyList->Append(FontFamilyName(eFamily_moz_fixed));
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseFamily(nsCSSValue& aValue)
|
|
{
|
|
RefPtr<css::FontFamilyListRefCnt> familyList =
|
|
new css::FontFamilyListRefCnt();
|
|
nsAutoString family;
|
|
bool single, quoted;
|
|
|
|
// keywords only have meaning in the first position
|
|
if (!ParseOneFamily(family, single, quoted))
|
|
return false;
|
|
|
|
// check for keywords, but only when keywords appear by themselves
|
|
// i.e. not in compounds such as font-family: default blah;
|
|
bool foundGeneric = false;
|
|
if (single) {
|
|
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(family);
|
|
switch (keyword) {
|
|
case eCSSKeyword_inherit:
|
|
aValue.SetInheritValue();
|
|
return true;
|
|
case eCSSKeyword_default:
|
|
// 605231 - don't parse unquoted 'default' reserved keyword
|
|
return false;
|
|
case eCSSKeyword_initial:
|
|
aValue.SetInitialValue();
|
|
return true;
|
|
case eCSSKeyword_unset:
|
|
if (nsLayoutUtils::UnsetValueEnabled()) {
|
|
aValue.SetUnsetValue();
|
|
return true;
|
|
}
|
|
break;
|
|
case eCSSKeyword__moz_use_system_font:
|
|
if (!IsParsingCompoundProperty()) {
|
|
aValue.SetSystemFontValue();
|
|
return true;
|
|
}
|
|
break;
|
|
default:
|
|
foundGeneric = AppendGeneric(keyword, familyList);
|
|
}
|
|
}
|
|
|
|
if (!foundGeneric) {
|
|
familyList->Append(
|
|
FontFamilyName(family, (quoted ? eQuotedName : eUnquotedName)));
|
|
}
|
|
|
|
for (;;) {
|
|
if (!ExpectSymbol(',', true))
|
|
break;
|
|
|
|
nsAutoString nextFamily;
|
|
if (!ParseOneFamily(nextFamily, single, quoted))
|
|
return false;
|
|
|
|
// at this point unquoted keywords are not allowed
|
|
// as font family names but can appear within names
|
|
foundGeneric = false;
|
|
if (single) {
|
|
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(nextFamily);
|
|
switch (keyword) {
|
|
case eCSSKeyword_inherit:
|
|
case eCSSKeyword_initial:
|
|
case eCSSKeyword_default:
|
|
case eCSSKeyword__moz_use_system_font:
|
|
return false;
|
|
case eCSSKeyword_unset:
|
|
if (nsLayoutUtils::UnsetValueEnabled()) {
|
|
return false;
|
|
}
|
|
break;
|
|
default:
|
|
foundGeneric = AppendGeneric(keyword, familyList);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundGeneric) {
|
|
familyList->Append(
|
|
FontFamilyName(nextFamily, (quoted ? eQuotedName : eUnquotedName)));
|
|
}
|
|
}
|
|
|
|
if (familyList->IsEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
aValue.SetFontFamilyListValue(familyList);
|
|
return true;
|
|
}
|
|
|
|
// src: ( uri-src | local-src ) (',' ( uri-src | local-src ) )*
|
|
// uri-src: uri [ 'format(' string ( ',' string )* ')' ]
|
|
// local-src: 'local(' ( string | ident ) ')'
|
|
|
|
bool
|
|
CSSParserImpl::ParseFontSrc(nsCSSValue& aValue)
|
|
{
|
|
// could we maybe turn nsCSSValue::Array into InfallibleTArray<nsCSSValue>?
|
|
InfallibleTArray<nsCSSValue> values;
|
|
nsCSSValue cur;
|
|
for (;;) {
|
|
if (!GetToken(true))
|
|
break;
|
|
|
|
if (mToken.mType == eCSSToken_URL) {
|
|
SetValueToURL(cur, mToken.mIdent);
|
|
values.AppendElement(cur);
|
|
if (!ParseFontSrcFormat(values))
|
|
return false;
|
|
|
|
} else if (mToken.mType == eCSSToken_Function &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("local")) {
|
|
// css3-fonts does not specify a formal grammar for local().
|
|
// The text permits both unquoted identifiers and quoted
|
|
// strings. We resolve this ambiguity in the spec by
|
|
// assuming that the appropriate production is a single
|
|
// <family-name>, possibly surrounded by whitespace.
|
|
|
|
nsAutoString family;
|
|
bool single, quoted;
|
|
if (!ParseOneFamily(family, single, quoted)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
if (!ExpectSymbol(')', true)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
// reject generics
|
|
if (single) {
|
|
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(family);
|
|
switch (keyword) {
|
|
case eCSSKeyword_serif:
|
|
case eCSSKeyword_sans_serif:
|
|
case eCSSKeyword_monospace:
|
|
case eCSSKeyword_cursive:
|
|
case eCSSKeyword_fantasy:
|
|
case eCSSKeyword__moz_fixed:
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
cur.SetStringValue(family, eCSSUnit_Local_Font);
|
|
values.AppendElement(cur);
|
|
} else {
|
|
// We don't know what to do with this token; unget it and error out
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
if (!ExpectSymbol(',', true))
|
|
break;
|
|
}
|
|
|
|
if (values.Length() == 0)
|
|
return false;
|
|
|
|
RefPtr<nsCSSValue::Array> srcVals
|
|
= nsCSSValue::Array::Create(values.Length());
|
|
|
|
uint32_t i;
|
|
for (i = 0; i < values.Length(); i++)
|
|
srcVals->Item(i) = values[i];
|
|
aValue.SetArrayValue(srcVals, eCSSUnit_Array);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseFontSrcFormat(InfallibleTArray<nsCSSValue> & values)
|
|
{
|
|
if (!GetToken(true))
|
|
return true; // EOF harmless here
|
|
if (mToken.mType != eCSSToken_Function ||
|
|
!mToken.mIdent.LowerCaseEqualsLiteral("format")) {
|
|
UngetToken();
|
|
return true;
|
|
}
|
|
|
|
do {
|
|
if (!GetToken(true))
|
|
return false; // EOF - no need for SkipUntil
|
|
|
|
if (mToken.mType != eCSSToken_String) {
|
|
UngetToken();
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
nsCSSValue cur(mToken.mIdent, eCSSUnit_Font_Format);
|
|
values.AppendElement(cur);
|
|
} while (ExpectSymbol(',', true));
|
|
|
|
if (!ExpectSymbol(')', true)) {
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// font-ranges: urange ( ',' urange )*
|
|
bool
|
|
CSSParserImpl::ParseFontRanges(nsCSSValue& aValue)
|
|
{
|
|
InfallibleTArray<uint32_t> ranges;
|
|
for (;;) {
|
|
if (!GetToken(true))
|
|
break;
|
|
|
|
if (mToken.mType != eCSSToken_URange) {
|
|
UngetToken();
|
|
break;
|
|
}
|
|
|
|
// An invalid range token is a parsing error, causing the entire
|
|
// descriptor to be ignored.
|
|
if (!mToken.mIntegerValid)
|
|
return false;
|
|
|
|
uint32_t low = mToken.mInteger;
|
|
uint32_t high = mToken.mInteger2;
|
|
|
|
// A range that descends, or a range that is entirely outside the
|
|
// current range of Unicode (U+0-10FFFF) is ignored, but does not
|
|
// invalidate the descriptor. A range that straddles the high end
|
|
// is clipped.
|
|
if (low <= 0x10FFFF && low <= high) {
|
|
if (high > 0x10FFFF)
|
|
high = 0x10FFFF;
|
|
|
|
ranges.AppendElement(low);
|
|
ranges.AppendElement(high);
|
|
}
|
|
if (!ExpectSymbol(',', true))
|
|
break;
|
|
}
|
|
|
|
if (ranges.Length() == 0)
|
|
return false;
|
|
|
|
RefPtr<nsCSSValue::Array> srcVals
|
|
= nsCSSValue::Array::Create(ranges.Length());
|
|
|
|
for (uint32_t i = 0; i < ranges.Length(); i++)
|
|
srcVals->Item(i).SetIntValue(ranges[i], eCSSUnit_Integer);
|
|
aValue.SetArrayValue(srcVals, eCSSUnit_Array);
|
|
return true;
|
|
}
|
|
|
|
// font-feature-settings: normal | <feature-tag-value> [, <feature-tag-value>]*
|
|
// <feature-tag-value> = <string> [ <integer> | on | off ]?
|
|
|
|
// minimum - "tagx", "tagy", "tagz"
|
|
// edge error case - "tagx" on 1, "tagx" "tagy", "tagx" -1, "tagx" big
|
|
|
|
// pair value is always x = string, y = int
|
|
|
|
// font feature tags must be four ASCII characters
|
|
#define FEATURE_TAG_LENGTH 4
|
|
|
|
static bool
|
|
ValidFontFeatureTag(const nsString& aTag)
|
|
{
|
|
if (aTag.Length() != FEATURE_TAG_LENGTH) {
|
|
return false;
|
|
}
|
|
uint32_t i;
|
|
for (i = 0; i < FEATURE_TAG_LENGTH; i++) {
|
|
uint32_t ch = aTag[i];
|
|
if (ch < 0x20 || ch > 0x7e) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseFontFeatureSettings(nsCSSValue& aValue)
|
|
{
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
|
|
nullptr)) {
|
|
return true;
|
|
}
|
|
|
|
nsCSSValuePairList *cur = aValue.SetPairListValue();
|
|
for (;;) {
|
|
// feature tag
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType != eCSSToken_String ||
|
|
!ValidFontFeatureTag(mToken.mIdent)) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
cur->mXValue.SetStringValue(mToken.mIdent, eCSSUnit_String);
|
|
|
|
if (!GetToken(true)) {
|
|
cur->mYValue.SetIntValue(1, eCSSUnit_Integer);
|
|
break;
|
|
}
|
|
|
|
// optional value or on/off keyword
|
|
if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid &&
|
|
mToken.mInteger >= 0) {
|
|
cur->mYValue.SetIntValue(mToken.mInteger, eCSSUnit_Integer);
|
|
} else if (mToken.mType == eCSSToken_Ident &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("on")) {
|
|
cur->mYValue.SetIntValue(1, eCSSUnit_Integer);
|
|
} else if (mToken.mType == eCSSToken_Ident &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("off")) {
|
|
cur->mYValue.SetIntValue(0, eCSSUnit_Integer);
|
|
} else {
|
|
// something other than value/on/off, set default value
|
|
cur->mYValue.SetIntValue(1, eCSSUnit_Integer);
|
|
UngetToken();
|
|
}
|
|
|
|
if (!ExpectSymbol(',', true)) {
|
|
break;
|
|
}
|
|
|
|
cur->mNext = new nsCSSValuePairList;
|
|
cur = cur->mNext;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseListStyle()
|
|
{
|
|
// 'list-style' can accept 'none' for two different subproperties,
|
|
// 'list-style-type' and 'list-style-image'. In order to accept
|
|
// 'none' as the value of either but still allow another value for
|
|
// either, we need to ensure that the first 'none' we find gets
|
|
// allocated to a dummy property instead. Since parse function for
|
|
// 'list-style-type' could accept values for 'list-style-position',
|
|
// we put position in front of type.
|
|
static const nsCSSPropertyID listStyleIDs[] = {
|
|
eCSSPropertyExtra_x_none_value,
|
|
eCSSProperty_list_style_position,
|
|
eCSSProperty_list_style_type,
|
|
eCSSProperty_list_style_image
|
|
};
|
|
|
|
nsCSSValue values[MOZ_ARRAY_LENGTH(listStyleIDs)];
|
|
int32_t found =
|
|
ParseChoice(values, listStyleIDs, ArrayLength(listStyleIDs));
|
|
if (found < 1) {
|
|
return false;
|
|
}
|
|
|
|
if ((found & (1|4|8)) == (1|4|8)) {
|
|
if (values[0].GetUnit() == eCSSUnit_None) {
|
|
// We found a 'none' plus another value for both of
|
|
// 'list-style-type' and 'list-style-image'. This is a parse
|
|
// error, since the 'none' has to count for at least one of them.
|
|
return false;
|
|
} else {
|
|
NS_ASSERTION(found == (1|2|4|8) && values[0] == values[1] &&
|
|
values[0] == values[2] && values[0] == values[3],
|
|
"should be a special value");
|
|
}
|
|
}
|
|
|
|
if ((found & 2) == 0) {
|
|
values[1].SetIntValue(NS_STYLE_LIST_STYLE_POSITION_OUTSIDE,
|
|
eCSSUnit_Enumerated);
|
|
}
|
|
if ((found & 4) == 0) {
|
|
// Provide default values
|
|
nsString type = (found & 1) ?
|
|
NS_LITERAL_STRING("none") : NS_LITERAL_STRING("disc");
|
|
values[2].SetStringValue(type, eCSSUnit_Ident);
|
|
}
|
|
if ((found & 8) == 0) {
|
|
values[3].SetNoneValue();
|
|
}
|
|
|
|
// Start at 1 to avoid appending fake value.
|
|
for (uint32_t index = 1; index < ArrayLength(listStyleIDs); ++index) {
|
|
AppendValue(listStyleIDs[index], values[index]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseListStyleType(nsCSSValue& aValue)
|
|
{
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_STRING,
|
|
nullptr)) {
|
|
return true;
|
|
}
|
|
|
|
if (ParseCounterStyleNameValue(aValue) || ParseSymbols(aValue)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseMargin()
|
|
{
|
|
static const nsCSSPropertyID kMarginSideIDs[] = {
|
|
eCSSProperty_margin_top,
|
|
eCSSProperty_margin_right,
|
|
eCSSProperty_margin_bottom,
|
|
eCSSProperty_margin_left
|
|
};
|
|
|
|
return ParseBoxProperties(kMarginSideIDs);
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseObjectPosition()
|
|
{
|
|
nsCSSValue value;
|
|
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr) &&
|
|
!ParsePositionValue(value)) {
|
|
return false;
|
|
}
|
|
AppendValue(eCSSProperty_object_position, value);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseOutline()
|
|
{
|
|
const int32_t numProps = 3;
|
|
static const nsCSSPropertyID kOutlineIDs[] = {
|
|
eCSSProperty_outline_color,
|
|
eCSSProperty_outline_style,
|
|
eCSSProperty_outline_width
|
|
};
|
|
|
|
nsCSSValue values[numProps];
|
|
int32_t found = ParseChoice(values, kOutlineIDs, numProps);
|
|
if (found < 1) {
|
|
return false;
|
|
}
|
|
|
|
// Provide default values
|
|
if ((found & 1) == 0) { // Provide default outline-color
|
|
values[0].SetIntValue(NS_STYLE_COLOR_MOZ_USE_TEXT_COLOR, eCSSUnit_Enumerated);
|
|
}
|
|
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_STYLE_COLOR_MOZ_USE_TEXT_COLOR,
|
|
eCSSUnit_Enumerated);
|
|
}
|
|
|
|
for (int32_t index = 0; index < numProps; index++) {
|
|
AppendValue(kTextDecorationIDs[index], values[index]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseTextEmphasis()
|
|
{
|
|
static constexpr nsCSSPropertyID kTextEmphasisIDs[] = {
|
|
eCSSProperty_text_emphasis_style,
|
|
eCSSProperty_text_emphasis_color
|
|
};
|
|
constexpr int32_t numProps = MOZ_ARRAY_LENGTH(kTextEmphasisIDs);
|
|
nsCSSValue values[numProps];
|
|
|
|
int32_t found = ParseChoice(values, kTextEmphasisIDs, numProps);
|
|
if (found < 1) {
|
|
return false;
|
|
}
|
|
|
|
if (!(found & 1)) { // Provide default text-emphasis-style
|
|
values[0].SetNoneValue();
|
|
}
|
|
if (!(found & 2)) { // Provide default text-emphasis-color
|
|
values[1].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
|
|
}
|
|
|
|
for (int32_t index = 0; index < numProps; index++) {
|
|
AppendValue(kTextEmphasisIDs[index], values[index]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseTextEmphasisPosition(nsCSSValue& aValue)
|
|
{
|
|
static_assert((NS_STYLE_TEXT_EMPHASIS_POSITION_OVER ^
|
|
NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER ^
|
|
NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT ^
|
|
NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT) ==
|
|
(NS_STYLE_TEXT_EMPHASIS_POSITION_OVER |
|
|
NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER |
|
|
NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT |
|
|
NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT),
|
|
"text-emphasis-position constants should be bitmasks");
|
|
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
|
|
return true;
|
|
}
|
|
|
|
nsCSSValue first, second;
|
|
const auto& kTable = nsCSSProps::kTextEmphasisPositionKTable;
|
|
if (!ParseSingleTokenVariant(first, VARIANT_KEYWORD, kTable) ||
|
|
!ParseSingleTokenVariant(second, VARIANT_KEYWORD, kTable)) {
|
|
return false;
|
|
}
|
|
|
|
auto firstValue = first.GetIntValue();
|
|
auto secondValue = second.GetIntValue();
|
|
if ((firstValue == NS_STYLE_TEXT_EMPHASIS_POSITION_OVER ||
|
|
firstValue == NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER) ==
|
|
(secondValue == NS_STYLE_TEXT_EMPHASIS_POSITION_OVER ||
|
|
secondValue == NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER)) {
|
|
return false;
|
|
}
|
|
|
|
aValue.SetIntValue(firstValue | secondValue, eCSSUnit_Enumerated);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseTextEmphasisStyle(nsCSSValue& aValue)
|
|
{
|
|
static_assert((NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK ^
|
|
NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK) ==
|
|
(NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK |
|
|
NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK),
|
|
"text-emphasis-style shape and fill constants "
|
|
"should not intersect");
|
|
static_assert(NS_STYLE_TEXT_EMPHASIS_STYLE_FILLED == 0,
|
|
"Making 'filled' zero ensures that if neither 'filled' nor "
|
|
"'open' is specified, we compute it to 'filled' per spec");
|
|
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_HOS, nullptr)) {
|
|
return true;
|
|
}
|
|
|
|
nsCSSValue first, second;
|
|
const auto& fillKTable = nsCSSProps::kTextEmphasisStyleFillKTable;
|
|
const auto& shapeKTable = nsCSSProps::kTextEmphasisStyleShapeKTable;
|
|
if (ParseSingleTokenVariant(first, VARIANT_KEYWORD, fillKTable)) {
|
|
ParseSingleTokenVariant(second, VARIANT_KEYWORD, shapeKTable);
|
|
} else if (ParseSingleTokenVariant(first, VARIANT_KEYWORD, shapeKTable)) {
|
|
ParseSingleTokenVariant(second, VARIANT_KEYWORD, fillKTable);
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
auto value = first.GetIntValue();
|
|
if (second.GetUnit() == eCSSUnit_Enumerated) {
|
|
value |= second.GetIntValue();
|
|
}
|
|
aValue.SetIntValue(value, eCSSUnit_Enumerated);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseTextAlign(nsCSSValue& aValue, const KTableEntry aTable[])
|
|
{
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
|
|
// 'inherit', 'initial' and 'unset' must be alone
|
|
return true;
|
|
}
|
|
|
|
nsCSSValue left;
|
|
if (!ParseSingleTokenVariant(left, VARIANT_KEYWORD, aTable)) {
|
|
return false;
|
|
}
|
|
|
|
if (!nsLayoutUtils::IsTextAlignUnsafeValueEnabled()) {
|
|
aValue = left;
|
|
return true;
|
|
}
|
|
|
|
nsCSSValue right;
|
|
if (ParseSingleTokenVariant(right, VARIANT_KEYWORD, aTable)) {
|
|
// 'true' must be combined with some other value than 'true'.
|
|
if (left.GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE &&
|
|
right.GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) {
|
|
return false;
|
|
}
|
|
aValue.SetPairValue(left, right);
|
|
} else {
|
|
// Single value 'true' is not allowed.
|
|
if (left.GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) {
|
|
return false;
|
|
}
|
|
aValue = left;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseTextAlign(nsCSSValue& aValue)
|
|
{
|
|
return ParseTextAlign(aValue, nsCSSProps::kTextAlignKTable);
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseTextAlignLast(nsCSSValue& aValue)
|
|
{
|
|
return ParseTextAlign(aValue, nsCSSProps::kTextAlignLastKTable);
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseTextDecorationLine(nsCSSValue& aValue)
|
|
{
|
|
static_assert((NS_STYLE_TEXT_DECORATION_LINE_NONE ^
|
|
NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE ^
|
|
NS_STYLE_TEXT_DECORATION_LINE_OVERLINE ^
|
|
NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH ^
|
|
NS_STYLE_TEXT_DECORATION_LINE_BLINK ^
|
|
NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS) ==
|
|
(NS_STYLE_TEXT_DECORATION_LINE_NONE |
|
|
NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE |
|
|
NS_STYLE_TEXT_DECORATION_LINE_OVERLINE |
|
|
NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH |
|
|
NS_STYLE_TEXT_DECORATION_LINE_BLINK |
|
|
NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS),
|
|
"text decoration constants need to be bitmasks");
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_HK,
|
|
nsCSSProps::kTextDecorationLineKTable)) {
|
|
if (eCSSUnit_Enumerated == aValue.GetUnit()) {
|
|
int32_t intValue = aValue.GetIntValue();
|
|
if (intValue != NS_STYLE_TEXT_DECORATION_LINE_NONE) {
|
|
// look for more keywords
|
|
nsCSSValue keyword;
|
|
int32_t index;
|
|
for (index = 0; index < 3; index++) {
|
|
if (ParseEnum(keyword, nsCSSProps::kTextDecorationLineKTable)) {
|
|
int32_t newValue = keyword.GetIntValue();
|
|
if (newValue == NS_STYLE_TEXT_DECORATION_LINE_NONE ||
|
|
newValue & intValue) {
|
|
// 'none' keyword in conjuction with others is not allowed, and
|
|
// duplicate keyword is not allowed.
|
|
return false;
|
|
}
|
|
intValue |= newValue;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
aValue.SetIntValue(intValue, eCSSUnit_Enumerated);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseTextOverflow(nsCSSValue& aValue)
|
|
{
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
|
|
// 'inherit', 'initial' and 'unset' must be alone
|
|
return true;
|
|
}
|
|
|
|
nsCSSValue left;
|
|
if (!ParseSingleTokenVariant(left, VARIANT_KEYWORD | VARIANT_STRING,
|
|
nsCSSProps::kTextOverflowKTable))
|
|
return false;
|
|
|
|
nsCSSValue right;
|
|
if (ParseSingleTokenVariant(right, VARIANT_KEYWORD | VARIANT_STRING,
|
|
nsCSSProps::kTextOverflowKTable))
|
|
aValue.SetPairValue(left, right);
|
|
else {
|
|
aValue = left;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseTouchAction(nsCSSValue& aValue)
|
|
{
|
|
// Avaliable values of property touch-action:
|
|
// auto | none | [pan-x || pan-y] | manipulation
|
|
|
|
if (!ParseSingleTokenVariant(aValue, VARIANT_HK,
|
|
nsCSSProps::kTouchActionKTable)) {
|
|
return false;
|
|
}
|
|
|
|
// Auto and None keywords aren't allowed in conjunction with others.
|
|
// Also inherit, initial and unset values are available.
|
|
if (eCSSUnit_Enumerated != aValue.GetUnit()) {
|
|
return true;
|
|
}
|
|
|
|
int32_t intValue = aValue.GetIntValue();
|
|
nsCSSValue nextValue;
|
|
if (ParseEnum(nextValue, nsCSSProps::kTouchActionKTable)) {
|
|
int32_t nextIntValue = nextValue.GetIntValue();
|
|
|
|
// duplicates aren't allowed.
|
|
if (nextIntValue & intValue) {
|
|
return false;
|
|
}
|
|
|
|
// Auto and None and Manipulation is not allowed in conjunction with others.
|
|
if ((intValue | nextIntValue) & (NS_STYLE_TOUCH_ACTION_NONE |
|
|
NS_STYLE_TOUCH_ACTION_AUTO |
|
|
NS_STYLE_TOUCH_ACTION_MANIPULATION)) {
|
|
return false;
|
|
}
|
|
|
|
aValue.SetIntValue(nextIntValue | intValue, eCSSUnit_Enumerated);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseTextCombineUpright(nsCSSValue& aValue)
|
|
{
|
|
if (!ParseSingleTokenVariant(aValue, VARIANT_HK,
|
|
nsCSSProps::kTextCombineUprightKTable)) {
|
|
return false;
|
|
}
|
|
|
|
// if 'digits', need to check for an explicit number [2, 3, 4]
|
|
if (eCSSUnit_Enumerated == aValue.GetUnit() &&
|
|
aValue.GetIntValue() == NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_2) {
|
|
if (!nsLayoutUtils::TextCombineUprightDigitsEnabled()) {
|
|
return false;
|
|
}
|
|
if (!GetToken(true)) {
|
|
return true;
|
|
}
|
|
if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid) {
|
|
switch (mToken.mInteger) {
|
|
case 2: // already set, nothing to do
|
|
break;
|
|
case 3:
|
|
aValue.SetIntValue(NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_3,
|
|
eCSSUnit_Enumerated);
|
|
break;
|
|
case 4:
|
|
aValue.SetIntValue(NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_4,
|
|
eCSSUnit_Enumerated);
|
|
break;
|
|
default:
|
|
// invalid digits value
|
|
return false;
|
|
}
|
|
} else {
|
|
UngetToken();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////
|
|
// transform Parsing Implementation
|
|
|
|
/* Reads a function list of arguments and consumes the closing parenthesis.
|
|
* Do not call this function directly; it's meant to be called from
|
|
* ParseFunction.
|
|
*/
|
|
bool
|
|
CSSParserImpl::ParseFunctionInternals(const uint32_t aVariantMask[],
|
|
uint32_t aVariantMaskAll,
|
|
uint16_t aMinElems,
|
|
uint16_t aMaxElems,
|
|
InfallibleTArray<nsCSSValue> &aOutput)
|
|
{
|
|
NS_ASSERTION((aVariantMask && !aVariantMaskAll) ||
|
|
(!aVariantMask && aVariantMaskAll),
|
|
"only one of the two variant mask parameters can be set");
|
|
|
|
for (uint16_t index = 0; index < aMaxElems; ++index) {
|
|
nsCSSValue newValue;
|
|
uint32_t m = aVariantMaskAll ? aVariantMaskAll : aVariantMask[index];
|
|
if (ParseVariant(newValue, m, nullptr) != CSSParseResult::Ok) {
|
|
break;
|
|
}
|
|
|
|
if (nsCSSValue::IsFloatUnit(newValue.GetUnit())) {
|
|
// Clamp infinity or -infinity values to max float or -max float to avoid
|
|
// calculations with infinity.
|
|
newValue.SetFloatValue(
|
|
mozilla::clamped(newValue.GetFloatValue(),
|
|
-std::numeric_limits<float>::max(),
|
|
std::numeric_limits<float>::max()),
|
|
newValue.GetUnit());
|
|
}
|
|
|
|
aOutput.AppendElement(newValue);
|
|
|
|
if (ExpectSymbol(',', true)) {
|
|
// Move on to the next argument if we see a comma.
|
|
continue;
|
|
}
|
|
|
|
if (ExpectSymbol(')', true)) {
|
|
// Make sure we've read enough symbols if we see a closing parenthesis.
|
|
return (index + 1) >= aMinElems;
|
|
}
|
|
|
|
// Only a comma or a closing parenthesis is valid after an argument.
|
|
break;
|
|
}
|
|
|
|
// If we're here, we've hit an error without seeing a closing parenthesis or
|
|
// we've read too many elements without seeing a closing parenthesis.
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
/* Parses a function [ input of the form (a [, b]*) ] and stores it
|
|
* as an nsCSSValue that holds a function of the form
|
|
* function-name arg1 arg2 ... argN
|
|
*
|
|
* On error, the return value is false.
|
|
*
|
|
* @param aFunction The name of the function that we're reading.
|
|
* @param aAllowedTypes An array of values corresponding to the legal
|
|
* types for each element in the function. The zeroth element in the
|
|
* array corresponds to the first function parameter, etc. The length
|
|
* of this array _must_ be greater than or equal to aMaxElems or the
|
|
* behavior is undefined. If not null, aAllowTypesAll must be 0.
|
|
* @param aAllowedTypesAll If set, every element tested for these types
|
|
* @param aMinElems Minimum number of elements to read. Reading fewer than
|
|
* this many elements will result in the function failing.
|
|
* @param aMaxElems Maximum number of elements to read. Reading more than
|
|
* this many elements will result in the function failing.
|
|
* @param aValue (out) The value that was parsed.
|
|
*/
|
|
bool
|
|
CSSParserImpl::ParseFunction(nsCSSKeyword aFunction,
|
|
const uint32_t aAllowedTypes[],
|
|
uint32_t aAllowedTypesAll,
|
|
uint16_t aMinElems, uint16_t aMaxElems,
|
|
nsCSSValue &aValue)
|
|
{
|
|
NS_ASSERTION((aAllowedTypes && !aAllowedTypesAll) ||
|
|
(!aAllowedTypes && aAllowedTypesAll),
|
|
"only one of the two allowed type parameter can be set");
|
|
typedef InfallibleTArray<nsCSSValue>::size_type arrlen_t;
|
|
|
|
/* 2^16 - 2, so that if we have 2^16 - 2 transforms, we have 2^16 - 1
|
|
* elements stored in the the nsCSSValue::Array.
|
|
*/
|
|
static const arrlen_t MAX_ALLOWED_ELEMS = 0xFFFE;
|
|
|
|
/* Read in a list of values as an array, failing if we can't or if
|
|
* it's out of bounds.
|
|
*
|
|
* We reserve 16 entries in the foundValues array in order to avoid
|
|
* having to resize the array dynamically when parsing some well-formed
|
|
* functions. The number 16 is coming from the number of arguments that
|
|
* matrix3d() accepts.
|
|
*/
|
|
AutoTArray<nsCSSValue, 16> foundValues;
|
|
if (!ParseFunctionInternals(aAllowedTypes, aAllowedTypesAll, aMinElems,
|
|
aMaxElems, foundValues)) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* In case the user has given us more than 2^16 - 2 arguments,
|
|
* we'll truncate them at 2^16 - 2 arguments.
|
|
*/
|
|
uint16_t numArgs = std::min(foundValues.Length(), MAX_ALLOWED_ELEMS);
|
|
RefPtr<nsCSSValue::Array> convertedArray =
|
|
aValue.InitFunction(aFunction, numArgs);
|
|
|
|
/* Copy things over. */
|
|
for (uint16_t index = 0; index < numArgs; ++index)
|
|
convertedArray->Item(index + 1) = foundValues[static_cast<arrlen_t>(index)];
|
|
|
|
/* Return it! */
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Given a token, determines the minimum and maximum number of function
|
|
* parameters to read, along with the mask that should be used to read
|
|
* those function parameters. If the token isn't a transform function,
|
|
* returns an error.
|
|
*
|
|
* @param aToken The token identifying the function.
|
|
* @param aIsPrefixed If true, parse matrices using the matrix syntax
|
|
* for -moz-transform.
|
|
* @param aDisallowRelativeValues If true, only allow variants that are
|
|
* numbers or have non-relative dimensions.
|
|
* @param aMinElems [out] The minimum number of elements to read.
|
|
* @param aMaxElems [out] The maximum number of elements to read
|
|
* @param aVariantMask [out] The variant mask to use during parsing
|
|
* @return Whether the information was loaded successfully.
|
|
*/
|
|
static bool GetFunctionParseInformation(nsCSSKeyword aToken,
|
|
bool aIsPrefixed,
|
|
bool aDisallowRelativeValues,
|
|
uint16_t &aMinElems,
|
|
uint16_t &aMaxElems,
|
|
const uint32_t *& aVariantMask)
|
|
{
|
|
/* These types represent the common variant masks that will be used to
|
|
* parse out the individual functions. The order in the enumeration
|
|
* must match the order in which the masks are declared.
|
|
*/
|
|
enum { eLengthPercentCalc,
|
|
eLengthCalc,
|
|
eAbsoluteLengthCalc,
|
|
eTwoLengthPercentCalcs,
|
|
eTwoAbsoluteLengthCalcs,
|
|
eTwoLengthPercentCalcsOneLengthCalc,
|
|
eThreeAbsoluteLengthCalc,
|
|
eAngle,
|
|
eTwoAngles,
|
|
eNumber,
|
|
eNonNegativeLength,
|
|
eNonNegativeAbsoluteLength,
|
|
eTwoNumbers,
|
|
eThreeNumbers,
|
|
eThreeNumbersOneAngle,
|
|
eMatrix,
|
|
eMatrixPrefixed,
|
|
eMatrix3d,
|
|
eMatrix3dPrefixed,
|
|
eNumVariantMasks };
|
|
static const int32_t kMaxElemsPerFunction = 16;
|
|
static const uint32_t kVariantMasks[eNumVariantMasks][kMaxElemsPerFunction] = {
|
|
{VARIANT_LPCALC},
|
|
{VARIANT_LCALC},
|
|
{VARIANT_LB},
|
|
{VARIANT_LPCALC, VARIANT_LPCALC},
|
|
{VARIANT_LBCALC, VARIANT_LBCALC},
|
|
{VARIANT_LPCALC, VARIANT_LPCALC, VARIANT_LCALC},
|
|
{VARIANT_LBCALC, VARIANT_LBCALC, VARIANT_LBCALC},
|
|
{VARIANT_ANGLE_OR_ZERO},
|
|
{VARIANT_ANGLE_OR_ZERO, VARIANT_ANGLE_OR_ZERO},
|
|
{VARIANT_NUMBER},
|
|
{VARIANT_LENGTH|VARIANT_NONNEGATIVE_DIMENSION},
|
|
{VARIANT_LB|VARIANT_NONNEGATIVE_DIMENSION},
|
|
{VARIANT_NUMBER, VARIANT_NUMBER},
|
|
{VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER},
|
|
{VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_ANGLE_OR_ZERO},
|
|
{VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
|
|
VARIANT_NUMBER, VARIANT_NUMBER},
|
|
{VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
|
|
VARIANT_LPNCALC, VARIANT_LPNCALC},
|
|
{VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
|
|
VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
|
|
VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
|
|
VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER},
|
|
{VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
|
|
VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
|
|
VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
|
|
VARIANT_LPNCALC, VARIANT_LPNCALC, VARIANT_LNCALC, VARIANT_NUMBER}};
|
|
// Map from a mask to a congruent mask that excludes relative variants.
|
|
static const int32_t kNonRelativeVariantMap[eNumVariantMasks] = {
|
|
eAbsoluteLengthCalc,
|
|
eAbsoluteLengthCalc,
|
|
eAbsoluteLengthCalc,
|
|
eTwoAbsoluteLengthCalcs,
|
|
eTwoAbsoluteLengthCalcs,
|
|
eThreeAbsoluteLengthCalc,
|
|
eThreeAbsoluteLengthCalc,
|
|
eAngle,
|
|
eTwoAngles,
|
|
eNumber,
|
|
eNonNegativeAbsoluteLength,
|
|
eNonNegativeAbsoluteLength,
|
|
eTwoNumbers,
|
|
eThreeNumbers,
|
|
eThreeNumbersOneAngle,
|
|
eMatrix,
|
|
eMatrix,
|
|
eMatrix3d,
|
|
eMatrix3d };
|
|
|
|
#ifdef DEBUG
|
|
static const uint8_t kVariantMaskLengths[eNumVariantMasks] =
|
|
{1, 1, 1, 2, 2, 3, 3, 1, 2, 1, 1, 1, 2, 3, 4, 6, 6, 16, 16};
|
|
#endif
|
|
|
|
int32_t variantIndex = eNumVariantMasks;
|
|
|
|
switch (aToken) {
|
|
case eCSSKeyword_translatex:
|
|
case eCSSKeyword_translatey:
|
|
/* Exactly one length or percent. */
|
|
variantIndex = eLengthPercentCalc;
|
|
aMinElems = 1U;
|
|
aMaxElems = 1U;
|
|
break;
|
|
case eCSSKeyword_translatez:
|
|
/* Exactly one length */
|
|
variantIndex = eLengthCalc;
|
|
aMinElems = 1U;
|
|
aMaxElems = 1U;
|
|
break;
|
|
case eCSSKeyword_translate3d:
|
|
/* Exactly two lengthds or percents and a number */
|
|
variantIndex = eTwoLengthPercentCalcsOneLengthCalc;
|
|
aMinElems = 3U;
|
|
aMaxElems = 3U;
|
|
break;
|
|
case eCSSKeyword_scalez:
|
|
case eCSSKeyword_scalex:
|
|
case eCSSKeyword_scaley:
|
|
/* Exactly one scale factor. */
|
|
variantIndex = eNumber;
|
|
aMinElems = 1U;
|
|
aMaxElems = 1U;
|
|
break;
|
|
case eCSSKeyword_scale3d:
|
|
/* Exactly three scale factors. */
|
|
variantIndex = eThreeNumbers;
|
|
aMinElems = 3U;
|
|
aMaxElems = 3U;
|
|
break;
|
|
case eCSSKeyword_rotatex:
|
|
case eCSSKeyword_rotatey:
|
|
case eCSSKeyword_rotate:
|
|
case eCSSKeyword_rotatez:
|
|
/* Exactly one angle. */
|
|
variantIndex = eAngle;
|
|
aMinElems = 1U;
|
|
aMaxElems = 1U;
|
|
break;
|
|
case eCSSKeyword_rotate3d:
|
|
variantIndex = eThreeNumbersOneAngle;
|
|
aMinElems = 4U;
|
|
aMaxElems = 4U;
|
|
break;
|
|
case eCSSKeyword_translate:
|
|
/* One or two lengths or percents. */
|
|
variantIndex = eTwoLengthPercentCalcs;
|
|
aMinElems = 1U;
|
|
aMaxElems = 2U;
|
|
break;
|
|
case eCSSKeyword_skew:
|
|
/* Exactly one or two angles. */
|
|
variantIndex = eTwoAngles;
|
|
aMinElems = 1U;
|
|
aMaxElems = 2U;
|
|
break;
|
|
case eCSSKeyword_scale:
|
|
/* One or two scale factors. */
|
|
variantIndex = eTwoNumbers;
|
|
aMinElems = 1U;
|
|
aMaxElems = 2U;
|
|
break;
|
|
case eCSSKeyword_skewx:
|
|
/* Exactly one angle. */
|
|
variantIndex = eAngle;
|
|
aMinElems = 1U;
|
|
aMaxElems = 1U;
|
|
break;
|
|
case eCSSKeyword_skewy:
|
|
/* Exactly one angle. */
|
|
variantIndex = eAngle;
|
|
aMinElems = 1U;
|
|
aMaxElems = 1U;
|
|
break;
|
|
case eCSSKeyword_matrix:
|
|
/* Six values, all numbers. */
|
|
variantIndex = aIsPrefixed ? eMatrixPrefixed : eMatrix;
|
|
aMinElems = 6U;
|
|
aMaxElems = 6U;
|
|
break;
|
|
case eCSSKeyword_matrix3d:
|
|
/* 16 matrix values, all numbers */
|
|
variantIndex = aIsPrefixed ? eMatrix3dPrefixed : eMatrix3d;
|
|
aMinElems = 16U;
|
|
aMaxElems = 16U;
|
|
break;
|
|
case eCSSKeyword_perspective:
|
|
/* Exactly one scale number. */
|
|
variantIndex = eNonNegativeLength;
|
|
aMinElems = 1U;
|
|
aMaxElems = 1U;
|
|
break;
|
|
default:
|
|
/* Oh dear, we didn't match. Report an error. */
|
|
return false;
|
|
}
|
|
|
|
if (aDisallowRelativeValues) {
|
|
variantIndex = kNonRelativeVariantMap[variantIndex];
|
|
}
|
|
|
|
NS_ASSERTION(aMinElems > 0, "Didn't update minimum elements!");
|
|
NS_ASSERTION(aMaxElems > 0, "Didn't update maximum elements!");
|
|
NS_ASSERTION(aMinElems <= aMaxElems, "aMinElems > aMaxElems!");
|
|
NS_ASSERTION(variantIndex >= 0, "Invalid variant mask!");
|
|
NS_ASSERTION(variantIndex < eNumVariantMasks, "Invalid variant mask!");
|
|
#ifdef DEBUG
|
|
NS_ASSERTION(aMaxElems <= kVariantMaskLengths[variantIndex],
|
|
"Invalid aMaxElems for this variant mask.");
|
|
#endif
|
|
|
|
// Convert the index into a mask.
|
|
aVariantMask = kVariantMasks[variantIndex];
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CSSParserImpl::ParseWillChange()
|
|
{
|
|
nsCSSValue listValue;
|
|
nsCSSValueList* currentListValue = listValue.SetListValue();
|
|
bool first = true;
|
|
for (;;) {
|
|
const uint32_t variantMask = VARIANT_IDENTIFIER |
|
|
VARIANT_INHERIT |
|
|
VARIANT_NONE |
|
|
VARIANT_ALL |
|
|
VARIANT_AUTO;
|
|
nsCSSValue value;
|
|
if (!ParseSingleTokenVariant(value, variantMask, nullptr)) {
|
|
return false;
|
|
}
|
|
|
|
if (value.GetUnit() == eCSSUnit_None ||
|
|
value.GetUnit() == eCSSUnit_All)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (value.GetUnit() != eCSSUnit_Ident) {
|
|
if (first) {
|
|
AppendValue(eCSSProperty_will_change, value);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
nsString str;
|
|
value.GetStringValue(str);
|
|
if (str.LowerCaseEqualsLiteral("default") ||
|
|
str.LowerCaseEqualsLiteral("will-change")) {
|
|
return false;
|
|
}
|
|
|
|
currentListValue->mValue = value;
|
|
|
|
if (!ExpectSymbol(',', true)) {
|
|
break;
|
|
}
|
|
currentListValue->mNext = new nsCSSValueList;
|
|
currentListValue = currentListValue->mNext;
|
|
first = false;
|
|
}
|
|
|
|
AppendValue(eCSSProperty_will_change, listValue);
|
|
return true;
|
|
}
|
|
|
|
/* Reads a single transform function from the tokenizer stream, reporting an
|
|
* error if something goes wrong.
|
|
*/
|
|
bool
|
|
CSSParserImpl::ParseSingleTransform(bool aIsPrefixed,
|
|
bool aDisallowRelativeValues,
|
|
nsCSSValue& aValue)
|
|
{
|
|
if (!GetToken(true))
|
|
return false;
|
|
|
|
if (mToken.mType != eCSSToken_Function) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
const uint32_t* variantMask;
|
|
uint16_t minElems, maxElems;
|
|
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
|
|
|
|
if (!GetFunctionParseInformation(keyword, aIsPrefixed,
|
|
aDisallowRelativeValues,
|
|
minElems, maxElems,
|
|
variantMask))
|
|
return false;
|
|
|
|
return ParseFunction(keyword, variantMask, 0, minElems, maxElems, aValue);
|
|
}
|
|
|
|
/* Parses a transform property list by continuously reading in properties
|
|
* and constructing a matrix from it.
|
|
*/
|
|
bool
|
|
CSSParserImpl::ParseTransform(bool aIsPrefixed, bool aDisallowRelativeValues)
|
|
{
|
|
nsCSSValue value;
|
|
// 'inherit', 'initial', 'unset' and 'none' must be alone
|
|
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
|
|
nullptr)) {
|
|
nsCSSValueSharedList* list = new nsCSSValueSharedList;
|
|
value.SetSharedListValue(list);
|
|
list->mHead = new nsCSSValueList;
|
|
nsCSSValueList* cur = list->mHead;
|
|
for (;;) {
|
|
if (!ParseSingleTransform(aIsPrefixed, aDisallowRelativeValues,
|
|
cur->mValue)) {
|
|
return false;
|
|
}
|
|
if (CheckEndProperty()) {
|
|
break;
|
|
}
|
|
cur->mNext = new nsCSSValueList;
|
|
cur = cur->mNext;
|
|
}
|
|
}
|
|
AppendValue(eCSSProperty_transform, value);
|
|
return true;
|
|
}
|
|
|
|
/* Reads a polygon function's argument list.
|
|
*/
|
|
bool
|
|
CSSParserImpl::ParsePolygonFunction(nsCSSValue& aValue)
|
|
{
|
|
uint16_t numArgs = 1;
|
|
|
|
nsCSSValue fillRuleValue;
|
|
if (ParseEnum(fillRuleValue, nsCSSProps::kFillRuleKTable)) {
|
|
numArgs++;
|
|
|
|
// The fill-rule must be comma separated from the polygon points.
|
|
if (!ExpectSymbol(',', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedComma);
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
nsCSSValue coordinates;
|
|
nsCSSValuePairList* item = coordinates.SetPairListValue();
|
|
for (;;) {
|
|
nsCSSValue xValue, yValue;
|
|
if (ParseVariant(xValue, VARIANT_LPCALC, nullptr) != CSSParseResult::Ok ||
|
|
ParseVariant(yValue, VARIANT_LPCALC, nullptr) != CSSParseResult::Ok) {
|
|
REPORT_UNEXPECTED_TOKEN(PECoordinatePair);
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
item->mXValue = xValue;
|
|
item->mYValue = yValue;
|
|
|
|
// See whether to continue or whether to look for end of function.
|
|
if (!ExpectSymbol(',', true)) {
|
|
// We need to read the closing parenthesis.
|
|
if (!ExpectSymbol(')', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
item->mNext = new nsCSSValuePairList;
|
|
item = item->mNext;
|
|
}
|
|
|
|
RefPtr<nsCSSValue::Array> functionArray =
|
|
aValue.InitFunction(eCSSKeyword_polygon, numArgs);
|
|
functionArray->Item(numArgs) = coordinates;
|
|
if (numArgs > 1) {
|
|
functionArray->Item(1) = fillRuleValue;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseCircleOrEllipseFunction(nsCSSKeyword aKeyword,
|
|
nsCSSValue& aValue)
|
|
{
|
|
nsCSSValue radiusX, radiusY, position;
|
|
bool hasRadius = false, hasPosition = false;
|
|
|
|
int32_t mask = VARIANT_LPCALC | VARIANT_NONNEGATIVE_DIMENSION |
|
|
VARIANT_KEYWORD;
|
|
CSSParseResult result =
|
|
ParseVariant(radiusX, mask, nsCSSProps::kShapeRadiusKTable);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
} else if (result == CSSParseResult::Ok) {
|
|
if (aKeyword == eCSSKeyword_ellipse) {
|
|
if (ParseVariant(radiusY, mask, nsCSSProps::kShapeRadiusKTable) !=
|
|
CSSParseResult::Ok) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedRadius);
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
}
|
|
hasRadius = true;
|
|
}
|
|
|
|
if (!ExpectSymbol(')', true)) {
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEPositionEOF);
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType != eCSSToken_Ident ||
|
|
!mToken.mIdent.LowerCaseEqualsLiteral("at") ||
|
|
!ParsePositionValueForBasicShape(position)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
if (!ExpectSymbol(')', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
hasPosition = true;
|
|
}
|
|
|
|
size_t count = aKeyword == eCSSKeyword_circle ? 2 : 3;
|
|
RefPtr<nsCSSValue::Array> functionArray =
|
|
aValue.InitFunction(aKeyword, count);
|
|
if (hasRadius) {
|
|
functionArray->Item(1) = radiusX;
|
|
if (aKeyword == eCSSKeyword_ellipse) {
|
|
functionArray->Item(2) = radiusY;
|
|
}
|
|
}
|
|
if (hasPosition) {
|
|
functionArray->Item(count) = position;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseInsetFunction(nsCSSValue& aValue)
|
|
{
|
|
RefPtr<nsCSSValue::Array> functionArray =
|
|
aValue.InitFunction(eCSSKeyword_inset, 5);
|
|
|
|
int count = 0;
|
|
while (count < 4) {
|
|
CSSParseResult result =
|
|
ParseVariant(functionArray->Item(count + 1), VARIANT_LPCALC, nullptr);
|
|
if (result == CSSParseResult::Error) {
|
|
count = 0;
|
|
break;
|
|
} else if (result == CSSParseResult::NotFound) {
|
|
break;
|
|
}
|
|
++count;
|
|
}
|
|
|
|
if (count == 0) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedShapeArg);
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
if (!ExpectSymbol(')', true)) {
|
|
if (!GetToken(true)) {
|
|
NS_NOTREACHED("ExpectSymbol should have returned true");
|
|
return false;
|
|
}
|
|
|
|
RefPtr<nsCSSValue::Array> radiusArray = nsCSSValue::Array::Create(4);
|
|
functionArray->Item(5).SetArrayValue(radiusArray, eCSSUnit_Array);
|
|
if (mToken.mType != eCSSToken_Ident ||
|
|
!mToken.mIdent.LowerCaseEqualsLiteral("round") ||
|
|
!ParseBoxCornerRadiiInternals(radiusArray->ItemStorage())) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedRadius);
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
if (!ExpectSymbol(')', true)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens)
|
|
{
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType != eCSSToken_Function) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
// Specific shape function parsing always consumes tokens.
|
|
*aConsumedTokens = true;
|
|
nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
|
|
switch (keyword) {
|
|
case eCSSKeyword_polygon:
|
|
return ParsePolygonFunction(aValue);
|
|
case eCSSKeyword_circle:
|
|
case eCSSKeyword_ellipse:
|
|
return ParseCircleOrEllipseFunction(keyword, aValue);
|
|
case eCSSKeyword_inset:
|
|
return ParseInsetFunction(aValue);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseReferenceBoxAndBasicShape(
|
|
nsCSSValue& aValue,
|
|
const KTableEntry aBoxKeywordTable[])
|
|
{
|
|
nsCSSValue referenceBox;
|
|
bool hasBox = ParseEnum(referenceBox, aBoxKeywordTable);
|
|
|
|
const bool boxCameFirst = hasBox;
|
|
|
|
nsCSSValue basicShape;
|
|
bool basicShapeConsumedTokens = false;
|
|
bool hasShape = ParseBasicShape(basicShape, &basicShapeConsumedTokens);
|
|
|
|
// Parsing wasn't successful if ParseBasicShape consumed tokens but failed
|
|
// or if the token was neither a reference box nor a basic shape.
|
|
if ((!hasShape && basicShapeConsumedTokens) || (!hasBox && !hasShape)) {
|
|
return false;
|
|
}
|
|
|
|
// Check if the second argument is a reference box if the first wasn't.
|
|
if (!hasBox) {
|
|
hasBox = ParseEnum(referenceBox, aBoxKeywordTable);
|
|
}
|
|
|
|
RefPtr<nsCSSValue::Array> fullValue =
|
|
nsCSSValue::Array::Create((hasBox && hasShape) ? 2 : 1);
|
|
|
|
if (hasBox && hasShape) {
|
|
fullValue->Item(boxCameFirst ? 0 : 1) = referenceBox;
|
|
fullValue->Item(boxCameFirst ? 1 : 0) = basicShape;
|
|
} else if (hasBox) {
|
|
fullValue->Item(0) = referenceBox;
|
|
} else {
|
|
MOZ_ASSERT(hasShape, "should've bailed if we got neither box nor shape");
|
|
fullValue->Item(0) = basicShape;
|
|
}
|
|
|
|
aValue.SetArrayValue(fullValue, eCSSUnit_Array);
|
|
return true;
|
|
}
|
|
|
|
// Parse a clip-path url to a <clipPath> element or a basic shape.
|
|
bool
|
|
CSSParserImpl::ParseClipPath(nsCSSValue& aValue)
|
|
{
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_HUO, nullptr)) {
|
|
return true;
|
|
}
|
|
|
|
if (!nsLayoutUtils::CSSClipPathShapesEnabled()) {
|
|
// With CSS Clip Path Shapes disabled, we should only accept
|
|
// SVG clipPath reference and none.
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURL);
|
|
return false;
|
|
}
|
|
|
|
return ParseReferenceBoxAndBasicShape(
|
|
aValue, nsCSSProps::kClipPathGeometryBoxKTable);
|
|
}
|
|
|
|
// none | [ <basic-shape> || <shape-box> ] | <image>
|
|
bool
|
|
CSSParserImpl::ParseShapeOutside(nsCSSValue& aValue)
|
|
{
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_HUO, nullptr)) {
|
|
// 'inherit', 'initial', 'unset', 'none', and <image> url must be alone.
|
|
return true;
|
|
}
|
|
|
|
return ParseReferenceBoxAndBasicShape(
|
|
aValue, nsCSSProps::kShapeOutsideShapeBoxKTable);
|
|
}
|
|
|
|
bool CSSParserImpl::ParseTransformOrigin(bool aPerspective)
|
|
{
|
|
nsCSSValuePair position;
|
|
if (!ParseBoxPositionValues(position, true))
|
|
return false;
|
|
|
|
nsCSSPropertyID prop = eCSSProperty_transform_origin;
|
|
if (aPerspective) {
|
|
prop = eCSSProperty_perspective_origin;
|
|
}
|
|
|
|
// Unlike many other uses of pairs, this position should always be stored
|
|
// as a pair, even if the values are the same, so it always serializes as
|
|
// a pair, and to keep the computation code simple.
|
|
if (position.mXValue.GetUnit() == eCSSUnit_Inherit ||
|
|
position.mXValue.GetUnit() == eCSSUnit_Initial ||
|
|
position.mXValue.GetUnit() == eCSSUnit_Unset) {
|
|
MOZ_ASSERT(position.mXValue == position.mYValue,
|
|
"inherit/initial/unset only half?");
|
|
AppendValue(prop, position.mXValue);
|
|
} else {
|
|
nsCSSValue value;
|
|
if (aPerspective) {
|
|
value.SetPairValue(position.mXValue, position.mYValue);
|
|
} else {
|
|
nsCSSValue depth;
|
|
CSSParseResult result =
|
|
ParseVariant(depth, VARIANT_LENGTH | VARIANT_CALC, nullptr);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
} else if (result == CSSParseResult::NotFound) {
|
|
depth.SetFloatValue(0.0f, eCSSUnit_Pixel);
|
|
}
|
|
value.SetTripletValue(position.mXValue, position.mYValue, depth);
|
|
}
|
|
|
|
AppendValue(prop, value);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Reads a drop-shadow value. At the moment the Filter Effects specification
|
|
* just expects one shadow item. Should this ever change to a list of shadow
|
|
* items, use ParseShadowList instead.
|
|
*/
|
|
bool
|
|
CSSParserImpl::ParseDropShadow(nsCSSValue* aValue)
|
|
{
|
|
// Use nsCSSValueList to reuse the shadow resolving code in
|
|
// nsRuleNode and nsComputedDOMStyle.
|
|
nsCSSValue shadow;
|
|
nsCSSValueList* cur = shadow.SetListValue();
|
|
if (!ParseShadowItem(cur->mValue, false))
|
|
return false;
|
|
|
|
if (!ExpectSymbol(')', true))
|
|
return false;
|
|
|
|
nsCSSValue::Array* dropShadow = aValue->InitFunction(eCSSKeyword_drop_shadow, 1);
|
|
|
|
// Copy things over.
|
|
dropShadow->Item(1) = shadow;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Reads a single url or filter function from the tokenizer stream, reporting an
|
|
* error if something goes wrong.
|
|
*/
|
|
bool
|
|
CSSParserImpl::ParseSingleFilter(nsCSSValue* aValue)
|
|
{
|
|
if (ParseSingleTokenVariant(*aValue, VARIANT_URL, nullptr)) {
|
|
return true;
|
|
}
|
|
|
|
if (!nsLayoutUtils::CSSFiltersEnabled()) {
|
|
// With CSS Filters disabled, we should only accept an SVG reference filter.
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURL);
|
|
return false;
|
|
}
|
|
|
|
if (!GetToken(true)) {
|
|
REPORT_UNEXPECTED_EOF(PEFilterEOF);
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType != eCSSToken_Function) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction);
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
nsCSSKeyword functionName = nsCSSKeywords::LookupKeyword(mToken.mIdent);
|
|
// Parse drop-shadow independently of the other filter functions
|
|
// because of its more complex characteristics.
|
|
if (functionName == eCSSKeyword_drop_shadow) {
|
|
if (ParseDropShadow(aValue)) {
|
|
return true;
|
|
} else {
|
|
// Unrecognized filter function.
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction);
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Set up the parsing rules based on the filter function.
|
|
uint32_t variantMask = VARIANT_PN;
|
|
bool rejectNegativeArgument = true;
|
|
bool clampArgumentToOne = false;
|
|
switch (functionName) {
|
|
case eCSSKeyword_blur:
|
|
variantMask = VARIANT_LCALC | VARIANT_NONNEGATIVE_DIMENSION;
|
|
// VARIANT_NONNEGATIVE_DIMENSION will already reject negative lengths.
|
|
rejectNegativeArgument = false;
|
|
break;
|
|
case eCSSKeyword_brightness:
|
|
case eCSSKeyword_contrast:
|
|
case eCSSKeyword_saturate:
|
|
break;
|
|
case eCSSKeyword_grayscale:
|
|
case eCSSKeyword_invert:
|
|
case eCSSKeyword_sepia:
|
|
case eCSSKeyword_opacity:
|
|
clampArgumentToOne = true;
|
|
break;
|
|
case eCSSKeyword_hue_rotate:
|
|
variantMask = VARIANT_ANGLE;
|
|
rejectNegativeArgument = false;
|
|
break;
|
|
default:
|
|
// Unrecognized filter function.
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction);
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
|
|
// Parse the function.
|
|
uint16_t minElems = 1U;
|
|
uint16_t maxElems = 1U;
|
|
uint32_t allVariants = 0;
|
|
if (!ParseFunction(functionName, &variantMask, allVariants,
|
|
minElems, maxElems, *aValue)) {
|
|
REPORT_UNEXPECTED(PEFilterFunctionArgumentsParsingError);
|
|
return false;
|
|
}
|
|
|
|
// Get the first and only argument to the filter function.
|
|
MOZ_ASSERT(aValue->GetUnit() == eCSSUnit_Function,
|
|
"expected a filter function");
|
|
MOZ_ASSERT(aValue->UnitHasArrayValue(),
|
|
"filter function should be an array");
|
|
MOZ_ASSERT(aValue->GetArrayValue()->Count() == 2,
|
|
"filter function should have exactly one argument");
|
|
nsCSSValue& arg = aValue->GetArrayValue()->Item(1);
|
|
|
|
if (rejectNegativeArgument &&
|
|
((arg.GetUnit() == eCSSUnit_Percent && arg.GetPercentValue() < 0.0f) ||
|
|
(arg.GetUnit() == eCSSUnit_Number && arg.GetFloatValue() < 0.0f))) {
|
|
REPORT_UNEXPECTED(PEExpectedNonnegativeNP);
|
|
return false;
|
|
}
|
|
|
|
if (clampArgumentToOne) {
|
|
if (arg.GetUnit() == eCSSUnit_Number &&
|
|
arg.GetFloatValue() > 1.0f) {
|
|
arg.SetFloatValue(1.0f, arg.GetUnit());
|
|
} else if (arg.GetUnit() == eCSSUnit_Percent &&
|
|
arg.GetPercentValue() > 1.0f) {
|
|
arg.SetPercentValue(1.0f);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Parses a filter property value by continuously reading in urls and/or filter
|
|
* functions and constructing a list.
|
|
*
|
|
* When CSS Filters are enabled, the filter property accepts one or more SVG
|
|
* reference filters and/or CSS filter functions.
|
|
* e.g. filter: url(#my-filter-1) blur(3px) url(#my-filter-2) grayscale(50%);
|
|
*
|
|
* When CSS Filters are disabled, the filter property only accepts one SVG
|
|
* reference filter.
|
|
* e.g. filter: url(#my-filter);
|
|
*/
|
|
bool
|
|
CSSParserImpl::ParseFilter()
|
|
{
|
|
nsCSSValue value;
|
|
// 'inherit', 'initial', 'unset' and 'none' must be alone
|
|
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
|
|
nullptr)) {
|
|
nsCSSValueList* cur = value.SetListValue();
|
|
while (cur) {
|
|
if (!ParseSingleFilter(&cur->mValue)) {
|
|
return false;
|
|
}
|
|
if (CheckEndProperty()) {
|
|
break;
|
|
}
|
|
if (!nsLayoutUtils::CSSFiltersEnabled()) {
|
|
// With CSS Filters disabled, we should only accept one SVG reference
|
|
// filter.
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
|
|
return false;
|
|
}
|
|
cur->mNext = new nsCSSValueList;
|
|
cur = cur->mNext;
|
|
}
|
|
}
|
|
AppendValue(eCSSProperty_filter, value);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseTransitionProperty()
|
|
{
|
|
nsCSSValue value;
|
|
// 'inherit', 'initial', 'unset' and 'none' must be alone
|
|
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
|
|
nullptr)) {
|
|
// Accept a list of arbitrary identifiers. They should be
|
|
// CSS properties, but we want to accept any so that we
|
|
// accept properties that we don't know about yet, e.g.
|
|
// transition-property: invalid-property, left, opacity;
|
|
nsCSSValueList* cur = value.SetListValue();
|
|
for (;;) {
|
|
if (!ParseSingleTokenVariant(cur->mValue,
|
|
VARIANT_IDENTIFIER | VARIANT_ALL,
|
|
nullptr)) {
|
|
return false;
|
|
}
|
|
if (cur->mValue.GetUnit() == eCSSUnit_Ident) {
|
|
nsDependentString str(cur->mValue.GetStringBufferValue());
|
|
// Exclude 'none', 'inherit', 'initial' and 'unset' according to the
|
|
// same rules as for 'counter-reset' in CSS 2.1.
|
|
if (str.LowerCaseEqualsLiteral("none") ||
|
|
str.LowerCaseEqualsLiteral("inherit") ||
|
|
str.LowerCaseEqualsLiteral("initial") ||
|
|
(str.LowerCaseEqualsLiteral("unset") &&
|
|
nsLayoutUtils::UnsetValueEnabled())) {
|
|
return false;
|
|
}
|
|
}
|
|
if (!ExpectSymbol(',', true)) {
|
|
break;
|
|
}
|
|
cur->mNext = new nsCSSValueList;
|
|
cur = cur->mNext;
|
|
}
|
|
}
|
|
AppendValue(eCSSProperty_transition_property, value);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseTransitionTimingFunctionValues(nsCSSValue& aValue)
|
|
{
|
|
NS_ASSERTION(!mHavePushBack &&
|
|
mToken.mType == eCSSToken_Function &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("cubic-bezier"),
|
|
"unexpected initial state");
|
|
|
|
RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(4);
|
|
|
|
float x1, x2, y1, y2;
|
|
if (!ParseTransitionTimingFunctionValueComponent(x1, ',', true) ||
|
|
!ParseTransitionTimingFunctionValueComponent(y1, ',', false) ||
|
|
!ParseTransitionTimingFunctionValueComponent(x2, ',', true) ||
|
|
!ParseTransitionTimingFunctionValueComponent(y2, ')', false)) {
|
|
return false;
|
|
}
|
|
|
|
val->Item(0).SetFloatValue(x1, eCSSUnit_Number);
|
|
val->Item(1).SetFloatValue(y1, eCSSUnit_Number);
|
|
val->Item(2).SetFloatValue(x2, eCSSUnit_Number);
|
|
val->Item(3).SetFloatValue(y2, eCSSUnit_Number);
|
|
|
|
aValue.SetArrayValue(val, eCSSUnit_Cubic_Bezier);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseTransitionTimingFunctionValueComponent(float& aComponent,
|
|
char aStop,
|
|
bool aIsXPoint)
|
|
{
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
nsCSSToken* tk = &mToken;
|
|
if (tk->mType == eCSSToken_Number) {
|
|
float num = tk->mNumber;
|
|
|
|
// Clamp infinity or -infinity values to max float or -max float to avoid
|
|
// calculations with infinity.
|
|
num = mozilla::clamped(num, -std::numeric_limits<float>::max(),
|
|
std::numeric_limits<float>::max());
|
|
|
|
// X control point should be inside [0, 1] range.
|
|
if (aIsXPoint && (num < 0.0 || num > 1.0)) {
|
|
return false;
|
|
}
|
|
aComponent = num;
|
|
if (ExpectSymbol(aStop, true)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseTransitionStepTimingFunctionValues(nsCSSValue& aValue)
|
|
{
|
|
NS_ASSERTION(!mHavePushBack &&
|
|
mToken.mType == eCSSToken_Function &&
|
|
mToken.mIdent.LowerCaseEqualsLiteral("steps"),
|
|
"unexpected initial state");
|
|
|
|
RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(2);
|
|
|
|
if (!ParseSingleTokenOneOrLargerVariant(val->Item(0), VARIANT_INTEGER,
|
|
nullptr)) {
|
|
return false;
|
|
}
|
|
|
|
int32_t type = -1; // indicates an implicit end value
|
|
if (ExpectSymbol(',', true)) {
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
if (mToken.mType == eCSSToken_Ident) {
|
|
if (mToken.mIdent.LowerCaseEqualsLiteral("start")) {
|
|
type = NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START;
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("end")) {
|
|
type = NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_END;
|
|
}
|
|
}
|
|
if (type == -1) {
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
}
|
|
val->Item(1).SetIntValue(type, eCSSUnit_Enumerated);
|
|
|
|
if (!ExpectSymbol(')', true)) {
|
|
return false;
|
|
}
|
|
|
|
aValue.SetArrayValue(val, eCSSUnit_Steps);
|
|
return true;
|
|
}
|
|
|
|
static nsCSSValueList*
|
|
AppendValueToList(nsCSSValue& aContainer,
|
|
nsCSSValueList* aTail,
|
|
const nsCSSValue& aValue)
|
|
{
|
|
nsCSSValueList* entry;
|
|
if (aContainer.GetUnit() == eCSSUnit_Null) {
|
|
MOZ_ASSERT(!aTail, "should not have an entry");
|
|
entry = aContainer.SetListValue();
|
|
} else {
|
|
MOZ_ASSERT(!aTail->mNext, "should not have a next entry");
|
|
MOZ_ASSERT(aContainer.GetUnit() == eCSSUnit_List, "not a list");
|
|
entry = new nsCSSValueList;
|
|
aTail->mNext = entry;
|
|
}
|
|
entry->mValue = aValue;
|
|
return entry;
|
|
}
|
|
|
|
CSSParserImpl::ParseAnimationOrTransitionShorthandResult
|
|
CSSParserImpl::ParseAnimationOrTransitionShorthand(
|
|
const nsCSSPropertyID* aProperties,
|
|
const nsCSSValue* aInitialValues,
|
|
nsCSSValue* aValues,
|
|
size_t aNumProperties)
|
|
{
|
|
nsCSSValue tempValue;
|
|
// first see if 'inherit', 'initial' or 'unset' is specified. If one is,
|
|
// it can be the only thing specified, so don't attempt to parse any
|
|
// additional properties
|
|
if (ParseSingleTokenVariant(tempValue, VARIANT_INHERIT, nullptr)) {
|
|
for (uint32_t i = 0; i < aNumProperties; ++i) {
|
|
AppendValue(aProperties[i], tempValue);
|
|
}
|
|
return eParseAnimationOrTransitionShorthand_Inherit;
|
|
}
|
|
|
|
static const size_t maxNumProperties = 8;
|
|
MOZ_ASSERT(aNumProperties <= maxNumProperties,
|
|
"can't handle this many properties");
|
|
nsCSSValueList *cur[maxNumProperties];
|
|
bool parsedProperty[maxNumProperties];
|
|
|
|
for (size_t i = 0; i < aNumProperties; ++i) {
|
|
cur[i] = nullptr;
|
|
}
|
|
bool atEOP = false; // at end of property?
|
|
for (;;) { // loop over comma-separated transitions or animations
|
|
// whether a particular subproperty was specified for this
|
|
// transition or animation
|
|
bool haveAnyProperty = false;
|
|
for (size_t i = 0; i < aNumProperties; ++i) {
|
|
parsedProperty[i] = false;
|
|
}
|
|
for (;;) { // loop over values within a transition or animation
|
|
bool foundProperty = false;
|
|
// check to see if we're at the end of one full transition or
|
|
// animation definition (either because we hit a comma or because
|
|
// we hit the end of the property definition)
|
|
if (ExpectSymbol(',', true))
|
|
break;
|
|
if (CheckEndProperty()) {
|
|
atEOP = true;
|
|
break;
|
|
}
|
|
|
|
// else, try to parse the next transition or animation sub-property
|
|
for (uint32_t i = 0; !foundProperty && i < aNumProperties; ++i) {
|
|
if (!parsedProperty[i]) {
|
|
// if we haven't found this property yet, try to parse it
|
|
CSSParseResult result =
|
|
ParseSingleValueProperty(tempValue, aProperties[i]);
|
|
if (result == CSSParseResult::Error) {
|
|
return eParseAnimationOrTransitionShorthand_Error;
|
|
}
|
|
if (result == CSSParseResult::Ok) {
|
|
parsedProperty[i] = true;
|
|
cur[i] = AppendValueToList(aValues[i], cur[i], tempValue);
|
|
foundProperty = true;
|
|
haveAnyProperty = true;
|
|
break; // out of inner loop; continue looking for next sub-property
|
|
}
|
|
}
|
|
}
|
|
if (!foundProperty) {
|
|
// We're not at a ',' or at the end of the property, but we couldn't
|
|
// parse any of the sub-properties, so the declaration is invalid.
|
|
return eParseAnimationOrTransitionShorthand_Error;
|
|
}
|
|
}
|
|
|
|
if (!haveAnyProperty) {
|
|
// Got an empty item.
|
|
return eParseAnimationOrTransitionShorthand_Error;
|
|
}
|
|
|
|
// We hit the end of the property or the end of one transition
|
|
// or animation definition, add its components to the list.
|
|
for (uint32_t i = 0; i < aNumProperties; ++i) {
|
|
// If all of the subproperties were not explicitly specified, fill
|
|
// in the missing ones with initial values.
|
|
if (!parsedProperty[i]) {
|
|
cur[i] = AppendValueToList(aValues[i], cur[i], aInitialValues[i]);
|
|
}
|
|
}
|
|
|
|
if (atEOP)
|
|
break;
|
|
// else we just hit a ',' so continue parsing the next compound transition
|
|
}
|
|
|
|
return eParseAnimationOrTransitionShorthand_Values;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseTransition()
|
|
{
|
|
static const nsCSSPropertyID kTransitionProperties[] = {
|
|
eCSSProperty_transition_duration,
|
|
eCSSProperty_transition_timing_function,
|
|
// Must check 'transition-delay' after 'transition-duration', since
|
|
// that's our assumption about what the spec means for the shorthand
|
|
// syntax (the first time given is the duration, and the second
|
|
// given is the delay).
|
|
eCSSProperty_transition_delay,
|
|
// Must check 'transition-property' after
|
|
// 'transition-timing-function' since 'transition-property' accepts
|
|
// any keyword.
|
|
eCSSProperty_transition_property
|
|
};
|
|
static const uint32_t numProps = MOZ_ARRAY_LENGTH(kTransitionProperties);
|
|
// this is a shorthand property that accepts -property, -delay,
|
|
// -duration, and -timing-function with some components missing.
|
|
// there can be multiple transitions, separated with commas
|
|
|
|
nsCSSValue initialValues[numProps];
|
|
initialValues[0].SetFloatValue(0.0, eCSSUnit_Seconds);
|
|
initialValues[1].SetIntValue(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE,
|
|
eCSSUnit_Enumerated);
|
|
initialValues[2].SetFloatValue(0.0, eCSSUnit_Seconds);
|
|
initialValues[3].SetAllValue();
|
|
|
|
nsCSSValue values[numProps];
|
|
|
|
ParseAnimationOrTransitionShorthandResult spres =
|
|
ParseAnimationOrTransitionShorthand(kTransitionProperties,
|
|
initialValues, values, numProps);
|
|
if (spres != eParseAnimationOrTransitionShorthand_Values) {
|
|
return spres != eParseAnimationOrTransitionShorthand_Error;
|
|
}
|
|
|
|
// Make two checks on the list for 'transition-property':
|
|
// + If there is more than one item, then none of the items can be
|
|
// 'none'.
|
|
// + None of the items can be 'inherit', 'initial' or 'unset'.
|
|
{
|
|
MOZ_ASSERT(kTransitionProperties[3] == eCSSProperty_transition_property,
|
|
"array index mismatch");
|
|
nsCSSValueList *l = values[3].GetListValue();
|
|
bool multipleItems = !!l->mNext;
|
|
do {
|
|
const nsCSSValue& val = l->mValue;
|
|
if (val.GetUnit() == eCSSUnit_None) {
|
|
if (multipleItems) {
|
|
// This is a syntax error.
|
|
return false;
|
|
}
|
|
|
|
// Unbox a solitary 'none'.
|
|
values[3].SetNoneValue();
|
|
break;
|
|
}
|
|
if (val.GetUnit() == eCSSUnit_Ident) {
|
|
nsDependentString str(val.GetStringBufferValue());
|
|
if (str.EqualsLiteral("inherit") ||
|
|
str.EqualsLiteral("initial") ||
|
|
(str.EqualsLiteral("unset") &&
|
|
nsLayoutUtils::UnsetValueEnabled())) {
|
|
return false;
|
|
}
|
|
}
|
|
} while ((l = l->mNext));
|
|
}
|
|
|
|
// Save all parsed transition sub-properties in mTempData
|
|
for (uint32_t i = 0; i < numProps; ++i) {
|
|
AppendValue(kTransitionProperties[i], values[i]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseAnimation()
|
|
{
|
|
static const nsCSSPropertyID kAnimationProperties[] = {
|
|
eCSSProperty_animation_duration,
|
|
eCSSProperty_animation_timing_function,
|
|
// Must check 'animation-delay' after 'animation-duration', since
|
|
// that's our assumption about what the spec means for the shorthand
|
|
// syntax (the first time given is the duration, and the second
|
|
// given is the delay).
|
|
eCSSProperty_animation_delay,
|
|
eCSSProperty_animation_direction,
|
|
eCSSProperty_animation_fill_mode,
|
|
eCSSProperty_animation_iteration_count,
|
|
eCSSProperty_animation_play_state,
|
|
// Must check 'animation-name' after 'animation-timing-function',
|
|
// 'animation-direction', 'animation-fill-mode',
|
|
// 'animation-iteration-count', and 'animation-play-state' since
|
|
// 'animation-name' accepts any keyword.
|
|
eCSSProperty_animation_name
|
|
};
|
|
static const uint32_t numProps = MOZ_ARRAY_LENGTH(kAnimationProperties);
|
|
// this is a shorthand property that accepts -property, -delay,
|
|
// -duration, and -timing-function with some components missing.
|
|
// there can be multiple animations, separated with commas
|
|
|
|
nsCSSValue initialValues[numProps];
|
|
initialValues[0].SetFloatValue(0.0, eCSSUnit_Seconds);
|
|
initialValues[1].SetIntValue(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE,
|
|
eCSSUnit_Enumerated);
|
|
initialValues[2].SetFloatValue(0.0, eCSSUnit_Seconds);
|
|
initialValues[3].SetIntValue(static_cast<uint32_t>(mozilla::dom::PlaybackDirection::Normal),
|
|
eCSSUnit_Enumerated);
|
|
initialValues[4].SetIntValue(static_cast<uint32_t>(mozilla::dom::FillMode::None),
|
|
eCSSUnit_Enumerated);
|
|
initialValues[5].SetFloatValue(1.0f, eCSSUnit_Number);
|
|
initialValues[6].SetIntValue(NS_STYLE_ANIMATION_PLAY_STATE_RUNNING, eCSSUnit_Enumerated);
|
|
initialValues[7].SetNoneValue();
|
|
|
|
nsCSSValue values[numProps];
|
|
|
|
ParseAnimationOrTransitionShorthandResult spres =
|
|
ParseAnimationOrTransitionShorthand(kAnimationProperties,
|
|
initialValues, values, numProps);
|
|
if (spres != eParseAnimationOrTransitionShorthand_Values) {
|
|
return spres != eParseAnimationOrTransitionShorthand_Error;
|
|
}
|
|
|
|
// Save all parsed animation sub-properties in mTempData
|
|
for (uint32_t i = 0; i < numProps; ++i) {
|
|
AppendValue(kAnimationProperties[i], values[i]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseShadowItem(nsCSSValue& aValue, bool aIsBoxShadow)
|
|
{
|
|
// A shadow list item is an array, with entries in this sequence:
|
|
enum {
|
|
IndexX,
|
|
IndexY,
|
|
IndexRadius,
|
|
IndexSpread, // only for box-shadow
|
|
IndexColor,
|
|
IndexInset // only for box-shadow
|
|
};
|
|
|
|
RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(6);
|
|
|
|
if (aIsBoxShadow) {
|
|
// Optional inset keyword (ignore errors)
|
|
ParseSingleTokenVariant(val->Item(IndexInset), VARIANT_KEYWORD,
|
|
nsCSSProps::kBoxShadowTypeKTable);
|
|
}
|
|
|
|
nsCSSValue xOrColor;
|
|
bool haveColor = false;
|
|
if (ParseVariant(xOrColor, VARIANT_COLOR | VARIANT_LENGTH | VARIANT_CALC,
|
|
nullptr) != CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
if (xOrColor.IsLengthUnit() || xOrColor.IsCalcUnit()) {
|
|
val->Item(IndexX) = xOrColor;
|
|
} else {
|
|
// Must be a color (as string or color value)
|
|
NS_ASSERTION(xOrColor.GetUnit() == eCSSUnit_Ident ||
|
|
xOrColor.GetUnit() == eCSSUnit_EnumColor ||
|
|
xOrColor.IsNumericColorUnit(),
|
|
"Must be a color value");
|
|
val->Item(IndexColor) = xOrColor;
|
|
haveColor = true;
|
|
|
|
// X coordinate mandatory after color
|
|
if (ParseVariant(val->Item(IndexX), VARIANT_LENGTH | VARIANT_CALC,
|
|
nullptr) != CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Y coordinate; mandatory
|
|
if (ParseVariant(val->Item(IndexY), VARIANT_LENGTH | VARIANT_CALC,
|
|
nullptr) != CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
|
|
// Optional radius. Ignore errors except if they pass a negative
|
|
// value which we must reject. If we use ParseNonNegativeVariant
|
|
// we can't tell the difference between an unspecified radius
|
|
// and a negative radius.
|
|
CSSParseResult result =
|
|
ParseVariant(val->Item(IndexRadius), VARIANT_LENGTH | VARIANT_CALC,
|
|
nullptr);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
} else if (result == CSSParseResult::Ok) {
|
|
if (val->Item(IndexRadius).IsLengthUnit() &&
|
|
val->Item(IndexRadius).GetFloatValue() < 0) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (aIsBoxShadow) {
|
|
// Optional spread
|
|
if (ParseVariant(val->Item(IndexSpread), VARIANT_LENGTH | VARIANT_CALC,
|
|
nullptr) == CSSParseResult::Error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!haveColor) {
|
|
// Optional color
|
|
if (ParseVariant(val->Item(IndexColor), VARIANT_COLOR, nullptr) ==
|
|
CSSParseResult::Error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (aIsBoxShadow && val->Item(IndexInset).GetUnit() == eCSSUnit_Null) {
|
|
// Optional inset keyword
|
|
ParseSingleTokenVariant(val->Item(IndexInset), VARIANT_KEYWORD,
|
|
nsCSSProps::kBoxShadowTypeKTable);
|
|
}
|
|
|
|
aValue.SetArrayValue(val, eCSSUnit_Array);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseShadowList(nsCSSPropertyID aProperty)
|
|
{
|
|
nsAutoParseCompoundProperty compound(this);
|
|
bool isBoxShadow = aProperty == eCSSProperty_box_shadow;
|
|
|
|
nsCSSValue value;
|
|
// 'inherit', 'initial', 'unset' and 'none' must be alone
|
|
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
|
|
nullptr)) {
|
|
nsCSSValueList* cur = value.SetListValue();
|
|
for (;;) {
|
|
if (!ParseShadowItem(cur->mValue, isBoxShadow)) {
|
|
return false;
|
|
}
|
|
if (!ExpectSymbol(',', true)) {
|
|
break;
|
|
}
|
|
cur->mNext = new nsCSSValueList;
|
|
cur = cur->mNext;
|
|
}
|
|
}
|
|
AppendValue(aProperty, value);
|
|
return true;
|
|
}
|
|
|
|
int32_t
|
|
CSSParserImpl::GetNamespaceIdForPrefix(const nsString& aPrefix)
|
|
{
|
|
NS_PRECONDITION(!aPrefix.IsEmpty(), "Must have a prefix here");
|
|
|
|
int32_t nameSpaceID = kNameSpaceID_Unknown;
|
|
if (mNameSpaceMap) {
|
|
// user-specified identifiers are case-sensitive (bug 416106)
|
|
nsCOMPtr<nsIAtom> prefix = NS_Atomize(aPrefix);
|
|
nameSpaceID = mNameSpaceMap->FindNameSpaceID(prefix);
|
|
}
|
|
// else no declared namespaces
|
|
|
|
if (nameSpaceID == kNameSpaceID_Unknown) { // unknown prefix, dump it
|
|
REPORT_UNEXPECTED_P(PEUnknownNamespacePrefix, aPrefix);
|
|
}
|
|
|
|
return nameSpaceID;
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::SetDefaultNamespaceOnSelector(nsCSSSelector& aSelector)
|
|
{
|
|
if (mNameSpaceMap) {
|
|
aSelector.SetNameSpace(mNameSpaceMap->FindNameSpaceID(nullptr));
|
|
} else {
|
|
aSelector.SetNameSpace(kNameSpaceID_Unknown); // wildcard
|
|
}
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParsePaint(nsCSSPropertyID aPropID)
|
|
{
|
|
nsCSSValue x, y;
|
|
|
|
if (ParseVariant(x, VARIANT_HC | VARIANT_NONE | VARIANT_URL |
|
|
VARIANT_OPENTYPE_SVG_KEYWORD,
|
|
nsCSSProps::kContextPatternKTable) != CSSParseResult::Ok) {
|
|
return false;
|
|
}
|
|
|
|
bool canHaveFallback = x.GetUnit() == eCSSUnit_URL ||
|
|
x.GetUnit() == eCSSUnit_Enumerated;
|
|
if (canHaveFallback) {
|
|
CSSParseResult result =
|
|
ParseVariant(y, VARIANT_COLOR | VARIANT_NONE, nullptr);
|
|
if (result == CSSParseResult::Error) {
|
|
return false;
|
|
} else if (result == CSSParseResult::NotFound) {
|
|
y.SetNoneValue();
|
|
}
|
|
}
|
|
|
|
if (!canHaveFallback) {
|
|
AppendValue(aPropID, x);
|
|
} else {
|
|
nsCSSValue val;
|
|
val.SetPairValue(x, y);
|
|
AppendValue(aPropID, val);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseDasharray()
|
|
{
|
|
nsCSSValue value;
|
|
|
|
// 'inherit', 'initial', 'unset' and 'none' are only allowed on their own
|
|
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE |
|
|
VARIANT_OPENTYPE_SVG_KEYWORD,
|
|
nsCSSProps::kStrokeContextValueKTable)) {
|
|
nsCSSValueList *cur = value.SetListValue();
|
|
for (;;) {
|
|
if (!ParseSingleTokenNonNegativeVariant(cur->mValue, VARIANT_LPN,
|
|
nullptr)) {
|
|
return false;
|
|
}
|
|
if (CheckEndProperty()) {
|
|
break;
|
|
}
|
|
// skip optional commas between elements
|
|
(void)ExpectSymbol(',', true);
|
|
|
|
cur->mNext = new nsCSSValueList;
|
|
cur = cur->mNext;
|
|
}
|
|
}
|
|
AppendValue(eCSSProperty_stroke_dasharray, value);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseMarker()
|
|
{
|
|
nsCSSValue marker;
|
|
if (ParseSingleValueProperty(marker, eCSSProperty_marker_end) ==
|
|
CSSParseResult::Ok) {
|
|
AppendValue(eCSSProperty_marker_end, marker);
|
|
AppendValue(eCSSProperty_marker_mid, marker);
|
|
AppendValue(eCSSProperty_marker_start, marker);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParsePaintOrder()
|
|
{
|
|
static_assert
|
|
((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) > NS_STYLE_PAINT_ORDER_LAST_VALUE,
|
|
"bitfield width insufficient for paint-order constants");
|
|
|
|
static const KTableEntry kPaintOrderKTable[] = {
|
|
{ eCSSKeyword_normal, NS_STYLE_PAINT_ORDER_NORMAL },
|
|
{ eCSSKeyword_fill, NS_STYLE_PAINT_ORDER_FILL },
|
|
{ eCSSKeyword_stroke, NS_STYLE_PAINT_ORDER_STROKE },
|
|
{ eCSSKeyword_markers, NS_STYLE_PAINT_ORDER_MARKERS },
|
|
{ eCSSKeyword_UNKNOWN, -1 }
|
|
};
|
|
|
|
static_assert(MOZ_ARRAY_LENGTH(kPaintOrderKTable) ==
|
|
NS_STYLE_PAINT_ORDER_LAST_VALUE + 2,
|
|
"missing paint-order values in kPaintOrderKTable");
|
|
|
|
nsCSSValue value;
|
|
if (!ParseSingleTokenVariant(value, VARIANT_HK, kPaintOrderKTable)) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t seen = 0;
|
|
uint32_t order = 0;
|
|
uint32_t position = 0;
|
|
|
|
// Ensure that even cast to a signed int32_t when stored in CSSValue,
|
|
// we have enough space for the entire paint-order value.
|
|
static_assert
|
|
(NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE < 32,
|
|
"seen and order not big enough");
|
|
|
|
if (value.GetUnit() == eCSSUnit_Enumerated) {
|
|
uint32_t component = static_cast<uint32_t>(value.GetIntValue());
|
|
if (component != NS_STYLE_PAINT_ORDER_NORMAL) {
|
|
bool parsedOK = true;
|
|
for (;;) {
|
|
if (seen & (1 << component)) {
|
|
// Already seen this component.
|
|
UngetToken();
|
|
parsedOK = false;
|
|
break;
|
|
}
|
|
seen |= (1 << component);
|
|
order |= (component << position);
|
|
position += NS_STYLE_PAINT_ORDER_BITWIDTH;
|
|
if (!ParseEnum(value, kPaintOrderKTable)) {
|
|
break;
|
|
}
|
|
component = value.GetIntValue();
|
|
if (component == NS_STYLE_PAINT_ORDER_NORMAL) {
|
|
// Can't have "normal" in the middle of the list of paint components.
|
|
UngetToken();
|
|
parsedOK = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Fill in the remaining paint-order components in the order of their
|
|
// constant values.
|
|
if (parsedOK) {
|
|
for (component = 1;
|
|
component <= NS_STYLE_PAINT_ORDER_LAST_VALUE;
|
|
component++) {
|
|
if (!(seen & (1 << component))) {
|
|
order |= (component << position);
|
|
position += NS_STYLE_PAINT_ORDER_BITWIDTH;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static_assert(NS_STYLE_PAINT_ORDER_NORMAL == 0,
|
|
"unexpected value for NS_STYLE_PAINT_ORDER_NORMAL");
|
|
value.SetIntValue(static_cast<int32_t>(order), eCSSUnit_Enumerated);
|
|
}
|
|
|
|
AppendValue(eCSSProperty_paint_order, value);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::BackslashDropped()
|
|
{
|
|
return mScanner->GetEOFCharacters() &
|
|
nsCSSScanner::eEOFCharacters_DropBackslash;
|
|
}
|
|
|
|
void
|
|
CSSParserImpl::AppendImpliedEOFCharacters(nsAString& aResult)
|
|
{
|
|
nsCSSScanner::AppendImpliedEOFCharacters(mScanner->GetEOFCharacters(),
|
|
aResult);
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseAll()
|
|
{
|
|
nsCSSValue value;
|
|
if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
|
|
return false;
|
|
}
|
|
|
|
// It's unlikely we'll want to use 'all' from within a UA style sheet, so
|
|
// instead of computing the correct EnabledState value we just expand out
|
|
// to all content-visible properties.
|
|
CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, eCSSProperty_all,
|
|
CSSEnabledState::eForAllContent) {
|
|
AppendValue(*p, value);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseVariableDeclaration(CSSVariableDeclarations::Type* aType,
|
|
nsString& aValue)
|
|
{
|
|
CSSVariableDeclarations::Type type;
|
|
nsString variableValue;
|
|
bool dropBackslash;
|
|
nsString impliedCharacters;
|
|
|
|
// Record the token stream while parsing a variable value.
|
|
if (!mInSupportsCondition) {
|
|
mScanner->StartRecording();
|
|
}
|
|
if (!ParseValueWithVariables(&type, &dropBackslash, impliedCharacters,
|
|
nullptr, nullptr)) {
|
|
if (!mInSupportsCondition) {
|
|
mScanner->StopRecording();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (!mInSupportsCondition) {
|
|
if (type == CSSVariableDeclarations::eTokenStream) {
|
|
// This was indeed a token stream value, so store it in variableValue.
|
|
mScanner->StopRecording(variableValue);
|
|
if (dropBackslash) {
|
|
MOZ_ASSERT(!variableValue.IsEmpty() &&
|
|
variableValue[variableValue.Length() - 1] == '\\');
|
|
variableValue.Truncate(variableValue.Length() - 1);
|
|
}
|
|
variableValue.Append(impliedCharacters);
|
|
} else {
|
|
// This was either 'inherit' or 'initial'; we don't need the recorded
|
|
// input.
|
|
mScanner->StopRecording();
|
|
}
|
|
}
|
|
|
|
if (mHavePushBack && type == CSSVariableDeclarations::eTokenStream) {
|
|
// If we came to the end of a valid variable declaration and a token was
|
|
// pushed back, then it would have been ended by '!', ')', ';', ']' or '}'.
|
|
// We need to remove it from the recorded variable value.
|
|
MOZ_ASSERT(mToken.IsSymbol('!') ||
|
|
mToken.IsSymbol(')') ||
|
|
mToken.IsSymbol(';') ||
|
|
mToken.IsSymbol(']') ||
|
|
mToken.IsSymbol('}'));
|
|
if (!mInSupportsCondition) {
|
|
MOZ_ASSERT(!variableValue.IsEmpty());
|
|
MOZ_ASSERT(variableValue[variableValue.Length() - 1] == mToken.mSymbol);
|
|
variableValue.Truncate(variableValue.Length() - 1);
|
|
}
|
|
}
|
|
|
|
*aType = type;
|
|
aValue = variableValue;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseScrollSnapType()
|
|
{
|
|
nsCSSValue value;
|
|
if (!ParseSingleTokenVariant(value, VARIANT_HK,
|
|
nsCSSProps::kScrollSnapTypeKTable)) {
|
|
return false;
|
|
}
|
|
AppendValue(eCSSProperty_scroll_snap_type_x, value);
|
|
AppendValue(eCSSProperty_scroll_snap_type_y, value);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseScrollSnapPoints(nsCSSValue& aValue, nsCSSPropertyID aPropID)
|
|
{
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NONE,
|
|
nullptr)) {
|
|
return true;
|
|
}
|
|
if (!GetToken(true)) {
|
|
return false;
|
|
}
|
|
if (mToken.mType == eCSSToken_Function &&
|
|
nsCSSKeywords::LookupKeyword(mToken.mIdent) == eCSSKeyword_repeat) {
|
|
nsCSSValue lengthValue;
|
|
if (ParseNonNegativeVariant(lengthValue,
|
|
VARIANT_LENGTH | VARIANT_PERCENT | VARIANT_CALC,
|
|
nullptr) != CSSParseResult::Ok) {
|
|
REPORT_UNEXPECTED(PEExpectedNonnegativeNP);
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
if (!ExpectSymbol(')', true)) {
|
|
REPORT_UNEXPECTED(PEExpectedCloseParen);
|
|
SkipUntil(')');
|
|
return false;
|
|
}
|
|
RefPtr<nsCSSValue::Array> functionArray =
|
|
aValue.InitFunction(eCSSKeyword_repeat, 1);
|
|
functionArray->Item(1) = lengthValue;
|
|
return true;
|
|
}
|
|
UngetToken();
|
|
return false;
|
|
}
|
|
|
|
|
|
bool
|
|
CSSParserImpl::ParseScrollSnapDestination(nsCSSValue& aValue)
|
|
{
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
|
|
return true;
|
|
}
|
|
nsCSSValue itemValue;
|
|
if (!ParsePositionValue(aValue)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// This function is very similar to ParseImageLayerPosition, and ParseImageLayerSize.
|
|
bool
|
|
CSSParserImpl::ParseScrollSnapCoordinate(nsCSSValue& aValue)
|
|
{
|
|
if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NONE,
|
|
nullptr)) {
|
|
return true;
|
|
}
|
|
nsCSSValue itemValue;
|
|
if (!ParsePositionValue(itemValue)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
|
|
return false;
|
|
}
|
|
nsCSSValueList* item = aValue.SetListValue();
|
|
for (;;) {
|
|
item->mValue = itemValue;
|
|
if (!ExpectSymbol(',', true)) {
|
|
break;
|
|
}
|
|
if (!ParsePositionValue(itemValue)) {
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
|
|
return false;
|
|
}
|
|
item->mNext = new nsCSSValueList;
|
|
item = item->mNext;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::ParseValueWithVariables(CSSVariableDeclarations::Type* aType,
|
|
bool* aDropBackslash,
|
|
nsString& aImpliedCharacters,
|
|
void (*aFunc)(const nsAString&, void*),
|
|
void* aData)
|
|
{
|
|
// A property value is invalid if it contains variable references and also:
|
|
//
|
|
// * has unbalanced parens, brackets or braces
|
|
// * has any BAD_STRING or BAD_URL tokens
|
|
// * has any ';' or '!' tokens at the top level of a variable reference's
|
|
// fallback
|
|
//
|
|
// If the property is a custom property (i.e. a variable declaration), then
|
|
// it is also invalid if it consists of no tokens, such as:
|
|
//
|
|
// --invalid:;
|
|
//
|
|
// Note that is valid for a custom property to have a value that consists
|
|
// solely of white space, such as:
|
|
//
|
|
// --valid: ;
|
|
|
|
// Stack of closing characters for currently open constructs.
|
|
StopSymbolCharStack stack;
|
|
|
|
// Indexes into ')' characters in |stack| that correspond to "var(". This
|
|
// is used to stop parsing when we encounter a '!' or ';' at the top level
|
|
// of a variable reference's fallback.
|
|
AutoTArray<uint32_t, 16> references;
|
|
|
|
if (!GetToken(false)) {
|
|
// Variable value was empty since we reached EOF.
|
|
REPORT_UNEXPECTED_EOF(PEVariableEOF);
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType == eCSSToken_Symbol &&
|
|
(mToken.mSymbol == '!' ||
|
|
mToken.mSymbol == ')' ||
|
|
mToken.mSymbol == ';' ||
|
|
mToken.mSymbol == ']' ||
|
|
mToken.mSymbol == '}')) {
|
|
// Variable value was empty since we reached the end of the construct.
|
|
UngetToken();
|
|
REPORT_UNEXPECTED_TOKEN(PEVariableEmpty);
|
|
return false;
|
|
}
|
|
|
|
if (mToken.mType == eCSSToken_Whitespace) {
|
|
if (!GetToken(true)) {
|
|
// Variable value was white space only. This is valid.
|
|
MOZ_ASSERT(!BackslashDropped());
|
|
*aType = CSSVariableDeclarations::eTokenStream;
|
|
*aDropBackslash = false;
|
|
AppendImpliedEOFCharacters(aImpliedCharacters);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Look for 'initial', 'inherit' or 'unset' as the first non-white space
|
|
// token.
|
|
CSSVariableDeclarations::Type type = CSSVariableDeclarations::eTokenStream;
|
|
if (mToken.mType == eCSSToken_Ident) {
|
|
if (mToken.mIdent.LowerCaseEqualsLiteral("initial")) {
|
|
type = CSSVariableDeclarations::eInitial;
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("inherit")) {
|
|
type = CSSVariableDeclarations::eInherit;
|
|
} else if (mToken.mIdent.LowerCaseEqualsLiteral("unset")) {
|
|
type = CSSVariableDeclarations::eUnset;
|
|
}
|
|
}
|
|
|
|
if (type != CSSVariableDeclarations::eTokenStream) {
|
|
if (!GetToken(true)) {
|
|
// Variable value was 'initial' or 'inherit' followed by EOF.
|
|
MOZ_ASSERT(!BackslashDropped());
|
|
*aType = type;
|
|
*aDropBackslash = false;
|
|
AppendImpliedEOFCharacters(aImpliedCharacters);
|
|
return true;
|
|
}
|
|
UngetToken();
|
|
if (mToken.mType == eCSSToken_Symbol &&
|
|
(mToken.mSymbol == '!' ||
|
|
mToken.mSymbol == ')' ||
|
|
mToken.mSymbol == ';' ||
|
|
mToken.mSymbol == ']' ||
|
|
mToken.mSymbol == '}')) {
|
|
// Variable value was 'initial' or 'inherit' followed by the end
|
|
// of the declaration.
|
|
MOZ_ASSERT(!BackslashDropped());
|
|
*aType = type;
|
|
*aDropBackslash = false;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
do {
|
|
switch (mToken.mType) {
|
|
case eCSSToken_Symbol:
|
|
if (mToken.mSymbol == '(') {
|
|
stack.AppendElement(')');
|
|
} else if (mToken.mSymbol == '[') {
|
|
stack.AppendElement(']');
|
|
} else if (mToken.mSymbol == '{') {
|
|
stack.AppendElement('}');
|
|
} else if (mToken.mSymbol == ';' ||
|
|
mToken.mSymbol == '!') {
|
|
if (stack.IsEmpty()) {
|
|
UngetToken();
|
|
MOZ_ASSERT(!BackslashDropped());
|
|
*aType = CSSVariableDeclarations::eTokenStream;
|
|
*aDropBackslash = false;
|
|
return true;
|
|
} else if (!references.IsEmpty() &&
|
|
references.LastElement() == stack.Length() - 1) {
|
|
REPORT_UNEXPECTED_TOKEN(PEInvalidVariableTokenFallback);
|
|
SkipUntilAllOf(stack);
|
|
return false;
|
|
}
|
|
} else if (mToken.mSymbol == ')' ||
|
|
mToken.mSymbol == ']' ||
|
|
mToken.mSymbol == '}') {
|
|
for (;;) {
|
|
if (stack.IsEmpty()) {
|
|
UngetToken();
|
|
MOZ_ASSERT(!BackslashDropped());
|
|
*aType = CSSVariableDeclarations::eTokenStream;
|
|
*aDropBackslash = false;
|
|
return true;
|
|
}
|
|
char16_t c = stack.LastElement();
|
|
stack.TruncateLength(stack.Length() - 1);
|
|
if (!references.IsEmpty() &&
|
|
references.LastElement() == stack.Length()) {
|
|
references.TruncateLength(references.Length() - 1);
|
|
}
|
|
if (mToken.mSymbol == c) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case eCSSToken_Function:
|
|
if (mToken.mIdent.LowerCaseEqualsLiteral("var")) {
|
|
if (!GetToken(true)) {
|
|
// EOF directly after "var(".
|
|
REPORT_UNEXPECTED_EOF(PEExpectedVariableNameEOF);
|
|
return false;
|
|
}
|
|
if (mToken.mType != eCSSToken_Ident ||
|
|
!nsCSSProps::IsCustomPropertyName(mToken.mIdent)) {
|
|
// There must be an identifier directly after the "var(" and
|
|
// it must be a custom property name.
|
|
UngetToken();
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedVariableName);
|
|
SkipUntil(')');
|
|
SkipUntilAllOf(stack);
|
|
return false;
|
|
}
|
|
if (aFunc) {
|
|
MOZ_ASSERT(Substring(mToken.mIdent, 0,
|
|
CSS_CUSTOM_NAME_PREFIX_LENGTH).
|
|
EqualsLiteral("--"));
|
|
// remove '--'
|
|
const nsDependentSubstring varName =
|
|
Substring(mToken.mIdent, CSS_CUSTOM_NAME_PREFIX_LENGTH);
|
|
aFunc(varName, aData);
|
|
}
|
|
if (!GetToken(true)) {
|
|
// EOF right after "var(<ident>".
|
|
stack.AppendElement(')');
|
|
} else if (mToken.IsSymbol(',')) {
|
|
// Variable reference with fallback.
|
|
if (!GetToken(false) || mToken.IsSymbol(')')) {
|
|
// Comma must be followed by at least one fallback token.
|
|
REPORT_UNEXPECTED(PEExpectedVariableFallback);
|
|
SkipUntilAllOf(stack);
|
|
return false;
|
|
}
|
|
UngetToken();
|
|
references.AppendElement(stack.Length());
|
|
stack.AppendElement(')');
|
|
} else if (mToken.IsSymbol(')')) {
|
|
// Correctly closed variable reference.
|
|
} else {
|
|
// Malformed variable reference.
|
|
REPORT_UNEXPECTED_TOKEN(PEExpectedVariableCommaOrCloseParen);
|
|
SkipUntil(')');
|
|
SkipUntilAllOf(stack);
|
|
return false;
|
|
}
|
|
} else {
|
|
stack.AppendElement(')');
|
|
}
|
|
break;
|
|
|
|
case eCSSToken_Bad_String:
|
|
SkipUntilAllOf(stack);
|
|
return false;
|
|
|
|
case eCSSToken_Bad_URL:
|
|
SkipUntil(')');
|
|
SkipUntilAllOf(stack);
|
|
return false;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
} while (GetToken(true));
|
|
|
|
// Append any implied closing characters.
|
|
*aDropBackslash = BackslashDropped();
|
|
AppendImpliedEOFCharacters(aImpliedCharacters);
|
|
uint32_t i = stack.Length();
|
|
while (i--) {
|
|
aImpliedCharacters.Append(stack[i]);
|
|
}
|
|
|
|
*aType = type;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CSSParserImpl::IsValueValidForProperty(const nsCSSPropertyID aPropID,
|
|
const nsAString& aPropValue)
|
|
{
|
|
mData.AssertInitialState();
|
|
mTempData.AssertInitialState();
|
|
|
|
nsCSSScanner scanner(aPropValue, 0);
|
|
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, nullptr);
|
|
InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
|
|
|
|
// We normally would need to pass in a sheet principal to InitScanner,
|
|
// because we might parse a URL value. However, we will never use the
|
|
// parsed nsCSSValue (and so whether we have a sheet principal or not
|
|
// doesn't really matter), so to avoid failing the assertion in
|
|
// SetValueToURL, we set mSheetPrincipalRequired to false to declare
|
|
// that it's safe to skip the assertion.
|
|
AutoRestore<bool> autoRestore(mSheetPrincipalRequired);
|
|
mSheetPrincipalRequired = false;
|
|
|
|
nsAutoSuppressErrors suppressErrors(this);
|
|
|
|
mSection = eCSSSection_General;
|
|
scanner.SetSVGMode(false);
|
|
|
|
// Check for unknown properties
|
|
if (eCSSProperty_UNKNOWN == aPropID) {
|
|
ReleaseScanner();
|
|
return false;
|
|
}
|
|
|
|
// Check that the property and value parse successfully
|
|
bool parsedOK = ParseProperty(aPropID);
|
|
|
|
// Check for priority
|
|
parsedOK = parsedOK && ParsePriority() != ePriority_Error;
|
|
|
|
// We should now be at EOF
|
|
parsedOK = parsedOK && !GetToken(true);
|
|
|
|
mTempData.ClearProperty(aPropID);
|
|
mTempData.AssertInitialState();
|
|
mData.AssertInitialState();
|
|
|
|
CLEAR_ERROR();
|
|
ReleaseScanner();
|
|
|
|
return parsedOK;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Recycling of parser implementation objects
|
|
|
|
static CSSParserImpl* gFreeList = nullptr;
|
|
|
|
/* static */ void
|
|
nsCSSParser::Startup()
|
|
{
|
|
Preferences::AddBoolVarCache(&sOpentypeSVGEnabled,
|
|
"gfx.font_rendering.opentype_svg.enabled");
|
|
Preferences::AddBoolVarCache(&sWebkitPrefixedAliasesEnabled,
|
|
"layout.css.prefixes.webkit");
|
|
Preferences::AddBoolVarCache(&sWebkitDevicePixelRatioEnabled,
|
|
"layout.css.prefixes.device-pixel-ratio-webkit");
|
|
Preferences::AddBoolVarCache(&sUnprefixingServiceEnabled,
|
|
"layout.css.unprefixing-service.enabled");
|
|
#ifdef NIGHTLY_BUILD
|
|
Preferences::AddBoolVarCache(&sUnprefixingServiceGloballyWhitelisted,
|
|
"layout.css.unprefixing-service.globally-whitelisted");
|
|
#endif
|
|
Preferences::AddBoolVarCache(&sMozGradientsEnabled,
|
|
"layout.css.prefixes.gradients");
|
|
Preferences::AddBoolVarCache(&sControlCharVisibility,
|
|
"layout.css.control-characters.visible");
|
|
}
|
|
|
|
nsCSSParser::nsCSSParser(mozilla::css::Loader* aLoader,
|
|
CSSStyleSheet* aSheet)
|
|
{
|
|
CSSParserImpl *impl = gFreeList;
|
|
if (impl) {
|
|
gFreeList = impl->mNextFree;
|
|
impl->mNextFree = nullptr;
|
|
} else {
|
|
impl = new CSSParserImpl();
|
|
}
|
|
|
|
if (aLoader) {
|
|
impl->SetChildLoader(aLoader);
|
|
impl->SetQuirkMode(aLoader->GetCompatibilityMode() ==
|
|
eCompatibility_NavQuirks);
|
|
}
|
|
if (aSheet) {
|
|
impl->SetStyleSheet(aSheet);
|
|
}
|
|
|
|
mImpl = static_cast<void*>(impl);
|
|
}
|
|
|
|
nsCSSParser::~nsCSSParser()
|
|
{
|
|
CSSParserImpl *impl = static_cast<CSSParserImpl*>(mImpl);
|
|
impl->Reset();
|
|
impl->mNextFree = gFreeList;
|
|
gFreeList = impl;
|
|
}
|
|
|
|
/* static */ void
|
|
nsCSSParser::Shutdown()
|
|
{
|
|
CSSParserImpl *tofree = gFreeList;
|
|
CSSParserImpl *next;
|
|
while (tofree)
|
|
{
|
|
next = tofree->mNextFree;
|
|
delete tofree;
|
|
tofree = next;
|
|
}
|
|
}
|
|
|
|
// Wrapper methods
|
|
|
|
nsresult
|
|
nsCSSParser::ParseSheet(const nsAString& aInput,
|
|
nsIURI* aSheetURI,
|
|
nsIURI* aBaseURI,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
uint32_t aLineNumber,
|
|
css::LoaderReusableStyleSheets* aReusableSheets)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseSheet(aInput, aSheetURI, aBaseURI, aSheetPrincipal, aLineNumber,
|
|
aReusableSheets);
|
|
}
|
|
|
|
already_AddRefed<css::Declaration>
|
|
nsCSSParser::ParseStyleAttribute(const nsAString& aAttributeValue,
|
|
nsIURI* aDocURI,
|
|
nsIURI* aBaseURI,
|
|
nsIPrincipal* aNodePrincipal)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseStyleAttribute(aAttributeValue, aDocURI, aBaseURI, aNodePrincipal);
|
|
}
|
|
|
|
nsresult
|
|
nsCSSParser::ParseDeclarations(const nsAString& aBuffer,
|
|
nsIURI* aSheetURI,
|
|
nsIURI* aBaseURI,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
css::Declaration* aDeclaration,
|
|
bool* aChanged)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseDeclarations(aBuffer, aSheetURI, aBaseURI, aSheetPrincipal,
|
|
aDeclaration, aChanged);
|
|
}
|
|
|
|
nsresult
|
|
nsCSSParser::ParseRule(const nsAString& aRule,
|
|
nsIURI* aSheetURI,
|
|
nsIURI* aBaseURI,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
css::Rule** aResult)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseRule(aRule, aSheetURI, aBaseURI, aSheetPrincipal, aResult);
|
|
}
|
|
|
|
void
|
|
nsCSSParser::ParseProperty(const nsCSSPropertyID aPropID,
|
|
const nsAString& aPropValue,
|
|
nsIURI* aSheetURI,
|
|
nsIURI* aBaseURI,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
css::Declaration* aDeclaration,
|
|
bool* aChanged,
|
|
bool aIsImportant,
|
|
bool aIsSVGMode)
|
|
{
|
|
static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseProperty(aPropID, aPropValue, aSheetURI, aBaseURI,
|
|
aSheetPrincipal, aDeclaration, aChanged,
|
|
aIsImportant, aIsSVGMode);
|
|
}
|
|
|
|
void
|
|
nsCSSParser::ParseLonghandProperty(const nsCSSPropertyID aPropID,
|
|
const nsAString& aPropValue,
|
|
nsIURI* aSheetURI,
|
|
nsIURI* aBaseURI,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
nsCSSValue& aResult)
|
|
{
|
|
static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseLonghandProperty(aPropID, aPropValue, aSheetURI, aBaseURI,
|
|
aSheetPrincipal, aResult);
|
|
}
|
|
|
|
bool
|
|
nsCSSParser::ParseTransformProperty(const nsAString& aPropValue,
|
|
bool aDisallowRelativeValues,
|
|
nsCSSValue& aResult)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseTransformProperty(aPropValue, aDisallowRelativeValues, aResult);
|
|
}
|
|
|
|
void
|
|
nsCSSParser::ParseVariable(const nsAString& aVariableName,
|
|
const nsAString& aPropValue,
|
|
nsIURI* aSheetURI,
|
|
nsIURI* aBaseURI,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
css::Declaration* aDeclaration,
|
|
bool* aChanged,
|
|
bool aIsImportant)
|
|
{
|
|
static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseVariable(aVariableName, aPropValue, aSheetURI, aBaseURI,
|
|
aSheetPrincipal, aDeclaration, aChanged, aIsImportant);
|
|
}
|
|
|
|
void
|
|
nsCSSParser::ParseMediaList(const nsSubstring& aBuffer,
|
|
nsIURI* aURI,
|
|
uint32_t aLineNumber,
|
|
nsMediaList* aMediaList,
|
|
bool aHTMLMode)
|
|
{
|
|
static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseMediaList(aBuffer, aURI, aLineNumber, aMediaList, aHTMLMode);
|
|
}
|
|
|
|
bool
|
|
nsCSSParser::ParseSourceSizeList(const nsAString& aBuffer,
|
|
nsIURI* aURI,
|
|
uint32_t aLineNumber,
|
|
InfallibleTArray< nsAutoPtr<nsMediaQuery> >& aQueries,
|
|
InfallibleTArray<nsCSSValue>& aValues,
|
|
bool aHTMLMode)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseSourceSizeList(aBuffer, aURI, aLineNumber, aQueries, aValues,
|
|
aHTMLMode);
|
|
}
|
|
|
|
bool
|
|
nsCSSParser::ParseFontFamilyListString(const nsSubstring& aBuffer,
|
|
nsIURI* aURI,
|
|
uint32_t aLineNumber,
|
|
nsCSSValue& aValue)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseFontFamilyListString(aBuffer, aURI, aLineNumber, aValue);
|
|
}
|
|
|
|
bool
|
|
nsCSSParser::ParseColorString(const nsSubstring& aBuffer,
|
|
nsIURI* aURI,
|
|
uint32_t aLineNumber,
|
|
nsCSSValue& aValue,
|
|
bool aSuppressErrors /* false */)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseColorString(aBuffer, aURI, aLineNumber, aValue, aSuppressErrors);
|
|
}
|
|
|
|
nsresult
|
|
nsCSSParser::ParseSelectorString(const nsSubstring& aSelectorString,
|
|
nsIURI* aURI,
|
|
uint32_t aLineNumber,
|
|
nsCSSSelectorList** aSelectorList)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseSelectorString(aSelectorString, aURI, aLineNumber, aSelectorList);
|
|
}
|
|
|
|
already_AddRefed<nsCSSKeyframeRule>
|
|
nsCSSParser::ParseKeyframeRule(const nsSubstring& aBuffer,
|
|
nsIURI* aURI,
|
|
uint32_t aLineNumber)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseKeyframeRule(aBuffer, aURI, aLineNumber);
|
|
}
|
|
|
|
bool
|
|
nsCSSParser::ParseKeyframeSelectorString(const nsSubstring& aSelectorString,
|
|
nsIURI* aURI,
|
|
uint32_t aLineNumber,
|
|
InfallibleTArray<float>& aSelectorList)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseKeyframeSelectorString(aSelectorString, aURI, aLineNumber,
|
|
aSelectorList);
|
|
}
|
|
|
|
bool
|
|
nsCSSParser::EvaluateSupportsDeclaration(const nsAString& aProperty,
|
|
const nsAString& aValue,
|
|
nsIURI* aDocURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aDocPrincipal)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
EvaluateSupportsDeclaration(aProperty, aValue, aDocURL, aBaseURL,
|
|
aDocPrincipal);
|
|
}
|
|
|
|
bool
|
|
nsCSSParser::EvaluateSupportsCondition(const nsAString& aCondition,
|
|
nsIURI* aDocURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aDocPrincipal)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
EvaluateSupportsCondition(aCondition, aDocURL, aBaseURL, aDocPrincipal);
|
|
}
|
|
|
|
bool
|
|
nsCSSParser::EnumerateVariableReferences(const nsAString& aPropertyValue,
|
|
VariableEnumFunc aFunc,
|
|
void* aData)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
EnumerateVariableReferences(aPropertyValue, aFunc, aData);
|
|
}
|
|
|
|
bool
|
|
nsCSSParser::ResolveVariableValue(const nsAString& aPropertyValue,
|
|
const CSSVariableValues* aVariables,
|
|
nsString& aResult,
|
|
nsCSSTokenSerializationType& aFirstToken,
|
|
nsCSSTokenSerializationType& aLastToken)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
ResolveVariableValue(aPropertyValue, aVariables,
|
|
aResult, aFirstToken, aLastToken);
|
|
}
|
|
|
|
void
|
|
nsCSSParser::ParsePropertyWithVariableReferences(
|
|
nsCSSPropertyID aPropertyID,
|
|
nsCSSPropertyID aShorthandPropertyID,
|
|
const nsAString& aValue,
|
|
const CSSVariableValues* aVariables,
|
|
nsRuleData* aRuleData,
|
|
nsIURI* aDocURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aDocPrincipal,
|
|
CSSStyleSheet* aSheet,
|
|
uint32_t aLineNumber,
|
|
uint32_t aLineOffset)
|
|
{
|
|
static_cast<CSSParserImpl*>(mImpl)->
|
|
ParsePropertyWithVariableReferences(aPropertyID, aShorthandPropertyID,
|
|
aValue, aVariables, aRuleData, aDocURL,
|
|
aBaseURL, aDocPrincipal, aSheet,
|
|
aLineNumber, aLineOffset);
|
|
}
|
|
|
|
bool
|
|
nsCSSParser::ParseCounterStyleName(const nsAString& aBuffer,
|
|
nsIURI* aURL,
|
|
nsAString& aName)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseCounterStyleName(aBuffer, aURL, aName);
|
|
}
|
|
|
|
bool
|
|
nsCSSParser::ParseCounterDescriptor(nsCSSCounterDesc aDescID,
|
|
const nsAString& aBuffer,
|
|
nsIURI* aSheetURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
nsCSSValue& aValue)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseCounterDescriptor(aDescID, aBuffer,
|
|
aSheetURL, aBaseURL, aSheetPrincipal, aValue);
|
|
}
|
|
|
|
bool
|
|
nsCSSParser::ParseFontFaceDescriptor(nsCSSFontDesc aDescID,
|
|
const nsAString& aBuffer,
|
|
nsIURI* aSheetURL,
|
|
nsIURI* aBaseURL,
|
|
nsIPrincipal* aSheetPrincipal,
|
|
nsCSSValue& aValue)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
ParseFontFaceDescriptor(aDescID, aBuffer,
|
|
aSheetURL, aBaseURL, aSheetPrincipal, aValue);
|
|
}
|
|
|
|
bool
|
|
nsCSSParser::IsValueValidForProperty(const nsCSSPropertyID aPropID,
|
|
const nsAString& aPropValue)
|
|
{
|
|
return static_cast<CSSParserImpl*>(mImpl)->
|
|
IsValueValidForProperty(aPropID, aPropValue);
|
|
}
|
|
|
|
/* static */
|
|
uint8_t
|
|
nsCSSParser::ControlCharVisibilityDefault()
|
|
{
|
|
return sControlCharVisibility
|
|
? NS_STYLE_CONTROL_CHARACTER_VISIBILITY_VISIBLE
|
|
: NS_STYLE_CONTROL_CHARACTER_VISIBILITY_HIDDEN;
|
|
}
|