From 8be86514f04f333d3ef9338c8f40f60bb73d00f5 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Wed, 9 Jul 2025 06:44:26 +0800 Subject: [PATCH 1/9] Issue #2691: Implement ParseSupportsSelector --- layout/style/nsCSSParser.cpp | 107 +++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 2efce10dd9..8734ff1787 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -755,6 +755,7 @@ protected: bool ParseSupportsConditionTermsAfterOperator( bool& aConditionMet, SupportsConditionTermOperator aOperator); + bool ParseSupportsSelector(bool& aConditionMet); bool ParseCounterStyleRule(RuleAppendFunc aAppendFunc, void* aProcessData); bool ParseCounterStyleName(nsAString& aName, bool aForDefinition); @@ -4784,6 +4785,11 @@ CSSParserImpl::ParseSupportsConditionInParens(bool& aConditionMet) return ParseSupportsMozBoolPrefName(aConditionMet); } + if (mToken.mType == eCSSToken_Function && + mToken.mIdent.LowerCaseEqualsLiteral("selector")) { + return ParseSupportsSelector(aConditionMet); + } + if (mToken.mType == eCSSToken_Function || mToken.mType == eCSSToken_Bad_URL) { if (!SkipUntil(')')) { @@ -4844,6 +4850,105 @@ CSSParserImpl::ParseSupportsMozBoolPrefName(bool& aConditionMet) return true; } +// supports_selector +// : 'selector(' single_selector ')' +// ; +bool +CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) +{ + aConditionMet = false; + + nsAutoString selectorText; + bool foundClosingParen = false; + bool hasContent = false; + + + for (;;) { + if (!GetToken(true)) { + return false; // fail unexpected EOF + } + + if (mToken.IsSymbol(')')) { + foundClosingParen = true; + break; + } + + hasContent = true; + + bool needSpace = false; + if (!selectorText.IsEmpty()) { + char16_t lastChar = selectorText.Last(); + if (lastChar != ':' && lastChar != '.' && lastChar != '#' && lastChar != '[' && lastChar != '(') { + needSpace = true; + } + } + + if (mToken.mType == eCSSToken_Ident) { + if (needSpace) selectorText.Append(' '); + selectorText.Append(mToken.mIdent); + + } else if (mToken.mType == eCSSToken_AtKeyword) { + if (needSpace) selectorText.Append(' '); + selectorText.Append('@'); + selectorText.Append(mToken.mIdent); + + } else if (mToken.mType == eCSSToken_Hash) { + if (needSpace) selectorText.Append(' '); + selectorText.Append('#'); + selectorText.Append(mToken.mIdent); + + } else if (mToken.mType == eCSSToken_Symbol) { + selectorText.Append(mToken.mSymbol); + + } else if (mToken.mType == eCSSToken_String) { + if (needSpace) selectorText.Append(' '); + selectorText.Append('"'); + selectorText.Append(mToken.mIdent); + selectorText.Append('"'); + + } else if (mToken.mType == eCSSToken_Function) { + if (needSpace) selectorText.Append(' '); + selectorText.Append(mToken.mIdent); + selectorText.Append('('); + + } else { + if (!mToken.mIdent.IsEmpty()) { + if (needSpace) selectorText.Append(' '); + selectorText.Append(mToken.mIdent); + } + } + } + + if (!foundClosingParen) { + return false; + } + + if (!hasContent) { + aConditionMet = false; + return true; + } + + selectorText.CompressWhitespace(true, true); + + // basic validation + // reject issues like multiple selectors (comma-separated) + if (selectorText.FindChar(',') >= 0) { + aConditionMet = false; + } else if (selectorText.IsEmpty()) { + aConditionMet = false; + } else { + // assume basic selectors, pseudo-elements, and compound selectors to be true + aConditionMet = true; + + // special case: reject obviously unsupported pseudo-elements + if (selectorText.Find(NS_LITERAL_STRING("::-webkit-")) >= 0) { + aConditionMet = false; + } + } + + return true; +} + // supports_condition_in_parens_inside_parens // : core_declaration // | supports_condition_negation @@ -4912,6 +5017,8 @@ CSSParserImpl::ParseSupportsConditionInParensInsideParens(bool& aConditionMet) ParseSupportsConditionTerms(aConditionMet); } + + // supports_condition_terms // : S+ 'and' supports_condition_terms_after_operator('and') // | S+ 'or' supports_condition_terms_after_operator('or') From e9a719052923627ef154304a6d12538fcc860efd Mon Sep 17 00:00:00 2001 From: erixreyes Date: Thu, 10 Jul 2025 09:25:36 +0800 Subject: [PATCH 2/9] Issue #2691: Adjusted the function to use ParseSelectorGroup --- layout/style/nsCSSParser.cpp | 128 +++++++++++++++++++++++------------ 1 file changed, 83 insertions(+), 45 deletions(-) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 8734ff1787..fec3e77459 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -4862,8 +4862,7 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) bool foundClosingParen = false; bool hasContent = false; - - for (;;) { + while (true) { if (!GetToken(true)) { return false; // fail unexpected EOF } @@ -4883,38 +4882,47 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) } } - if (mToken.mType == eCSSToken_Ident) { - if (needSpace) selectorText.Append(' '); - selectorText.Append(mToken.mIdent); - - } else if (mToken.mType == eCSSToken_AtKeyword) { - if (needSpace) selectorText.Append(' '); - selectorText.Append('@'); - selectorText.Append(mToken.mIdent); - - } else if (mToken.mType == eCSSToken_Hash) { - if (needSpace) selectorText.Append(' '); - selectorText.Append('#'); - selectorText.Append(mToken.mIdent); - - } else if (mToken.mType == eCSSToken_Symbol) { - selectorText.Append(mToken.mSymbol); - - } else if (mToken.mType == eCSSToken_String) { - if (needSpace) selectorText.Append(' '); - selectorText.Append('"'); - selectorText.Append(mToken.mIdent); - selectorText.Append('"'); - - } else if (mToken.mType == eCSSToken_Function) { - if (needSpace) selectorText.Append(' '); - selectorText.Append(mToken.mIdent); - selectorText.Append('('); - - } else { - if (!mToken.mIdent.IsEmpty()) { + switch (mToken.mType) { + case eCSSToken_Ident: if (needSpace) selectorText.Append(' '); selectorText.Append(mToken.mIdent); + break; + + case eCSSToken_AtKeyword: + if (needSpace) selectorText.Append(' '); + selectorText.Append('@'); + selectorText.Append(mToken.mIdent); + break; + + case eCSSToken_Hash: + if (needSpace) selectorText.Append(' '); + selectorText.Append('#'); + selectorText.Append(mToken.mIdent); + break; + + case eCSSToken_Symbol: + selectorText.Append(mToken.mSymbol); + break; + + case eCSSToken_String: + if (needSpace) selectorText.Append(' '); + selectorText.Append('"'); + selectorText.Append(mToken.mIdent); + selectorText.Append('"'); + break; + + case eCSSToken_Function: + if (needSpace) selectorText.Append(' '); + selectorText.Append(mToken.mIdent); + selectorText.Append('('); + break; + + default: + if (!mToken.mIdent.IsEmpty()) { + if (needSpace) selectorText.Append(' '); + selectorText.Append(mToken.mIdent); + } + break; } } } @@ -4930,21 +4938,53 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) selectorText.CompressWhitespace(true, true); - // basic validation - // reject issues like multiple selectors (comma-separated) - if (selectorText.FindChar(',') >= 0) { + if (selectorText.IsEmpty()) { aConditionMet = false; - } else if (selectorText.IsEmpty()) { - aConditionMet = false; - } else { - // assume basic selectors, pseudo-elements, and compound selectors to be true - aConditionMet = true; - - // special case: reject obviously unsupported pseudo-elements - if (selectorText.Find(NS_LITERAL_STRING("::-webkit-")) >= 0) { + return true; + } + + int32_t parenDepth = 0; + for (uint32_t i = 0; i < selectorText.Length(); i++) { + char16_t c = selectorText.CharAt(i); + if (c == '(') { + parenDepth++; + } else if (c == ')') { + if (parenDepth > 0) { + parenDepth--; + } + } else if (c == ',' && parenDepth == 0) { // top-level comma found aConditionMet = false; + return true; } } + + if (selectorText.Find(NS_LITERAL_STRING("::-webkit-")) >= 0) { + aConditionMet = false; + return true; + } + + nsCSSScanner tempScanner(selectorText, 0); + css::ErrorReporter tempReporter(tempScanner, mSheet, mChildLoader, mSheetURI); + + nsCSSScanner* savedScanner = mScanner; + css::ErrorReporter* savedReporter = mReporter; + + mScanner = &tempScanner; + mReporter = &tempReporter; + + nsCSSSelectorList* selectorList = nullptr; + SelectorParsingFlags flags = SelectorParsingFlags::eNone; + bool parseSuccess = ParseSelectorGroup(selectorList, flags); + + mScanner = savedScanner; + mReporter = savedReporter; + + if (parseSuccess && selectorList) { + aConditionMet = true; + delete selectorList; + } else { + aConditionMet = false; + } return true; } @@ -5017,8 +5057,6 @@ CSSParserImpl::ParseSupportsConditionInParensInsideParens(bool& aConditionMet) ParseSupportsConditionTerms(aConditionMet); } - - // supports_condition_terms // : S+ 'and' supports_condition_terms_after_operator('and') // | S+ 'or' supports_condition_terms_after_operator('or') From fcb4b3d01f58eb26e6f4c9576f7b6d99406eb907 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Fri, 11 Jul 2025 10:28:28 +0800 Subject: [PATCH 3/9] Issue #2691: Add basic parentheses tracking to ParseSupportsSelector --- layout/style/nsCSSParser.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index fec3e77459..e8258aa55f 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -4861,6 +4861,7 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) nsAutoString selectorText; bool foundClosingParen = false; bool hasContent = false; + int32_t parenDepth = 0; while (true) { if (!GetToken(true)) { @@ -4868,11 +4869,18 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) } if (mToken.IsSymbol(')')) { - foundClosingParen = true; - break; - } - - hasContent = true; + if (parenDepth == 0) { + foundClosingParen = true; + break; + } else { + parenDepth--; + selectorText.Append(mToken.mSymbol); + } + } else if (mToken.IsSymbol('(')) { + parenDepth++; + selectorText.Append(mToken.mSymbol); + } else { + hasContent = true; bool needSpace = false; if (!selectorText.IsEmpty()) { @@ -4915,6 +4923,7 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) if (needSpace) selectorText.Append(' '); selectorText.Append(mToken.mIdent); selectorText.Append('('); + parenDepth++; break; default: @@ -4943,7 +4952,7 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) return true; } - int32_t parenDepth = 0; + parenDepth = 0; // reset parenDepth for comma checking for (uint32_t i = 0; i < selectorText.Length(); i++) { char16_t c = selectorText.CharAt(i); if (c == '(') { From 69556b7b1b84a19d6342c0a138a323ec6732e7c7 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Fri, 11 Jul 2025 10:34:37 +0800 Subject: [PATCH 4/9] Issue #2691: Improve token handling for selectors --- layout/style/nsCSSParser.cpp | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index e8258aa55f..e65ac502a4 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -13,6 +13,7 @@ #include // for std::stable_sort #include // for std::numeric_limits +#include // for std::floor #include "nsCSSParser.h" #include "nsAlgorithm.h" @@ -4902,8 +4903,12 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) selectorText.Append(mToken.mIdent); break; + case eCSSToken_ID: + selectorText.Append('#'); + selectorText.Append(mToken.mIdent); + break; + case eCSSToken_Hash: - if (needSpace) selectorText.Append(' '); selectorText.Append('#'); selectorText.Append(mToken.mIdent); break; @@ -4926,6 +4931,25 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) parenDepth++; break; + case eCSSToken_Number: + if (needSpace) selectorText.Append(' '); + if (mToken.mNumber == floor(mToken.mNumber)) { + selectorText.AppendInt(int32_t(mToken.mNumber)); + } else { + selectorText.AppendFloat(mToken.mNumber); + } + break; + + case eCSSToken_Dimension: + if (needSpace) selectorText.Append(' '); + if (mToken.mNumber == floor(mToken.mNumber)) { + selectorText.AppendInt(int32_t(mToken.mNumber)); + } else { + selectorText.AppendFloat(mToken.mNumber); + } + selectorText.Append(mToken.mIdent); + break; + default: if (!mToken.mIdent.IsEmpty()) { if (needSpace) selectorText.Append(' '); From aaae6484eee00f3b74a79c12dae35b24c2632321 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Fri, 11 Jul 2025 10:41:52 +0800 Subject: [PATCH 5/9] Issue #2691: Enhance spacing logic for selector formatting --- layout/style/nsCSSParser.cpp | 63 ++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index e65ac502a4..35477fddb5 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -4886,8 +4886,44 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) bool needSpace = false; if (!selectorText.IsEmpty()) { char16_t lastChar = selectorText.Last(); - if (lastChar != ':' && lastChar != '.' && lastChar != '#' && lastChar != '[' && lastChar != '(') { - needSpace = true; + + // Don't add spaces inside parentheses for nth-child formulas + if (parenDepth > 0) { + // Inside parentheses, only add space before "of" keyword + if (mToken.mType == eCSSToken_Ident && mToken.mIdent.LowerCaseEqualsLiteral("of")) { + needSpace = true; + } + // Also add space after "of" + else if (mToken.mType != eCSSToken_Ident && lastChar != '(' && lastChar != ' ' && + selectorText.Length() >= 2) { + // Check if previous token was "of" + nsAutoString lastTwo; + if (selectorText.Length() >= 2) { + lastTwo = Substring(selectorText, selectorText.Length() - 2, 2); + if (lastTwo.EqualsLiteral("of")) { + needSpace = true; + } + } + } + } else { + // Outside parentheses, use normal spacing rules + // Add space before identifiers and classes if last char was alphanumeric + if ((mToken.mType == eCSSToken_Ident || mToken.mType == eCSSToken_Symbol) && + lastChar != ' ' && lastChar != ':' && lastChar != '.' && lastChar != '#' && + lastChar != '[' && lastChar != '(' && lastChar != '>' && lastChar != '+' && + lastChar != '~' && lastChar != ',') { + // Special case: add space before '.' if it follows an identifier + if (mToken.mType == eCSSToken_Symbol && mToken.mSymbol == '.' && + ((lastChar >= 'a' && lastChar <= 'z') || (lastChar >= 'A' && lastChar <= 'Z'))) { + needSpace = true; + } + // Add space between consecutive identifiers + else if (mToken.mType == eCSSToken_Ident && + ((lastChar >= 'a' && lastChar <= 'z') || (lastChar >= 'A' && lastChar <= 'Z') || + (lastChar >= '0' && lastChar <= '9') || lastChar == ')')) { + needSpace = true; + } + } } } @@ -4914,7 +4950,28 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) break; case eCSSToken_Symbol: - selectorText.Append(mToken.mSymbol); + if ((mToken.mSymbol == '+' || mToken.mSymbol == '-') && parenDepth > 0) { + selectorText.Append(mToken.mSymbol); + } + // add spaces around combinators (only at top level) + else if ((mToken.mSymbol == '+' || mToken.mSymbol == '>' || mToken.mSymbol == '~') && parenDepth == 0) { + if (!selectorText.IsEmpty() && selectorText.Last() != ' ') { + selectorText.Append(' '); + } + selectorText.Append(mToken.mSymbol); + selectorText.Append(' '); + } + // add spaces around top-level combinators + else if (mToken.mSymbol == '.' && !selectorText.IsEmpty()) { + char16_t lastChar = selectorText.Last(); + if ((lastChar >= 'a' && lastChar <= 'z') || (lastChar >= 'A' && lastChar <= 'Z')) { + selectorText.Append(' '); + } + selectorText.Append(mToken.mSymbol); + } + else { + selectorText.Append(mToken.mSymbol); + } break; case eCSSToken_String: From 224d1724b2cecca097356a0adf9ed6b4bf7fbcf5 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Fri, 11 Jul 2025 10:46:20 +0800 Subject: [PATCH 6/9] Issue #2691: Add special handling for nth-child expressions --- layout/style/nsCSSParser.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 35477fddb5..7b75a00e27 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -13,7 +13,7 @@ #include // for std::stable_sort #include // for std::numeric_limits -#include // for std::floor +#include // for std::floor and std::abs #include "nsCSSParser.h" #include "nsAlgorithm.h" @@ -4989,11 +4989,24 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) break; case eCSSToken_Number: - if (needSpace) selectorText.Append(' '); - if (mToken.mNumber == floor(mToken.mNumber)) { - selectorText.AppendInt(int32_t(mToken.mNumber)); + if (mToken.mHasSign && parenDepth > 0) { + if (mToken.mNumber >= 0) { + selectorText.Append('+'); + } else { + selectorText.Append('-'); + } + if (std::abs(mToken.mNumber) == floor(std::abs(mToken.mNumber))) { + selectorText.AppendInt(int32_t(std::abs(mToken.mNumber))); + } else { + selectorText.AppendFloat(std::abs(mToken.mNumber)); + } } else { - selectorText.AppendFloat(mToken.mNumber); + if (needSpace) selectorText.Append(' '); + if (mToken.mNumber == floor(mToken.mNumber)) { + selectorText.AppendInt(int32_t(mToken.mNumber)); + } else { + selectorText.AppendFloat(mToken.mNumber); + } } break; From ea580ec54ba03fccef434a46aee18388aa1101a7 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Thu, 17 Jul 2025 12:44:53 +0800 Subject: [PATCH 7/9] Issue #2691: Improve Parser State Handling --- layout/style/nsCSSParser.cpp | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 7b75a00e27..548cfdf20b 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -5066,29 +5066,23 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) return true; } - nsCSSScanner tempScanner(selectorText, 0); - css::ErrorReporter tempReporter(tempScanner, mSheet, mChildLoader, mSheetURI); + // isolate parser instance to avoid state corruption + CSSParserImpl tempParser; + tempParser.SetStyleSheet(mSheet); + tempParser.SetChildLoader(mChildLoader); - nsCSSScanner* savedScanner = mScanner; - css::ErrorReporter* savedReporter = mReporter; - - mScanner = &tempScanner; - mReporter = &tempReporter; - - nsCSSSelectorList* selectorList = nullptr; + // check support SelectorParsingFlags flags = SelectorParsingFlags::eNone; - bool parseSuccess = ParseSelectorGroup(selectorList, flags); - - mScanner = savedScanner; - mReporter = savedReporter; - - if (parseSuccess && selectorList) { - aConditionMet = true; + nsCSSSelectorList* selectorList = nullptr; + bool supportSuccess = tempParser.ParseSelectorString(selectorText, mSheetURI, 0, &selectorList) == NS_OK; + + // clean up + if (selectorList) { delete selectorList; - } else { - aConditionMet = false; } - + + + aConditionMet = supportSuccess; return true; } From f2d61708309cc16e6317c13364548418a1066eb2 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Thu, 17 Jul 2025 17:10:50 +0800 Subject: [PATCH 8/9] Issue #2691: Refactored ParseSupportsSelector --- layout/style/nsCSSParser.cpp | 318 ++++++++++++++++++----------------- 1 file changed, 168 insertions(+), 150 deletions(-) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 548cfdf20b..75c08aaf0a 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -4851,6 +4851,166 @@ CSSParserImpl::ParseSupportsMozBoolPrefName(bool& aConditionMet) return true; } +/* + builds selector strings with proper formatting and spacing. + reconstructs selectors from tokens. +*/ +static void +AppendSelectorToken(const nsCSSToken& aToken, + nsAutoString& aSelectorText, + int32_t& aParenDepth, + bool& aHasContent) +{ + aHasContent = true; + + // space calculation depending on paren nesting + bool needSpace = false; + if (!aSelectorText.IsEmpty()) { + char16_t lastChar = aSelectorText.Last(); + + // nth-child formulas: no spaces inside paren + if (aParenDepth > 0) { + // add space before and after 'of' + if (aToken.mType == eCSSToken_Ident && aToken.mIdent.LowerCaseEqualsLiteral("of")) { + needSpace = true; + } + else if (aToken.mType != eCSSToken_Ident && lastChar != '(' && lastChar != ' ' && + aSelectorText.Length() >= 2) { + nsAutoString lastTwo; + if (aSelectorText.Length() >= 2) { + lastTwo = Substring(aSelectorText, aSelectorText.Length() - 2, 2); + if (lastTwo.EqualsLiteral("of")) { + needSpace = true; + } + } + } + } else { + // normal case: standard spacing rules (add space before + // identifiers/classes if last char is alphanumeric) + if ((aToken.mType == eCSSToken_Ident || aToken.mType == eCSSToken_Symbol) && + lastChar != ' ' && lastChar != ':' && lastChar != '.' && lastChar != '#' && + lastChar != '[' && lastChar != '(' && lastChar != '>' && lastChar != '+' && + lastChar != '~' && lastChar != ',') { + + // special case: if it follows an identifier, add space before '.' + if (aToken.mType == eCSSToken_Symbol && aToken.mSymbol == '.' && + ((lastChar >= 'a' && lastChar <= 'z') || (lastChar >= 'A' && lastChar <= 'Z'))) { + needSpace = true; + } + // add space between consecutive identifiers + else if (aToken.mType == eCSSToken_Ident && + ((lastChar >= 'a' && lastChar <= 'z') || (lastChar >= 'A' && lastChar <= 'Z') || + (lastChar >= '0' && lastChar <= '9') || lastChar == ')')) { + needSpace = true; + } + } + } + } + + // token reconstruction + switch (aToken.mType) { + case eCSSToken_Ident: + if (needSpace) aSelectorText.Append(' '); + aSelectorText.Append(aToken.mIdent); + break; + + case eCSSToken_AtKeyword: + if (needSpace) aSelectorText.Append(' '); + aSelectorText.Append('@'); + aSelectorText.Append(aToken.mIdent); + break; + + case eCSSToken_ID: + aSelectorText.Append('#'); + aSelectorText.Append(aToken.mIdent); + break; + + case eCSSToken_Hash: + aSelectorText.Append('#'); + aSelectorText.Append(aToken.mIdent); + break; + + case eCSSToken_Symbol: + if ((aToken.mSymbol == '+' || aToken.mSymbol == '-') && aParenDepth > 0) { + aSelectorText.Append(aToken.mSymbol); + } + + // add spaces around combinators (at top level) + else if ((aToken.mSymbol == '+' || aToken.mSymbol == '>' || aToken.mSymbol == '~') && aParenDepth == 0) { + if (!aSelectorText.IsEmpty() && aSelectorText.Last() != ' ') { + aSelectorText.Append(' '); + } + aSelectorText.Append(aToken.mSymbol); + aSelectorText.Append(' '); + } + + else if (aToken.mSymbol == '.' && !aSelectorText.IsEmpty()) { + char16_t lastChar = aSelectorText.Last(); + if ((lastChar >= 'a' && lastChar <= 'z') || (lastChar >= 'A' && lastChar <= 'Z')) { + aSelectorText.Append(' '); + } + aSelectorText.Append(aToken.mSymbol); + } + else { + aSelectorText.Append(aToken.mSymbol); + } + break; + + case eCSSToken_String: + if (needSpace) aSelectorText.Append(' '); + aSelectorText.Append('"'); + aSelectorText.Append(aToken.mIdent); + aSelectorText.Append('"'); + break; + + case eCSSToken_Function: + if (needSpace) aSelectorText.Append(' '); + aSelectorText.Append(aToken.mIdent); + aSelectorText.Append('('); + aParenDepth++; + break; + + case eCSSToken_Number: + if (aToken.mHasSign && aParenDepth > 0) { + if (aToken.mNumber >= 0) { + aSelectorText.Append('+'); + } else { + aSelectorText.Append('-'); + } + if (std::abs(aToken.mNumber) == floor(std::abs(aToken.mNumber))) { + aSelectorText.AppendInt(int32_t(std::abs(aToken.mNumber))); + } else { + aSelectorText.AppendFloat(std::abs(aToken.mNumber)); + } + } else { + if (needSpace) aSelectorText.Append(' '); + if (aToken.mNumber == floor(aToken.mNumber)) { + aSelectorText.AppendInt(int32_t(aToken.mNumber)); + } else { + aSelectorText.AppendFloat(aToken.mNumber); + } + } + break; + + case eCSSToken_Dimension: + if (needSpace) aSelectorText.Append(' '); + if (aToken.mNumber == floor(aToken.mNumber)) { + aSelectorText.AppendInt(int32_t(aToken.mNumber)); + } else { + aSelectorText.AppendFloat(aToken.mNumber); + } + aSelectorText.Append(aToken.mIdent); + break; + + default: + if (!aToken.mIdent.IsEmpty()) { + if (needSpace) aSelectorText.Append(' '); + aSelectorText.Append(aToken.mIdent); + } + break; + } +} + // supports_selector // : 'selector(' single_selector ')' // ; @@ -4864,9 +5024,10 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) bool hasContent = false; int32_t parenDepth = 0; + // fail unexpected EOF while (true) { if (!GetToken(true)) { - return false; // fail unexpected EOF + return false; } if (mToken.IsSymbol(')')) { @@ -4881,152 +5042,7 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) parenDepth++; selectorText.Append(mToken.mSymbol); } else { - hasContent = true; - - bool needSpace = false; - if (!selectorText.IsEmpty()) { - char16_t lastChar = selectorText.Last(); - - // Don't add spaces inside parentheses for nth-child formulas - if (parenDepth > 0) { - // Inside parentheses, only add space before "of" keyword - if (mToken.mType == eCSSToken_Ident && mToken.mIdent.LowerCaseEqualsLiteral("of")) { - needSpace = true; - } - // Also add space after "of" - else if (mToken.mType != eCSSToken_Ident && lastChar != '(' && lastChar != ' ' && - selectorText.Length() >= 2) { - // Check if previous token was "of" - nsAutoString lastTwo; - if (selectorText.Length() >= 2) { - lastTwo = Substring(selectorText, selectorText.Length() - 2, 2); - if (lastTwo.EqualsLiteral("of")) { - needSpace = true; - } - } - } - } else { - // Outside parentheses, use normal spacing rules - // Add space before identifiers and classes if last char was alphanumeric - if ((mToken.mType == eCSSToken_Ident || mToken.mType == eCSSToken_Symbol) && - lastChar != ' ' && lastChar != ':' && lastChar != '.' && lastChar != '#' && - lastChar != '[' && lastChar != '(' && lastChar != '>' && lastChar != '+' && - lastChar != '~' && lastChar != ',') { - // Special case: add space before '.' if it follows an identifier - if (mToken.mType == eCSSToken_Symbol && mToken.mSymbol == '.' && - ((lastChar >= 'a' && lastChar <= 'z') || (lastChar >= 'A' && lastChar <= 'Z'))) { - needSpace = true; - } - // Add space between consecutive identifiers - else if (mToken.mType == eCSSToken_Ident && - ((lastChar >= 'a' && lastChar <= 'z') || (lastChar >= 'A' && lastChar <= 'Z') || - (lastChar >= '0' && lastChar <= '9') || lastChar == ')')) { - needSpace = true; - } - } - } - } - - switch (mToken.mType) { - case eCSSToken_Ident: - if (needSpace) selectorText.Append(' '); - selectorText.Append(mToken.mIdent); - break; - - case eCSSToken_AtKeyword: - if (needSpace) selectorText.Append(' '); - selectorText.Append('@'); - selectorText.Append(mToken.mIdent); - break; - - case eCSSToken_ID: - selectorText.Append('#'); - selectorText.Append(mToken.mIdent); - break; - - case eCSSToken_Hash: - selectorText.Append('#'); - selectorText.Append(mToken.mIdent); - break; - - case eCSSToken_Symbol: - if ((mToken.mSymbol == '+' || mToken.mSymbol == '-') && parenDepth > 0) { - selectorText.Append(mToken.mSymbol); - } - // add spaces around combinators (only at top level) - else if ((mToken.mSymbol == '+' || mToken.mSymbol == '>' || mToken.mSymbol == '~') && parenDepth == 0) { - if (!selectorText.IsEmpty() && selectorText.Last() != ' ') { - selectorText.Append(' '); - } - selectorText.Append(mToken.mSymbol); - selectorText.Append(' '); - } - // add spaces around top-level combinators - else if (mToken.mSymbol == '.' && !selectorText.IsEmpty()) { - char16_t lastChar = selectorText.Last(); - if ((lastChar >= 'a' && lastChar <= 'z') || (lastChar >= 'A' && lastChar <= 'Z')) { - selectorText.Append(' '); - } - selectorText.Append(mToken.mSymbol); - } - else { - selectorText.Append(mToken.mSymbol); - } - break; - - case eCSSToken_String: - if (needSpace) selectorText.Append(' '); - selectorText.Append('"'); - selectorText.Append(mToken.mIdent); - selectorText.Append('"'); - break; - - case eCSSToken_Function: - if (needSpace) selectorText.Append(' '); - selectorText.Append(mToken.mIdent); - selectorText.Append('('); - parenDepth++; - break; - - case eCSSToken_Number: - if (mToken.mHasSign && parenDepth > 0) { - if (mToken.mNumber >= 0) { - selectorText.Append('+'); - } else { - selectorText.Append('-'); - } - if (std::abs(mToken.mNumber) == floor(std::abs(mToken.mNumber))) { - selectorText.AppendInt(int32_t(std::abs(mToken.mNumber))); - } else { - selectorText.AppendFloat(std::abs(mToken.mNumber)); - } - } else { - if (needSpace) selectorText.Append(' '); - if (mToken.mNumber == floor(mToken.mNumber)) { - selectorText.AppendInt(int32_t(mToken.mNumber)); - } else { - selectorText.AppendFloat(mToken.mNumber); - } - } - break; - - case eCSSToken_Dimension: - if (needSpace) selectorText.Append(' '); - if (mToken.mNumber == floor(mToken.mNumber)) { - selectorText.AppendInt(int32_t(mToken.mNumber)); - } else { - selectorText.AppendFloat(mToken.mNumber); - } - selectorText.Append(mToken.mIdent); - break; - - default: - if (!mToken.mIdent.IsEmpty()) { - if (needSpace) selectorText.Append(' '); - selectorText.Append(mToken.mIdent); - } - break; - } + AppendSelectorToken(mToken, selectorText, parenDepth, hasContent); } } @@ -5046,7 +5062,8 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) return true; } - parenDepth = 0; // reset parenDepth for comma checking + // reset parenDepth for comma checking + parenDepth = 0; for (uint32_t i = 0; i < selectorText.Length(); i++) { char16_t c = selectorText.CharAt(i); if (c == '(') { @@ -5055,12 +5072,14 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) if (parenDepth > 0) { parenDepth--; } - } else if (c == ',' && parenDepth == 0) { // top-level comma found + } else if (c == ',' && parenDepth == 0) { + // top-level comma found aConditionMet = false; return true; } } + // catch ::-webkit- pseudo-elements if (selectorText.Find(NS_LITERAL_STRING("::-webkit-")) >= 0) { aConditionMet = false; return true; @@ -5081,7 +5100,6 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) delete selectorList; } - aConditionMet = supportSuccess; return true; } From 906f05e606636a8277510b372d89782497d66bc5 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Fri, 18 Jul 2025 02:33:53 +0800 Subject: [PATCH 9/9] Issue #2691: Utilize regex for spacing rules --- layout/style/nsCSSParser.cpp | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 75c08aaf0a..cfa538c803 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -12,8 +12,9 @@ #include "mozilla/TypedEnumBits.h" #include // for std::stable_sort -#include // for std::numeric_limits -#include // for std::floor and std::abs +#include // for std::numeric_limits +#include // for std::floor and std::abs +#include // for std::regex and std::regex_match #include "nsCSSParser.h" #include "nsAlgorithm.h" @@ -4887,21 +4888,22 @@ AppendSelectorToken(const nsCSSToken& aToken, } else { // normal case: standard spacing rules (add space before // identifiers/classes if last char is alphanumeric) - if ((aToken.mType == eCSSToken_Ident || aToken.mType == eCSSToken_Symbol) && - lastChar != ' ' && lastChar != ':' && lastChar != '.' && lastChar != '#' && - lastChar != '[' && lastChar != '(' && lastChar != '>' && lastChar != '+' && - lastChar != '~' && lastChar != ',') { - - // special case: if it follows an identifier, add space before '.' - if (aToken.mType == eCSSToken_Symbol && aToken.mSymbol == '.' && - ((lastChar >= 'a' && lastChar <= 'z') || (lastChar >= 'A' && lastChar <= 'Z'))) { - needSpace = true; + if ((aToken.mType == eCSSToken_Ident || aToken.mType == eCSSToken_Symbol) && + std::string(" :.#[(>+~," ).find(lastChar) == std::string::npos) { + + static const std::regex letter_regex("[a-zA-Z]"); + static const std::regex ident_regex("[a-zA-Z0-9)]"); + std::string lastCharStr(1, lastChar); + + // special case: if it follows an identifier, add space before '.' + if (aToken.mType == eCSSToken_Symbol && aToken.mSymbol == '.' && + std::regex_match(lastCharStr, letter_regex)) { + needSpace = true; } // add space between consecutive identifiers - else if (aToken.mType == eCSSToken_Ident && - ((lastChar >= 'a' && lastChar <= 'z') || (lastChar >= 'A' && lastChar <= 'Z') || - (lastChar >= '0' && lastChar <= '9') || lastChar == ')')) { - needSpace = true; + else if (aToken.mType == eCSSToken_Ident && + std::regex_match(lastCharStr, ident_regex)) { + needSpace = true; } } } @@ -5024,8 +5026,9 @@ CSSParserImpl::ParseSupportsSelector(bool& aConditionMet) bool hasContent = false; int32_t parenDepth = 0; - // fail unexpected EOF + while (true) { + // fail unexpected EOF if (!GetToken(true)) { return false; }