ソースを参照

LibWeb: Use Selectors instead of a String for :not() selectors

Rather than parsing the selector every time we want to check it, we
now parse it once at the beginning.

A bonus effect of this is that we now support a selector list in
:not(), instead of just a single selector, though only when using
the new parser.
Sam Atkins 4 年 前
コミット
ffc81cbfad

+ 20 - 0
Base/res/html/misc/not-selector.html

@@ -0,0 +1,20 @@
+<html>
+<head>
+<title>:only-child test</title>
+<style>
+div {
+    background: yellow;
+}
+
+div:not(div div) {
+    background: lime;
+}
+</style>
+</head>
+<body>
+  <div>I am not a descendant and should be green.</div>
+  <div>
+    <div>I am a descendant and should be yellow.</div>
+  </div>
+</body>
+</html>

+ 1 - 0
Base/res/html/misc/welcome.html

@@ -109,6 +109,7 @@
         <li><a href="nth-last-child.html">:nth-last-child</a></li>
         <li><a href="nth-last-child.html">:nth-last-child</a></li>
         <li><a href="empty.html">:empty</a></li>
         <li><a href="empty.html">:empty</a></li>
         <li><a href="root.html">:root</a></li>
         <li><a href="root.html">:root</a></li>
+        <li><a href="not-selector.html">:not</a></li>
         <li><a href="form.html">form</a></li>
         <li><a href="form.html">form</a></li>
         <li><a href="borders.html">borders</a></li>
         <li><a href="borders.html">borders</a></li>
         <li><a href="css.html">css</a></li>
         <li><a href="css.html">css</a></li>

+ 5 - 1
Userland/Libraries/LibWeb/CSS/Parser/DeprecatedCSSParser.cpp

@@ -606,7 +606,11 @@ public:
                 pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Checked;
                 pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Checked;
             } else if (pseudo_name.starts_with("not", CaseSensitivity::CaseInsensitive)) {
             } else if (pseudo_name.starts_with("not", CaseSensitivity::CaseInsensitive)) {
                 pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Not;
                 pseudo_class.type = CSS::Selector::SimpleSelector::PseudoClass::Type::Not;
-                pseudo_class.not_selector = capture_selector_args(pseudo_name);
+                auto not_selector = Web::parse_selector(m_context, capture_selector_args(pseudo_name));
+                if (not_selector) {
+                    pseudo_class.not_selector.clear();
+                    pseudo_class.not_selector.append(not_selector.release_nonnull());
+                }
             } else {
             } else {
                 dbgln("Unknown pseudo class: '{}'", pseudo_name);
                 dbgln("Unknown pseudo class: '{}'", pseudo_name);
                 return {};
                 return {};

+ 2 - 1
Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp

@@ -469,7 +469,8 @@ RefPtr<Selector> Parser::parse_single_selector(TokenStream<T>& tokens, bool is_r
                     }
                     }
                 } else if (pseudo_function.name().equals_ignoring_case("not")) {
                 } else if (pseudo_function.name().equals_ignoring_case("not")) {
                     pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Not;
                     pseudo_class.type = Selector::SimpleSelector::PseudoClass::Type::Not;
-                    pseudo_class.not_selector = pseudo_function.values_as_string();
+                    auto function_token_stream = TokenStream(pseudo_function.values());
+                    pseudo_class.not_selector = parse_a_selector(function_token_stream);
                 } else {
                 } else {
                     dbgln("Unknown pseudo class: '{}'()", pseudo_function.name());
                     dbgln("Unknown pseudo class: '{}'()", pseudo_function.name());
                     return {};
                     return {};

+ 0 - 5
Userland/Libraries/LibWeb/CSS/Parser/StyleFunctionRule.h

@@ -24,11 +24,6 @@ public:
 
 
     String const& name() const { return m_name; }
     String const& name() const { return m_name; }
     Vector<StyleComponentValueRule> const& values() const { return m_values; }
     Vector<StyleComponentValueRule> const& values() const { return m_values; }
-    // FIXME: This method is a temporary hack while much of the parser still expects a string, rather than tokens.
-    String values_as_string() const
-    {
-        return "";
-    }
 
 
     String to_string() const;
     String to_string() const;
 
 

+ 2 - 2
Userland/Libraries/LibWeb/CSS/Selector.h

@@ -8,6 +8,7 @@
 #pragma once
 #pragma once
 
 
 #include <AK/FlyString.h>
 #include <AK/FlyString.h>
+#include <AK/NonnullRefPtrVector.h>
 #include <AK/RefCounted.h>
 #include <AK/RefCounted.h>
 #include <AK/String.h>
 #include <AK/String.h>
 #include <AK/Vector.h>
 #include <AK/Vector.h>
@@ -64,8 +65,7 @@ public:
             // Only used when "pseudo_class" is "NthChild" or "NthLastChild".
             // Only used when "pseudo_class" is "NthChild" or "NthLastChild".
             NthChildPattern nth_child_pattern;
             NthChildPattern nth_child_pattern;
 
 
-            // FIXME: This wants to be a Selector, rather than parsing it each time it is used.
-            String not_selector {};
+            NonnullRefPtrVector<Selector> not_selector {};
         };
         };
         PseudoClass pseudo_class;
         PseudoClass pseudo_class;
 
 

+ 6 - 9
Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp

@@ -113,15 +113,12 @@ static bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoClass cons
         if (!element.has_attribute("checked"))
         if (!element.has_attribute("checked"))
             return false;
             return false;
         return true;
         return true;
-    case CSS::Selector::SimpleSelector::PseudoClass::Type::Not: {
-        if (pseudo_class.not_selector.is_empty())
-            return false;
-        auto not_selector = Web::parse_selector(CSS::DeprecatedParsingContext(element), pseudo_class.not_selector);
-        if (!not_selector)
-            return false;
-        auto not_matches = matches(not_selector.release_nonnull(), element);
-        return !not_matches;
-    }
+    case CSS::Selector::SimpleSelector::PseudoClass::Type::Not:
+        for (auto& selector : pseudo_class.not_selector) {
+            if (matches(selector, element))
+                return false;
+        }
+        return true;
     case CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild:
     case CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild:
     case CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild:
     case CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild:
         auto const step_size = pseudo_class.nth_child_pattern.step_size;
         auto const step_size = pseudo_class.nth_child_pattern.step_size;

+ 4 - 1
Userland/Libraries/LibWeb/Dump.cpp

@@ -393,7 +393,10 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector)
 
 
                 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) {
-                    builder.appendff("({})", pseudo_class.not_selector);
+                    builder.append("(");
+                    for (auto& selector : pseudo_class.not_selector)
+                        dump_selector(builder, selector);
+                    builder.append(")");
                 } else if ((pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild)
                 } else if ((pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthChild)
                     || (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild)) {
                     || (pseudo_class.type == CSS::Selector::SimpleSelector::PseudoClass::Type::NthLastChild)) {
                     builder.appendff("(step={}, offset={})", pseudo_class.nth_child_pattern.step_size, pseudo_class.nth_child_pattern.offset);
                     builder.appendff("(step={}, offset={})", pseudo_class.nth_child_pattern.step_size, pseudo_class.nth_child_pattern.offset);