1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-23 20:57:41 +00:00

LibWeb: Implement the :is() selector

This lets us finally get rid of a FIXME in the default style sheet. :^)
This commit is contained in:
Sam Atkins 2022-03-17 15:28:42 +00:00 committed by Andreas Kling
parent 88d218721b
commit c148ed50bb
6 changed files with 37 additions and 21 deletions

View file

@ -213,18 +213,11 @@ ol {
list-style-type: decimal; list-style-type: decimal;
} }
/* FIXME: Implement these using :is() :^) */ :is(ul, ol) ul {
/* :is(ul, ol) ul */
ul ul,
ol ul {
list-style-type: circle; list-style-type: circle;
} }
/* :is(ul, ol) :is(ul, ol) ul */ :is(ul, ol) :is(ul, ol) ul {
ul ul ul,
ol ul ul,
ul ol ul,
ol ol ul {
list-style-type: square; list-style-type: square;
} }

View file

@ -584,7 +584,14 @@ Result<Selector::SimpleSelector, Parser::ParsingResult> Parser::parse_simple_sel
}; };
auto& pseudo_function = pseudo_class_token.function(); auto& pseudo_function = pseudo_class_token.function();
if (pseudo_function.name().equals_ignoring_case("not")) { if (pseudo_function.name().equals_ignoring_case("is"sv)) {
simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Is;
auto function_token_stream = TokenStream(pseudo_function.values());
auto is_selector = parse_a_selector_list(function_token_stream, SelectorParsingMode::Forgiving);
// NOTE: Because it's forgiving, even complete garbage will parse OK as an empty selector-list.
VERIFY(!is_selector.is_error());
simple_selector.pseudo_class.argument_selector_list = is_selector.release_value();
} else if (pseudo_function.name().equals_ignoring_case("not"sv)) {
simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Not; simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Not;
auto function_token_stream = TokenStream(pseudo_function.values()); auto function_token_stream = TokenStream(pseudo_function.values());
auto not_selector = parse_a_selector_list(function_token_stream); auto not_selector = parse_a_selector_list(function_token_stream);
@ -592,20 +599,20 @@ Result<Selector::SimpleSelector, Parser::ParsingResult> Parser::parse_simple_sel
dbgln_if(CSS_PARSER_DEBUG, "Invalid selector in :not() clause"); dbgln_if(CSS_PARSER_DEBUG, "Invalid selector in :not() clause");
return ParsingResult::SyntaxError; return ParsingResult::SyntaxError;
} }
simple_selector.pseudo_class.not_selector = not_selector.release_value(); simple_selector.pseudo_class.argument_selector_list = not_selector.release_value();
} else if (pseudo_function.name().equals_ignoring_case("nth-child")) { } else if (pseudo_function.name().equals_ignoring_case("nth-child"sv)) {
simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthChild; simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthChild;
if (!parse_nth_child_pattern(simple_selector, pseudo_function)) if (!parse_nth_child_pattern(simple_selector, pseudo_function))
return ParsingResult::SyntaxError; return ParsingResult::SyntaxError;
} else if (pseudo_function.name().equals_ignoring_case("nth-last-child")) { } else if (pseudo_function.name().equals_ignoring_case("nth-last-child"sv)) {
simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthLastChild; simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthLastChild;
if (!parse_nth_child_pattern(simple_selector, pseudo_function)) if (!parse_nth_child_pattern(simple_selector, pseudo_function))
return ParsingResult::SyntaxError; return ParsingResult::SyntaxError;
} else if (pseudo_function.name().equals_ignoring_case("nth-of-type")) { } else if (pseudo_function.name().equals_ignoring_case("nth-of-type"sv)) {
simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthOfType; simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthOfType;
if (!parse_nth_child_pattern(simple_selector, pseudo_function)) if (!parse_nth_child_pattern(simple_selector, pseudo_function))
return ParsingResult::SyntaxError; return ParsingResult::SyntaxError;
} else if (pseudo_function.name().equals_ignoring_case("nth-last-of-type")) { } else if (pseudo_function.name().equals_ignoring_case("nth-last-of-type"sv)) {
simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthLastOfType; simple_selector.pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::NthLastOfType;
if (!parse_nth_child_pattern(simple_selector, pseudo_function)) if (!parse_nth_child_pattern(simple_selector, pseudo_function))
return ParsingResult::SyntaxError; return ParsingResult::SyntaxError;

View file

@ -158,6 +158,7 @@ String Selector::SimpleSelector::serialize() const
case Selector::SimpleSelector::PseudoClass::Type::NthChild: case Selector::SimpleSelector::PseudoClass::Type::NthChild:
case Selector::SimpleSelector::PseudoClass::Type::NthLastChild: case Selector::SimpleSelector::PseudoClass::Type::NthLastChild:
case Selector::SimpleSelector::PseudoClass::Type::Not: case Selector::SimpleSelector::PseudoClass::Type::Not:
case Selector::SimpleSelector::PseudoClass::Type::Is:
// Otherwise, append ":" (U+003A), followed by the name of the pseudo-class, followed by "(" (U+0028), // Otherwise, append ":" (U+003A), followed by the name of the pseudo-class, followed by "(" (U+0028),
// followed by the value of the pseudo-class argument(s) determined as per below, followed by ")" (U+0029), to s. // followed by the value of the pseudo-class argument(s) determined as per below, followed by ")" (U+0029), to s.
s.append(':'); s.append(':');
@ -167,9 +168,11 @@ String Selector::SimpleSelector::serialize() const
|| pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::NthLastChild) { || pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::NthLastChild) {
// The result of serializing the value using the rules to serialize an <an+b> value. // The result of serializing the value using the rules to serialize an <an+b> value.
s.append(pseudo_class.nth_child_pattern.serialize()); s.append(pseudo_class.nth_child_pattern.serialize());
} else if (pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::Not) { } else if (pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::Not
|| pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::Is) {
// The result of serializing the value using the rules for serializing a group of selectors. // The result of serializing the value using the rules for serializing a group of selectors.
s.append(serialize_a_group_of_selectors(pseudo_class.not_selector)); // NOTE: `:is()` isn't in the spec for this yet, but it should be!
s.append(serialize_a_group_of_selectors(pseudo_class.argument_selector_list));
} }
s.append(')'); s.append(')');
break; break;

View file

@ -75,6 +75,7 @@ public:
Disabled, Disabled,
Enabled, Enabled,
Checked, Checked,
Is,
Not, Not,
Active, Active,
}; };
@ -84,7 +85,7 @@ public:
// Only used when "pseudo_class" is "NthChild" or "NthLastChild". // Only used when "pseudo_class" is "NthChild" or "NthLastChild".
ANPlusBPattern nth_child_pattern; ANPlusBPattern nth_child_pattern;
SelectorList not_selector {}; SelectorList argument_selector_list {};
}; };
PseudoClass pseudo_class {}; PseudoClass pseudo_class {};
PseudoElement pseudo_element { PseudoElement::None }; PseudoElement pseudo_element { PseudoElement::None };
@ -211,6 +212,8 @@ constexpr StringView pseudo_class_name(Selector::SimpleSelector::PseudoClass::Ty
return "nth-child"sv; return "nth-child"sv;
case Selector::SimpleSelector::PseudoClass::Type::NthLastChild: case Selector::SimpleSelector::PseudoClass::Type::NthLastChild:
return "nth-last-child"sv; return "nth-last-child"sv;
case Selector::SimpleSelector::PseudoClass::Type::Is:
return "is"sv;
case Selector::SimpleSelector::PseudoClass::Type::Not: case Selector::SimpleSelector::PseudoClass::Type::Not:
return "not"sv; return "not"sv;
case Selector::SimpleSelector::PseudoClass::Type::None: case Selector::SimpleSelector::PseudoClass::Type::None:

View file

@ -154,8 +154,14 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
return true; return true;
case CSS::Selector::SimpleSelector::PseudoClass::Type::Checked: case CSS::Selector::SimpleSelector::PseudoClass::Type::Checked:
return matches_checked_pseudo_class(element); return matches_checked_pseudo_class(element);
case CSS::Selector::SimpleSelector::PseudoClass::Type::Is:
for (auto& selector : pseudo_class.argument_selector_list) {
if (matches(selector, element))
return true;
}
return false;
case CSS::Selector::SimpleSelector::PseudoClass::Type::Not: case CSS::Selector::SimpleSelector::PseudoClass::Type::Not:
for (auto& selector : pseudo_class.not_selector) { for (auto& selector : pseudo_class.argument_selector_list) {
if (matches(selector, element)) if (matches(selector, element))
return false; return false;
} }

View file

@ -430,12 +430,16 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector)
case CSS::Selector::SimpleSelector::PseudoClass::Type::Not: case CSS::Selector::SimpleSelector::PseudoClass::Type::Not:
pseudo_class_description = "Not"; pseudo_class_description = "Not";
break; break;
case CSS::Selector::SimpleSelector::PseudoClass::Type::Is:
pseudo_class_description = "Is";
break;
} }
builder.appendff(" pseudo_class={}", pseudo_class_description); 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::Not
|| pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::Is) {
builder.append("(["); builder.append("([");
for (auto& selector : pseudo_class.not_selector) for (auto& selector : pseudo_class.argument_selector_list)
dump_selector(builder, selector); dump_selector(builder, selector);
builder.append("])"); builder.append("])");
} else if ((pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild) } else if ((pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild)