diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index edf4fcee0f..7fa23e73ff 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -603,6 +603,14 @@ Result Parser::parse_simple_sel return ParsingResult::SyntaxError; } simple_selector.pseudo_class.argument_selector_list = not_selector.release_value(); + } else if (pseudo_function.name().equals_ignoring_case("lang"sv)) { + simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Lang; + if (pseudo_function.values().is_empty()) { + dbgln_if(CSS_PARSER_DEBUG, "Empty :lang() selector"); + return ParsingResult::SyntaxError; + } + // FIXME: Support multiple, comma-separated, language ranges. + simple_selector.pseudo_class.languages.append(pseudo_function.values().first().token().to_string()); } else if (pseudo_function.name().equals_ignoring_case("nth-child"sv)) { simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthChild; if (!parse_nth_child_pattern(simple_selector, pseudo_function, true)) diff --git a/Userland/Libraries/LibWeb/CSS/Selector.h b/Userland/Libraries/LibWeb/CSS/Selector.h index 773b37f8cc..7e4d8a5878 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.h +++ b/Userland/Libraries/LibWeb/CSS/Selector.h @@ -79,6 +79,7 @@ public: Not, Where, Active, + Lang, }; Type type { Type::None }; @@ -87,6 +88,9 @@ public: ANPlusBPattern nth_child_pattern; SelectorList argument_selector_list {}; + + // Used for :lang(en-gb,dk) + Vector languages; }; PseudoClass pseudo_class {}; PseudoElement pseudo_element { PseudoElement::None }; @@ -219,6 +223,8 @@ constexpr StringView pseudo_class_name(Selector::SimpleSelector::PseudoClass::Ty return "not"sv; case Selector::SimpleSelector::PseudoClass::Type::Where: return "where"sv; + case Selector::SimpleSelector::PseudoClass::Type::Lang: + return "lang"sv; case Selector::SimpleSelector::PseudoClass::Type::None: break; } diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp index 8d73df25b5..7bad2280a2 100644 --- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -16,6 +16,34 @@ namespace Web::SelectorEngine { +// https://drafts.csswg.org/selectors-4/#the-lang-pseudo +static inline bool matches_lang_pseudo_class(DOM::Element const& element, Vector const& languages) +{ + FlyString element_language; + for (auto const* e = &element; e; e = e->parent_element()) { + auto lang = e->attribute(HTML::AttributeNames::lang); + if (!lang.is_null()) { + element_language = lang; + break; + } + } + if (element_language.is_null()) + return false; + + // 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; + if (language == "*"sv) + return true; + if (!element_language.view().contains('-')) + return element_language.equals_ignoring_case(language); + auto parts = element_language.view().split_view('-'); + return parts[0].equals_ignoring_case(language); + } + return false; +} + static inline bool matches_hover_pseudo_class(DOM::Element const& element) { auto* hovered_node = element.document().hovered_node(); @@ -140,6 +168,8 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla return !next_sibling_with_same_tag_name(element); case CSS::Selector::SimpleSelector::PseudoClass::Type::OnlyOfType: return !previous_sibling_with_same_tag_name(element) && !next_sibling_with_same_tag_name(element); + case CSS::Selector::SimpleSelector::PseudoClass::Type::Lang: + return matches_lang_pseudo_class(element, pseudo_class.languages); case CSS::Selector::SimpleSelector::PseudoClass::Type::Disabled: if (!element.tag_name().equals_ignoring_case(HTML::TagNames::input)) return false; diff --git a/Userland/Libraries/LibWeb/Dump.cpp b/Userland/Libraries/LibWeb/Dump.cpp index 99ae818506..89b6126cbb 100644 --- a/Userland/Libraries/LibWeb/Dump.cpp +++ b/Userland/Libraries/LibWeb/Dump.cpp @@ -438,10 +438,17 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector) case CSS::Selector::SimpleSelector::PseudoClass::Type::Where: pseudo_class_description = "Where"; break; + case CSS::Selector::SimpleSelector::PseudoClass::Type::Lang: + pseudo_class_description = "Lang"; + break; } builder.appendff(" pseudo_class={}", pseudo_class_description); - if (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Not + if (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Lang) { + builder.append('('); + builder.join(',', pseudo_class.languages); + builder.append(')'); + } else if (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Not || pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Is || pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Where) { builder.append("([");