diff --git a/layout/style/StyleRule.cpp b/layout/style/StyleRule.cpp index 8fe19bb34a..61d0d659e4 100644 --- a/layout/style/StyleRule.cpp +++ b/layout/style/StyleRule.cpp @@ -149,7 +149,7 @@ nsPseudoClassList::nsPseudoClassList(CSSPseudoClassType aType, mNext(nullptr) { NS_ASSERTION(nsCSSPseudoClasses::HasSelectorListArg(aType) || - nsCSSPseudoClasses::HasOptionalSelectorListArg(aType), + nsCSSPseudoClasses::HasOptionalSelectorListArg(aType), "unexpected pseudo-class"); NS_ASSERTION(aSelectorList, "selector list expected"); MOZ_COUNT_CTOR(nsPseudoClassList); diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 801e9356d5..d793f453ff 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -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 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 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 { diff --git a/layout/style/nsCSSPseudoClassList.h b/layout/style/nsCSSPseudoClassList.h index c57c28bc0b..740ebcc422 100644 --- a/layout/style/nsCSSPseudoClassList.h +++ b/layout/style/nsCSSPseudoClassList.h @@ -74,7 +74,11 @@ CSS_PSEUDO_CLASS(mozEmptyExceptChildrenWithLocalname, ":-moz-empty-except-childr CSS_PSEUDO_CLASS(lang, ":lang", 0, "") CSS_PSEUDO_CLASS(mozBoundElement, ":-moz-bound-element", 0, "") CSS_PSEUDO_CLASS(root, ":root", 0, "") -CSS_PSEUDO_CLASS(any, ":-moz-any", 0, "") +CSS_PSEUDO_CLASS(mozAny, ":-moz-any", 0, "") +CSS_PSEUDO_CLASS(is, ":is", 0, "layout.css.is-where-pseudo.enabled") +CSS_PSEUDO_CLASS(matches, ":matches", 0, "layout.css.is-where-pseudo.enabled") +CSS_PSEUDO_CLASS(any, ":any", 0, "layout.css.is-where-pseudo.enabled") +CSS_PSEUDO_CLASS(where, ":where", 0, "layout.css.is-where-pseudo.enabled") CSS_PSEUDO_CLASS(firstChild, ":first-child", 0, "") CSS_PSEUDO_CLASS(firstNode, ":-moz-first-node", 0, "") diff --git a/layout/style/nsCSSPseudoClasses.h b/layout/style/nsCSSPseudoClasses.h index 05f67b05cd..4a4bbe188c 100644 --- a/layout/style/nsCSSPseudoClasses.h +++ b/layout/style/nsCSSPseudoClasses.h @@ -58,10 +58,17 @@ public: static Type GetPseudoType(nsIAtom* aAtom, EnabledState aEnabledState); static bool HasStringArg(Type aType); static bool HasNthPairArg(Type aType); + static bool HasForgivingSelectorListArg(Type aType) { + return aType == Type::is || + aType == Type::matches || + aType == Type::any || + aType == Type::where; + } static bool HasSelectorListArg(Type aType) { - return aType == Type::any || - aType == Type::host || - aType == Type::hostContext; + return HasForgivingSelectorListArg(aType) || + aType == Type::mozAny || + aType == Type::host || + aType == Type::hostContext; } static bool HasOptionalSelectorListArg(Type aType) { return aType == Type::host; diff --git a/layout/style/nsCSSRuleProcessor.cpp b/layout/style/nsCSSRuleProcessor.cpp index dbd0e099b0..5b259ee991 100644 --- a/layout/style/nsCSSRuleProcessor.cpp +++ b/layout/style/nsCSSRuleProcessor.cpp @@ -1643,7 +1643,8 @@ StateSelectorMatches(Element* aElement, static bool AnySelectorInArgListMatches(Element* aElement, nsPseudoClassList* aList, NodeMatchContext& aNodeMatchContext, - TreeMatchContext& aTreeMatchContext); + TreeMatchContext& aTreeMatchContext, + bool aIsForgiving = false); static bool StateSelectorMatches(Element* aElement, @@ -1907,7 +1908,21 @@ static bool SelectorMatches(Element* aElement, } break; + case CSSPseudoClassType::is: + case CSSPseudoClassType::matches: case CSSPseudoClassType::any: + case CSSPseudoClassType::where: + { + if (!AnySelectorInArgListMatches(aElement, pseudoClass, + aNodeMatchContext, + aTreeMatchContext, + true)) { + return false; + } + } + break; + + case CSSPseudoClassType::mozAny: { if (!AnySelectorInArgListMatches(aElement, pseudoClass, aNodeMatchContext, @@ -2376,11 +2391,17 @@ static bool SelectorMatches(Element* aElement, static bool AnySelectorInArgListMatches(Element* aElement, nsPseudoClassList* aList, NodeMatchContext& aNodeMatchContext, - TreeMatchContext& aTreeMatchContext) + TreeMatchContext& aTreeMatchContext, + bool aIsForgiving) { nsCSSSelectorList *l; for (l = aList->u.mSelectors; l; l = l->mNext) { nsCSSSelector *s = l->mSelectors; + if (s == nullptr) { + MOZ_ASSERT(aIsForgiving, + "unexpected empty selector in unforgiving selector list"); + return false; + } MOZ_ASSERT(!s->mNext && !s->IsPseudoElement(), "parser failed"); if (SelectorMatches( @@ -3517,12 +3538,10 @@ AddSelector(RuleCascadeData* aCascade, } } - // Recur through any :-moz-any or :host-context selectors + // Recur through any pseudo-class that has a selector list argument. for (nsPseudoClassList* pseudoClass = negation->mPseudoClassList; pseudoClass; pseudoClass = pseudoClass->mNext) { - if (pseudoClass->mType == CSSPseudoClassType::any || - pseudoClass->mType == CSSPseudoClassType::host || - pseudoClass->mType == CSSPseudoClassType::hostContext) { + if (nsCSSPseudoClasses::HasSelectorListArg(pseudoClass->mType)) { for (nsCSSSelectorList *l = pseudoClass->u.mSelectors; l; l = l->mNext) { nsCSSSelector *s = l->mSelectors; if (!AddSelector(aCascade, aSelectorInTopLevel, s, diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 535ca1c93b..88ffb6ca7f 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2553,6 +2553,9 @@ pref("layout.css.prefixes.webkit", true); // pref is set to false.) pref("layout.css.prefixes.device-pixel-ratio-webkit", false); +// Is support for the :is() and :where() selectors enabled? +pref("layout.css.is-where-pseudo.enabled", true); + // Is support for the :scope selector enabled? pref("layout.css.scope-pseudo.enabled", true);