Browse Source

LibWeb: Implement the :is() selector

This lets us finally get rid of a FIXME in the default style sheet. :^)
Sam Atkins 3 years ago
parent
commit
c148ed50bb

+ 2 - 9
Userland/Libraries/LibWeb/CSS/Default.css

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

+ 13 - 6
Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp

@@ -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();
-            } else if (pseudo_function.name().equals_ignoring_case("nth-child")) {
+                simple_selector.pseudo_class.argument_selector_list = not_selector.release_value();
+            } 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;

+ 5 - 2
Userland/Libraries/LibWeb/CSS/Selector.cpp

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

+ 4 - 1
Userland/Libraries/LibWeb/CSS/Selector.h

@@ -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:

+ 7 - 1
Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp

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

+ 6 - 2
Userland/Libraries/LibWeb/Dump.cpp

@@ -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)