summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Glazman <daniel@glazman.org>2022-03-29 18:01:36 +0200
committerAndreas Kling <kling@serenityos.org>2022-03-29 18:53:20 +0200
commit91e1383b857c4a8bc55ec4e9c931eea217810cbe (patch)
treecd68dc976880d43db5a3b782fd5354b7cd512ee1
parent049d847230e034a485fa8f2e99c593ef5b4078bd (diff)
downloadserenity-91e1383b857c4a8bc55ec4e9c931eea217810cbe.zip
LibWeb: Implement attribute selector case identifier
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp25
-rw-r--r--Userland/Libraries/LibWeb/CSS/Selector.cpp15
-rw-r--r--Userland/Libraries/LibWeb/CSS/Selector.h6
-rw-r--r--Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp48
4 files changed, 82 insertions, 12 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
index fa52c3ad0b..1ead40652e 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
@@ -336,6 +336,7 @@ Result<Selector::SimpleSelector, Parser::ParsingResult> Parser::parse_attribute_
// correct with XML later, we'll need to keep the original case and then do
// a case-insensitive compare later.
.name = attribute_part.token().ident().to_lowercase_string(),
+ .case_type = Selector::SimpleSelector::Attribute::CaseType::DefaultMatch,
}
};
@@ -397,8 +398,30 @@ Result<Selector::SimpleSelector, Parser::ParsingResult> Parser::parse_attribute_
simple_selector.attribute().value = value_part.token().is(Token::Type::Ident) ? value_part.token().ident() : value_part.token().string();
attribute_tokens.skip_whitespace();
+ // Handle case-sensitivity suffixes. https://www.w3.org/TR/selectors-4/#attribute-case
+ if (attribute_tokens.has_next_token()) {
+ auto const& case_sensitivity_part = attribute_tokens.next_token();
+ if (case_sensitivity_part.is(Token::Type::Ident)) {
+ auto case_sensitivity = case_sensitivity_part.token().ident();
+ if (case_sensitivity.equals_ignoring_case("i")) {
+ simple_selector.attribute().case_type = Selector::SimpleSelector::Attribute::CaseType::CaseInsensitiveMatch;
+ } else if (case_sensitivity.equals_ignoring_case("s")) {
+ simple_selector.attribute().case_type = Selector::SimpleSelector::Attribute::CaseType::CaseSensitiveMatch;
+ } else {
+ dbgln_if(CSS_PARSER_DEBUG, "Expected a \"i\" or \"s\" attribute selector case sensitivity identifier, got: '{}'", case_sensitivity_part.to_debug_string());
+ return ParsingResult::SyntaxError;
+ }
+ } else {
+ dbgln_if(CSS_PARSER_DEBUG, "Expected an attribute selector case sensitivity identifier, got: '{}'", case_sensitivity_part.to_debug_string());
+ return ParsingResult::SyntaxError;
+ }
+ }
+
+ if (attribute_tokens.has_next_token()) {
+ dbgln_if(CSS_PARSER_DEBUG, "Was not expecting anything else inside attribute selector.");
+ return ParsingResult::SyntaxError;
+ }
- // FIXME: Handle case-sensitivity suffixes. https://www.w3.org/TR/selectors-4/#attribute-case
return simple_selector;
}
diff --git a/Userland/Libraries/LibWeb/CSS/Selector.cpp b/Userland/Libraries/LibWeb/CSS/Selector.cpp
index 98115e71c4..832267748c 100644
--- a/Userland/Libraries/LibWeb/CSS/Selector.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Selector.cpp
@@ -174,7 +174,20 @@ String Selector::SimpleSelector::serialize() const
serialize_a_string(s, attribute.value);
}
- // FIXME: 5. If the attribute selector has the case-sensitivity flag present, append " i" (U+0020 U+0069) to s.
+
+ // 5. If the attribute selector has the case-insensitivity flag present, append " i" (U+0020 U+0069) to s.
+ // If the attribute selector has the case-insensitivity flag present, append " s" (U+0020 U+0073) to s.
+ // (the line just above is an addition to CSS OM to match Selectors Level 4 last draft)
+ switch (attribute.case_type) {
+ case Selector::SimpleSelector::Attribute::CaseType::CaseInsensitiveMatch:
+ s.append(" i");
+ break;
+ case Selector::SimpleSelector::Attribute::CaseType::CaseSensitiveMatch:
+ s.append(" s");
+ break;
+ default:
+ break;
+ }
// 6. Append "]" (U+005D) to s.
s.append(']');
diff --git a/Userland/Libraries/LibWeb/CSS/Selector.h b/Userland/Libraries/LibWeb/CSS/Selector.h
index 14f65fd95e..1fc242e2da 100644
--- a/Userland/Libraries/LibWeb/CSS/Selector.h
+++ b/Userland/Libraries/LibWeb/CSS/Selector.h
@@ -100,9 +100,15 @@ public:
StartsWithString, // [att^=val]
EndsWithString, // [att$=val]
};
+ enum class CaseType {
+ DefaultMatch,
+ CaseSensitiveMatch,
+ CaseInsensitiveMatch,
+ };
MatchType match_type;
FlyString name {};
String value {};
+ CaseType case_type;
};
Type type;
diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
index 0801e0432f..4bfc3562d3 100644
--- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
+++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
@@ -78,17 +78,41 @@ static inline bool matches_checked_pseudo_class(DOM::Element const& element)
static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute const& attribute, DOM::Element const& element)
{
- switch (attribute.match_type) {
- case CSS::Selector::SimpleSelector::Attribute::MatchType::HasAttribute:
+ if (attribute.match_type == CSS::Selector::SimpleSelector::Attribute::MatchType::HasAttribute) {
+ // Early way out in case of an attribute existence selector.
return element.has_attribute(attribute.name);
+ }
+
+ auto const case_insensitive_match = (attribute.case_type == CSS::Selector::SimpleSelector::Attribute::CaseType::CaseInsensitiveMatch);
+ auto const case_sensitivity = case_insensitive_match
+ ? CaseSensitivity::CaseInsensitive
+ : CaseSensitivity::CaseSensitive;
+
+ switch (attribute.match_type) {
case CSS::Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch:
- return element.attribute(attribute.name) == attribute.value;
- case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsWord:
- return !attribute.value.is_empty()
- && element.attribute(attribute.name).split_view(' ').contains_slow(attribute.value);
+ return case_insensitive_match
+ ? element.attribute(attribute.name).equals_ignoring_case(attribute.value)
+ : element.attribute(attribute.name) == attribute.value;
+ case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsWord: {
+ if (attribute.value.is_empty()) {
+ // This selector is always false is match value is empty.
+ return false;
+ }
+ auto const view = element.attribute(attribute.name).split_view(' ');
+ auto const size = view.size();
+ for (size_t i = 0; i < size; ++i) {
+ auto const value = view.at(i);
+ if (case_insensitive_match
+ ? value.equals_ignoring_case(attribute.value)
+ : value == attribute.value) {
+ return true;
+ }
+ }
+ return false;
+ }
case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsString:
return !attribute.value.is_empty()
- && element.attribute(attribute.name).contains(attribute.value);
+ && element.attribute(attribute.name).contains(attribute.value, case_sensitivity);
case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithSegment: {
const auto element_attr_value = element.attribute(attribute.name);
if (element_attr_value.is_empty()) {
@@ -100,14 +124,18 @@ static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute co
return false;
}
auto segments = element_attr_value.split_view('-');
- return segments.first() == attribute.value;
+ return case_insensitive_match
+ ? segments.first().equals_ignoring_case(attribute.value)
+ : segments.first() == attribute.value;
}
case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithString:
return !attribute.value.is_empty()
- && element.attribute(attribute.name).starts_with(attribute.value);
+ && element.attribute(attribute.name).starts_with(attribute.value, case_sensitivity);
case CSS::Selector::SimpleSelector::Attribute::MatchType::EndsWithString:
return !attribute.value.is_empty()
- && element.attribute(attribute.name).ends_with(attribute.value);
+ && element.attribute(attribute.name).ends_with(attribute.value, case_sensitivity);
+ default:
+ break;
}
return false;