diff --git a/Tests/LibWeb/Ref/css-lang-selector-ref.html b/Tests/LibWeb/Ref/css-lang-selector-ref.html
new file mode 100644
index 0000000000..a6ae6f7831
--- /dev/null
+++ b/Tests/LibWeb/Ref/css-lang-selector-ref.html
@@ -0,0 +1,16 @@
+
+
+
Red
+Blue
+Blue
+
diff --git a/Tests/LibWeb/Ref/css-lang-selector.html b/Tests/LibWeb/Ref/css-lang-selector.html
new file mode 100644
index 0000000000..0cdcfd9fc2
--- /dev/null
+++ b/Tests/LibWeb/Ref/css-lang-selector.html
@@ -0,0 +1,18 @@
+
+
+Red
+Blue
+Blue
+
diff --git a/Tests/LibWeb/Ref/manifest.json b/Tests/LibWeb/Ref/manifest.json
index e47c0298fd..c47b532e0d 100644
--- a/Tests/LibWeb/Ref/manifest.json
+++ b/Tests/LibWeb/Ref/manifest.json
@@ -2,5 +2,6 @@
"square-flex.html": "square-ref.html",
"separate-borders-inline-table.html": "separate-borders-ref.html",
"opacity-stacking.html": "opacity-stacking-ref.html",
- "css-gradient-currentcolor.html": "css-gradient-currentcolor-ref.html"
+ "css-gradient-currentcolor.html": "css-gradient-currentcolor-ref.html",
+ "css-lang-selector.html": "css-lang-selector-ref.html"
}
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
index f4b7460784..255109bbdb 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
@@ -664,9 +664,29 @@ Parser::ParseErrorOr Parser::parse_pseudo_simple_selec
};
}
case PseudoClassMetadata::ParameterType::LanguageRanges: {
- // FIXME: Support multiple, comma-separated, language ranges.
Vector languages;
- languages.append(pseudo_function.values().first().token().to_string().release_value_but_fixme_should_propagate_errors());
+ auto function_token_stream = TokenStream(pseudo_function.values());
+ auto language_token_lists = parse_a_comma_separated_list_of_component_values(function_token_stream);
+
+ for (auto language_token_list : language_token_lists) {
+ auto language_token_stream = TokenStream(language_token_list);
+ language_token_stream.skip_whitespace();
+ auto language_token = language_token_stream.next_token();
+ if (!(language_token.is(Token::Type::Ident) || language_token.is(Token::Type::String))) {
+ dbgln_if(CSS_PARSER_DEBUG, "Invalid language range in :{}() - not a string/ident", pseudo_function.name());
+ return ParseError::SyntaxError;
+ }
+
+ auto language_string = language_token.is(Token::Type::String) ? language_token.token().string() : language_token.token().ident();
+ languages.append(MUST(FlyString::from_utf8(language_string)));
+
+ language_token_stream.skip_whitespace();
+ if (language_token_stream.has_next_token()) {
+ dbgln_if(CSS_PARSER_DEBUG, "Invalid language range in :{}() - trailing tokens", pseudo_function.name());
+ return ParseError::SyntaxError;
+ }
+ }
+
return Selector::SimpleSelector {
.type = Selector::SimpleSelector::Type::PseudoClass,
.value = Selector::SimpleSelector::PseudoClassSelector {
diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
index f89f0dd64c..a18b3fe362 100644
--- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
+++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
@@ -44,13 +44,14 @@ static inline bool matches_lang_pseudo_class(DOM::Element const& element, Vector
// FIXME: This is ad-hoc. Implement a proper language range matching algorithm as recommended by BCP47.
for (auto const& language : languages) {
if (language.is_empty())
- return false;
+ continue;
if (language == "*"sv)
return true;
- if (!element_language.to_string().contains('-'))
- return Infra::is_ascii_case_insensitive_match(element_language, language);
+ if (!element_language.to_string().contains('-') && Infra::is_ascii_case_insensitive_match(element_language, language))
+ return true;
auto parts = element_language.to_string().split_limit('-', 2).release_value_but_fixme_should_propagate_errors();
- return Infra::is_ascii_case_insensitive_match(parts[0], language);
+ if (Infra::is_ascii_case_insensitive_match(parts[0], language))
+ return true;
}
return false;
}