From fbae3b824a5f02e52c5ee0378aa53f5fcd4d47bc Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 12 Aug 2025 11:17:26 +0100 Subject: [PATCH] LibWeb/CSS: Move An+B matching code into ANPlusBPattern::matches() Not everything we want to match is an "nth element". Also moved its serialization function's implementation out of the header while I was at it. --- Libraries/LibWeb/CSS/Selector.cpp | 73 +++++++++++++++++++++++++ Libraries/LibWeb/CSS/Selector.h | 35 +----------- Libraries/LibWeb/CSS/SelectorEngine.cpp | 39 +------------ 3 files changed, 77 insertions(+), 70 deletions(-) diff --git a/Libraries/LibWeb/CSS/Selector.cpp b/Libraries/LibWeb/CSS/Selector.cpp index e8bc6b9e5c4..34be49f512c 100644 --- a/Libraries/LibWeb/CSS/Selector.cpp +++ b/Libraries/LibWeb/CSS/Selector.cpp @@ -823,4 +823,77 @@ SelectorList adapt_nested_relative_selector_list(SelectorList const& selectors) return new_list; } +// https://drafts.csswg.org/css-syntax-3/#anb-microsyntax +bool Selector::SimpleSelector::ANPlusBPattern::matches(int index) const +{ + // "If both a and b are equal to zero, the pseudo-class represents no element in the document tree." + if (step_size == 0 && offset == 0) + return false; + + // When "step_size == -1", selector represents first "offset" elements in document tree. + if (step_size == -1) + return !(offset <= 0 || index > offset); + + // When "step_size == 1", selector represents last "offset" elements in document tree. + if (step_size == 1) + return !(offset < 0 || index < offset); + + // When "step_size == 0", selector picks only the "offset" element. + if (step_size == 0) + return index == offset; + + // If both are negative, nothing can match. + if (step_size < 0 && offset < 0) + return false; + + // Like "a % b", but handles negative integers correctly. + auto const canonical_modulo = [](int a, int b) -> int { + int c = a % b; + if ((c < 0 && b > 0) || (c > 0 && b < 0)) { + c += b; + } + return c; + }; + + // When "step_size < 0", we start at "offset" and count backwards. + if (step_size < 0) + return index <= offset && canonical_modulo(index - offset, -step_size) == 0; + + // Otherwise, we start at "offset" and count forwards. + return index >= offset && canonical_modulo(index - offset, step_size) == 0; +} + +// https://drafts.csswg.org/css-syntax-3/#serializing-anb +String Selector::SimpleSelector::ANPlusBPattern::serialize() const +{ + // 1. If A is zero, return the serialization of B. + if (step_size == 0) + return String::number(offset); + + // 2. Otherwise, let result initially be an empty string. + StringBuilder result; + + // 3. + // - A is 1: Append "n" to result. + if (step_size == 1) + result.append('n'); + // - A is -1: Append "-n" to result. + else if (step_size == -1) + result.append("-n"sv); + // - A is non-zero: Serialize A and append it to result, then append "n" to result. + else if (step_size != 0) + result.appendff("{}n", step_size); + + // 4. + // - B is greater than zero: Append "+" to result, then append the serialization of B to result. + if (offset > 0) + result.appendff("+{}", offset); + // - B is less than zero: Append the serialization of B to result. + else if (offset < 0) + result.appendff("{}", offset); + + // 5. Return result. + return MUST(result.to_string()); +} + } diff --git a/Libraries/LibWeb/CSS/Selector.h b/Libraries/LibWeb/CSS/Selector.h index 8cbbf5573f7..1f6860f77c4 100644 --- a/Libraries/LibWeb/CSS/Selector.h +++ b/Libraries/LibWeb/CSS/Selector.h @@ -86,39 +86,8 @@ public: int step_size { 0 }; // "A" int offset = { 0 }; // "B" - // https://www.w3.org/TR/css-syntax-3/#serializing-anb - String serialize() const - { - // 1. If A is zero, return the serialization of B. - if (step_size == 0) { - return String::number(offset); - } - - // 2. Otherwise, let result initially be an empty string. - StringBuilder result; - - // 3. - // - A is 1: Append "n" to result. - if (step_size == 1) - result.append('n'); - // - A is -1: Append "-n" to result. - else if (step_size == -1) - result.append("-n"sv); - // - A is non-zero: Serialize A and append it to result, then append "n" to result. - else if (step_size != 0) - result.appendff("{}n", step_size); - - // 4. - // - B is greater than zero: Append "+" to result, then append the serialization of B to result. - if (offset > 0) - result.appendff("+{}", offset); - // - B is less than zero: Append the serialization of B to result. - if (offset < 0) - result.appendff("{}", offset); - - // 5. Return result. - return MUST(result.to_string()); - } + bool matches(int index) const; + String serialize() const; }; struct PseudoClassSelector { diff --git a/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Libraries/LibWeb/CSS/SelectorEngine.cpp index a642e1489be..d6d0fa9acc2 100644 --- a/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2018-2024, Andreas Kling - * Copyright (c) 2021-2024, Sam Atkins + * Copyright (c) 2021-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -671,10 +671,6 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla case CSS::PseudoClass::NthOfType: case CSS::PseudoClass::NthLastOfType: { auto& an_plus_b = pseudo_class.an_plus_b_patterns.first(); - auto const step_size = an_plus_b.step_size; - auto const offset = an_plus_b.offset; - if (step_size == 0 && offset == 0) - return false; // "If both a and b are equal to zero, the pseudo-class represents no element in the document tree." auto const* parent = element.parent(); if (!parent) @@ -730,38 +726,7 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla default: VERIFY_NOT_REACHED(); } - - // When "step_size == -1", selector represents first "offset" elements in document tree. - if (step_size == -1) - return !(offset <= 0 || index > offset); - - // When "step_size == 1", selector represents last "offset" elements in document tree. - if (step_size == 1) - return !(offset < 0 || index < offset); - - // When "step_size == 0", selector picks only the "offset" element. - if (step_size == 0) - return index == offset; - - // If both are negative, nothing can match. - if (step_size < 0 && offset < 0) - return false; - - // Like "a % b", but handles negative integers correctly. - auto const canonical_modulo = [](int a, int b) -> int { - int c = a % b; - if ((c < 0 && b > 0) || (c > 0 && b < 0)) { - c += b; - } - return c; - }; - - // When "step_size < 0", we start at "offset" and count backwards. - if (step_size < 0) - return index <= offset && canonical_modulo(index - offset, -step_size) == 0; - - // Otherwise, we start at "offset" and count forwards. - return index >= offset && canonical_modulo(index - offset, step_size) == 0; + return an_plus_b.matches(index); } case CSS::PseudoClass::Playing: { if (!is(element))