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; }