Issue #2078 - Part 1: Update CSS rule processor to handle :is() and :where() CSS pseudo-classes

This modifies selector list parsing to accommodate being "forgiving". Aliases for the :is selector's former names were also included. Note that the older and prefixed variant `-moz-any` remains unforgiving.
This commit is contained in:
FranklinDM 2023-01-03 20:52:31 +08:00 committed by roytam1
commit 453b715ef6
6 changed files with 103 additions and 35 deletions

View file

@ -780,7 +780,8 @@ protected:
CSSPseudoClassType aType);
nsSelectorParsingStatus ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
CSSPseudoClassType aType);
CSSPseudoClassType aType,
bool aIsForgiving);
nsSelectorParsingStatus ParseNegatedSimpleSelector(int32_t& aDataMask,
nsCSSSelector& aSelector);
@ -788,9 +789,13 @@ protected:
// 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);
char16_t aStopChar,
bool aIsForgiving);
bool ParseSelectorGroup(nsCSSSelectorList*& aListHead,
bool aIsForgiving);
bool ParseSelector(nsCSSSelectorList* aList,
char16_t aPrevCombinator,
bool aIsForgiving);
enum {
eParseDeclaration_InBraces = 1 << 0,
@ -2321,7 +2326,7 @@ CSSParserImpl::ParseSelectorString(const nsSubstring& aSelectorString,
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
InitScanner(scanner, reporter, aURI, aURI, nullptr);
bool success = ParseSelectorList(*aSelectorList, char16_t(0));
bool success = ParseSelectorList(*aSelectorList, char16_t(0), false);
// We deliberately do not call OUTPUT_ERROR here, because all our
// callers map a failure return to a JS exception, and if that JS
@ -5403,7 +5408,7 @@ CSSParserImpl::ParseRuleSet(RuleAppendFunc aAppendFunc, void* aData,
nsCSSSelectorList* slist = nullptr;
uint32_t linenum, colnum;
if (!GetNextTokenLocation(true, &linenum, &colnum) ||
!ParseSelectorList(slist, char16_t('{'))) {
!ParseSelectorList(slist, char16_t('{'), false)) {
REPORT_UNEXPECTED(PEBadSelectorRSIgnored);
OUTPUT_ERROR();
SkipRuleSet(aInsideBraces);
@ -5439,13 +5444,20 @@ CSSParserImpl::ParseRuleSet(RuleAppendFunc aAppendFunc, void* aData,
bool
CSSParserImpl::ParseSelectorList(nsCSSSelectorList*& aListHead,
char16_t aStopChar)
char16_t aStopChar,
bool aIsForgiving)
{
nsCSSSelectorList* list = nullptr;
if (! ParseSelectorGroup(list)) {
// must have at least one selector group
aListHead = nullptr;
return false;
if (! ParseSelectorGroup(list, aIsForgiving)) {
if (aIsForgiving) {
// Initialize to an empty list if the first selector group was invalid
// and we're a forgiving selector list.
list = new nsCSSSelectorList();
} else {
// must have at least one selector group
aListHead = nullptr;
return false;
}
}
NS_ASSERTION(nullptr != list, "no selector list");
aListHead = list;
@ -5467,7 +5479,11 @@ CSSParserImpl::ParseSelectorList(nsCSSSelectorList*& aListHead,
if (',' == tk->mSymbol) {
nsCSSSelectorList* newList = nullptr;
// Another selector group must follow
if (! ParseSelectorGroup(newList)) {
if (! ParseSelectorGroup(newList, aIsForgiving)) {
// Ignore invalid selectors if we're a forgiving selector list.
if (aIsForgiving) {
continue;
}
break;
}
// add new list to the end of the selector list
@ -5479,9 +5495,12 @@ CSSParserImpl::ParseSelectorList(nsCSSSelectorList*& aListHead,
return true;
}
}
REPORT_UNEXPECTED_TOKEN(PESelectorListExtra);
UngetToken();
break;
if (!aIsForgiving) {
REPORT_UNEXPECTED_TOKEN(PESelectorListExtra);
UngetToken();
break;
}
}
delete aListHead;
@ -5501,13 +5520,13 @@ static bool IsUniversalSelector(const nsCSSSelector& aSelector)
}
bool
CSSParserImpl::ParseSelectorGroup(nsCSSSelectorList*& aList)
CSSParserImpl::ParseSelectorGroup(nsCSSSelectorList*& aList, bool aIsForgiving)
{
char16_t combinator = 0;
nsAutoPtr<nsCSSSelectorList> list(new nsCSSSelectorList());
for (;;) {
if (!ParseSelector(list, combinator)) {
if (!ParseSelector(list, combinator, aIsForgiving)) {
return false;
}
@ -6140,8 +6159,11 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
else {
MOZ_ASSERT(nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType),
"unexpected pseudo with function token");
bool isForgiving =
nsCSSPseudoClasses::HasForgivingSelectorListArg(pseudoClassType);
parsingStatus = ParsePseudoClassWithSelectorListArg(aSelector,
pseudoClassType);
pseudoClassType,
isForgiving);
}
if (eSelectorParsingStatus_Continue != parsingStatus) {
if (eSelectorParsingStatus_Error == parsingStatus) {
@ -6515,18 +6537,24 @@ CSSParserImpl::ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
CSSPseudoClassType aType)
CSSPseudoClassType aType,
bool aIsForgiving)
{
nsAutoPtr<nsCSSSelectorList> slist;
if (! ParseSelectorList(*getter_Transfers(slist), char16_t(')'))) {
if (! ParseSelectorList(*getter_Transfers(slist), char16_t(')'), aIsForgiving)) {
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()) {
if (s == nullptr) {
MOZ_ASSERT(aIsForgiving,
"unexpected empty selector in unforgiving selector list");
break;
}
// Check that none of the selectors in the list have combinators or
// pseudo-elements.
if ((!aIsForgiving && s->mNext) || s->IsPseudoElement()) {
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
}
}
@ -6550,7 +6578,8 @@ CSSParserImpl::ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
*/
bool
CSSParserImpl::ParseSelector(nsCSSSelectorList* aList,
char16_t aPrevCombinator)
char16_t aPrevCombinator,
bool aIsForgiving)
{
if (! GetToken(true)) {
REPORT_UNEXPECTED_EOF(PESelectorEOF);
@ -6624,6 +6653,12 @@ CSSParserImpl::ParseSelector(nsCSSSelectorList* aList,
}
if (!dataMask) {
// XXX(franklindm): We're effectively ignoring stray combinators
// and empty selector groups here for forgiving selector lists.
// It doesn't seem right, but this is how tainted browsers do it.
if (aIsForgiving) {
return false;
}
if (selector->mNext) {
REPORT_UNEXPECTED(PESelectorGroupExtraCombinator);
} else {