Prechádzať zdrojové kódy

LibHTML: Add adjacent (+) and general (~) sibling combinators

This patch implements two more selector features:

- "div + p" matches the <p> sibling immediately after a <div>.
- "div ~ p" matches all <p> siblings after a <div>.
Andreas Kling 5 rokov pred
rodič
commit
bedb00603c

+ 16 - 0
Base/home/anon/www/selectors.html

@@ -15,6 +15,11 @@ div > .boo > .bee {
     background-color: #000000;
     color: #ff00ff;
 }
+#gen_sib ~ div,
+#adj_sib + div {
+    background-color: #0000ff;
+    color: #ffffff;
+}
 </style>
 </head>
 <body>
@@ -45,5 +50,16 @@ div > .boo > .bee {
                 </div>
             </div>
         </div>
+    </div>
+    <div>
+        <div id="gen_sib">All the siblings of this &lt;div&gt; should be blue.</div>
+        <div>First sibling (should be blue)</div>
+        <div>Second sibling (should be blue)</div>
+    </div>
+    <div>
+        <div id="adj_sib">The first sibling of this &lt;div&gt; should be blue.</div>
+        <div>First sibling (should be blue)</div>
+        <div>Second sibling (should not be blue)</div>
+    </div>
 </body>
 </html>

+ 2 - 0
Libraries/LibHTML/CSS/Selector.h

@@ -19,6 +19,8 @@ public:
             None,
             ImmediateChild,
             Descendant,
+            AdjacentSibling,
+            GeneralSibling,
         };
         Relation relation { Relation::None };
 

+ 12 - 0
Libraries/LibHTML/CSS/StyleResolver.cpp

@@ -51,6 +51,18 @@ static bool matches(const Selector& selector, int component_index, const Element
         if (!element.parent() || !element.parent()->is_element())
             return false;
         return matches(selector, component_index - 1, static_cast<const Element&>(*element.parent()));
+    case Selector::Component::Relation::AdjacentSibling:
+        ASSERT(component_index != 0);
+        if (auto* sibling = element.previous_element_sibling())
+            return matches(selector, component_index - 1, *sibling);
+        return false;
+    case Selector::Component::Relation::GeneralSibling:
+        ASSERT(component_index != 0);
+        for (auto* sibling = element.previous_element_sibling(); sibling; sibling = sibling->previous_element_sibling()) {
+            if (matches(selector, component_index - 1, *sibling))
+                return true;
+        }
+        return false;
     }
     ASSERT_NOT_REACHED();
 }

+ 6 - 0
Libraries/LibHTML/Dump.cpp

@@ -158,6 +158,12 @@ void dump_rule(const StyleRule& rule)
             case Selector::Component::Relation::Descendant:
                 relation_description = "{Descendant}";
                 break;
+            case Selector::Component::Relation::AdjacentSibling:
+                relation_description = "{AdjacentSibling}";
+                break;
+            case Selector::Component::Relation::GeneralSibling:
+                relation_description = "{GeneralSibling}";
+                break;
             }
             dbgprintf("    %s:%s %s\n", type_description, component.value.characters(), relation_description);
         }

+ 17 - 2
Libraries/LibHTML/Parser/CSSParser.cpp

@@ -84,6 +84,11 @@ public:
         return isalnum(ch) || ch == '-' || ch == '_' || ch == '(' || ch == ')' || ch == '@';
     }
 
+    bool is_combinator(char ch) const
+    {
+        return ch == '~' || ch == '>' || ch == '+';
+    }
+
     Optional<Selector::Component> parse_selector_component()
     {
         consume_whitespace();
@@ -93,8 +98,18 @@ public:
         if (peek() == '{')
             return {};
 
-        if (peek() == '>') {
-            relation = Selector::Component::Relation::ImmediateChild;
+        if (is_combinator(peek())) {
+            switch (peek()) {
+            case '>':
+                relation = Selector::Component::Relation::ImmediateChild;
+                break;
+            case '+':
+                relation = Selector::Component::Relation::AdjacentSibling;
+                break;
+            case '~':
+                relation = Selector::Component::Relation::GeneralSibling;
+                break;
+            }
             consume_one();
             consume_whitespace();
         }