Issue #2137 - Part 1: Modify :not() selector to accept a complex selector list

This commit is contained in:
FranklinDM 2023-03-10 00:20:44 +08:00 • committed by roytam1
commit 82fa9fb80b
7 changed files with 83 additions and 35 deletions

View file

@ -920,9 +920,13 @@ nsCSSSelector::AppendToStringWithoutCombinatorsOrNegations
// has a ":" that can't be escaped and (b) all pseudo-classes at // has a ":" that can't be escaped and (b) all pseudo-classes at
// this point are known, and therefore we know they don't need // this point are known, and therefore we know they don't need
// escaping. // escaping.
aString.Append(temp); if (!nsCSSPseudoClasses::IsHiddenFromSerialization(list->mType)) {
aString.Append(temp);
}
if (list->u.mMemory) { if (list->u.mMemory) {
aString.Append(char16_t('(')); if (!nsCSSPseudoClasses::IsHiddenFromSerialization(list->mType)) {
aString.Append(char16_t('('));
}
if (nsCSSPseudoClasses::HasStringArg(list->mType)) { if (nsCSSPseudoClasses::HasStringArg(list->mType)) {
nsStyleUtil::AppendEscapedCSSIdent( nsStyleUtil::AppendEscapedCSSIdent(
nsDependentString(list->u.mString), aString); nsDependentString(list->u.mString), aString);
@ -951,7 +955,9 @@ nsCSSSelector::AppendToStringWithoutCombinatorsOrNegations
list->u.mSelectorList->ToString(tmp, aSheet); list->u.mSelectorList->ToString(tmp, aSheet);
aString.Append(tmp); aString.Append(tmp);
} }
aString.Append(char16_t(')')); if (!nsCSSPseudoClasses::IsHiddenFromSerialization(list->mType)) {
aString.Append(char16_t(')'));
}
} }
} }
} }

View file

@ -67,6 +67,7 @@ static bool sWebkitPrefixedAliasesEnabled;
static bool sWebkitDevicePixelRatioEnabled; static bool sWebkitDevicePixelRatioEnabled;
static bool sMozGradientsEnabled; static bool sMozGradientsEnabled;
static bool sControlCharVisibility; static bool sControlCharVisibility;
static bool sLegacyNegationPseudoClassEnabled;
const uint32_t const uint32_t
nsCSSProps::kParserVariantTable[eCSSProperty_COUNT_no_shorthands] = { nsCSSProps::kParserVariantTable[eCSSProperty_COUNT_no_shorthands] = {
@ -6144,7 +6145,6 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
#ifdef MOZ_XUL #ifdef MOZ_XUL
isTree || isTree ||
#endif #endif
CSSPseudoClassType::negation == pseudoClassType ||
nsCSSPseudoClasses::HasStringArg(pseudoClassType) || nsCSSPseudoClasses::HasStringArg(pseudoClassType) ||
nsCSSPseudoClasses::HasNthPairArg(pseudoClassType) || nsCSSPseudoClasses::HasNthPairArg(pseudoClassType) ||
nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType)) && nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType)) &&
@ -6182,25 +6182,26 @@ CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask,
} }
} }
if (!parsingPseudoElement && if (!parsingPseudoElement && isPseudoClass) {
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; aDataMask |= SEL_MASK_PCLASS;
if (eCSSToken_Function == mToken.mType) { if (eCSSToken_Function == mToken.mType) {
nsSelectorParsingStatus parsingStatus; nsSelectorParsingStatus parsingStatus;
if (nsCSSPseudoClasses::HasStringArg(pseudoClassType)) { if (sLegacyNegationPseudoClassEnabled &&
CSSPseudoClassType::negation == pseudoClassType) {
// :not() can't be itself negated
if (aIsNegated) {
REPORT_UNEXPECTED_TOKEN(PEPseudoSelDoubleNot);
UngetToken();
return eSelectorParsingStatus_Error;
}
// CSS 3 Negation pseudo-class takes one simple selector as argument
parsingStatus =
ParseNegatedSimpleSelector(aDataMask, aSelector);
if (eSelectorParsingStatus_Continue != parsingStatus) {
return parsingStatus;
}
}
else if (nsCSSPseudoClasses::HasStringArg(pseudoClassType)) {
parsingStatus = parsingStatus =
ParsePseudoClassWithIdentArg(aSelector, pseudoClassType); ParsePseudoClassWithIdentArg(aSelector, pseudoClassType);
} }
@ -6583,8 +6584,6 @@ CSSParserImpl::ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
// //
// Parse the argument of a pseudo-class that has a selector list argument. // 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::nsSelectorParsingStatus
CSSParserImpl::ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector, CSSParserImpl::ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
@ -6612,22 +6611,35 @@ CSSParserImpl::ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
} }
for (nsCSSSelectorList *l = slist; l; l = l->mNext) { // Special handling for the :not() pseudo-class.
nsCSSSelector *s = l->mSelectors; if (aType == CSSPseudoClassType::negation) {
if (s == nullptr) { nsCSSSelector* negations = &aSelector;
MOZ_ASSERT(isForgiving, while (negations->mNegations) {
"unexpected empty selector in unforgiving selector list"); negations = negations->mNegations;
break;
} }
// Check that none of the selectors in the list have combinators or // XXX: Use a special internal-only pseudo-class to handle selector lists.
// pseudo-elements. // TODO: This should only happen if we don't have a simple selector.
if ((!isForgiving && s->mNext) || s->IsPseudoElement()) { nsCSSSelector* newSel = new nsCSSSelector();
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') newSel->AddPseudoClass(CSSPseudoClassType::mozAnyPrivate, slist.forget());
negations->mNegations = newSel;
} else {
for (nsCSSSelectorList *l = slist; l; l = l->mNext) {
nsCSSSelector *s = l->mSelectors;
if (s == nullptr) {
MOZ_ASSERT(isForgiving,
"unexpected empty selector in unforgiving selector list");
break;
}
// Check that none of the selectors in the list have combinators or
// pseudo-elements.
if ((!isForgiving && s->mNext) || s->IsPseudoElement()) {
return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
}
} }
}
// Add the pseudo with the selector list parameter // Add the pseudo with the selector list parameter
aSelector.AddPseudoClass(aType, slist.forget()); aSelector.AddPseudoClass(aType, slist.forget());
}
// close the parenthesis // close the parenthesis
if (!ExpectSymbol(')', true)) { if (!ExpectSymbol(')', true)) {
@ -17891,6 +17903,8 @@ nsCSSParser::Startup()
"layout.css.prefixes.gradients"); "layout.css.prefixes.gradients");
Preferences::AddBoolVarCache(&sControlCharVisibility, Preferences::AddBoolVarCache(&sControlCharVisibility,
"layout.css.control-characters.visible"); "layout.css.control-characters.visible");
Preferences::AddBoolVarCache(&sLegacyNegationPseudoClassEnabled,
"layout.css.legacy-negation-pseudo.enabled");
} }
nsCSSParser::nsCSSParser(mozilla::css::Loader* aLoader, nsCSSParser::nsCSSParser(mozilla::css::Loader* aLoader,

View file

@ -133,6 +133,11 @@ CSS_PSEUDO_CLASS(mozBrowserFrame, ":-moz-browser-frame",
// matching operation. // matching operation.
CSS_PSEUDO_CLASS(scope, ":scope", 0, "layout.css.scope-pseudo.enabled") CSS_PSEUDO_CLASS(scope, ":scope", 0, "layout.css.scope-pseudo.enabled")
// Matches selectors inside the selector list argument. Unlike :is(),
// this is unforgiving and hidden from serialization.
CSS_PSEUDO_CLASS(mozAnyPrivate, ":-moz-any-private",
CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME, "")
// :not needs to come at the end of the non-bit pseudo-class list, since // :not needs to come at the end of the non-bit pseudo-class list, since
// it doesn't actually get directly matched on in SelectorMatches. // it doesn't actually get directly matched on in SelectorMatches.
CSS_PSEUDO_CLASS(negation, ":not", 0, "") CSS_PSEUDO_CLASS(negation, ":not", 0, "")

View file

@ -122,7 +122,9 @@ bool
nsCSSPseudoClasses::HasSelectorListArg(Type aType) nsCSSPseudoClasses::HasSelectorListArg(Type aType)
{ {
return HasForgivingSelectorListArg(aType) || return HasForgivingSelectorListArg(aType) ||
aType == Type::negation ||
aType == Type::mozAny || aType == Type::mozAny ||
aType == Type::mozAnyPrivate ||
aType == Type::host || aType == Type::host ||
aType == Type::hostContext; aType == Type::hostContext;
} }
@ -133,6 +135,12 @@ nsCSSPseudoClasses::HasOptionalSelectorListArg(Type aType)
return aType == Type::host; return aType == Type::host;
} }
bool
nsCSSPseudoClasses::IsHiddenFromSerialization(Type aType)
{
return aType == Type::mozAnyPrivate;
}
void void
nsCSSPseudoClasses::PseudoTypeToString(Type aType, nsAString& aString) nsCSSPseudoClasses::PseudoTypeToString(Type aType, nsAString& aString)
{ {

View file

@ -62,6 +62,7 @@ public:
static bool HasForgivingSelectorListArg(Type aType); static bool HasForgivingSelectorListArg(Type aType);
static bool HasSelectorListArg(Type aType); static bool HasSelectorListArg(Type aType);
static bool HasOptionalSelectorListArg(Type aType); static bool HasOptionalSelectorListArg(Type aType);
static bool IsHiddenFromSerialization(Type aType);
static bool IsUserActionPseudoClass(Type aType); static bool IsUserActionPseudoClass(Type aType);
// Should only be used on types other than Count and NotPseudoClass // Should only be used on types other than Count and NotPseudoClass

View file

@ -1952,6 +1952,17 @@ static bool SelectorMatches(Element* aElement,
} }
break; break;
case CSSPseudoClassType::mozAnyPrivate:
{
if (!SelectorListMatches(aElement,
pseudoClass,
aNodeMatchContext,
aTreeMatchContext)) {
return false;
}
}
break;
case CSSPseudoClassType::host: case CSSPseudoClassType::host:
{ {
// In order to match :host, the element must be a shadow root host, // In order to match :host, the element must be a shadow root host,

View file

@ -2551,6 +2551,9 @@ pref("layout.css.prefixes.webkit", true);
// pref is set to false.) // pref is set to false.)
pref("layout.css.prefixes.device-pixel-ratio-webkit", false); pref("layout.css.prefixes.device-pixel-ratio-webkit", false);
// Is the legacy negation pseudo-class behavior enabled?
pref("layout.css.legacy-negation-pseudo.enabled", false);
// Is support for the :is() and :where() selectors enabled? // Is support for the :is() and :where() selectors enabled?
pref("layout.css.is-where-pseudo.enabled", true); pref("layout.css.is-where-pseudo.enabled", true);