فهرست منبع

LibWeb: Implement :enabled and :disabled pseudo classes to spec

Previously we only considered an element disabled if it was an <input>
element with the disabled attribute, but there's way more elements that
apply with more nuanced disabled/enabled rules.
Luke Wilde 2 سال پیش
والد
کامیت
2133b7d58a

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

@@ -222,17 +222,13 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
     case CSS::Selector::SimpleSelector::PseudoClass::Type::Lang:
         return matches_lang_pseudo_class(element, pseudo_class.languages);
     case CSS::Selector::SimpleSelector::PseudoClass::Type::Disabled:
-        if (!is<HTML::HTMLInputElement>(element))
-            return false;
-        if (!element.has_attribute(HTML::AttributeNames::disabled))
-            return false;
-        return true;
+        // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-disabled
+        // The :disabled pseudo-class must match any element that is actually disabled.
+        return element.is_actually_disabled();
     case CSS::Selector::SimpleSelector::PseudoClass::Type::Enabled:
-        if (!is<HTML::HTMLInputElement>(element))
-            return false;
-        if (element.has_attribute(HTML::AttributeNames::disabled))
-            return false;
-        return true;
+        // https://html.spec.whatwg.org/multipage/semantics-other.html#selector-enabled
+        // The :enabled pseudo-class must match any button, input, select, textarea, optgroup, option, fieldset element, or form-associated custom element that is not actually disabled.
+        return !element.is_actually_disabled();
     case CSS::Selector::SimpleSelector::PseudoClass::Type::Checked:
         return matches_checked_pseudo_class(element);
     case CSS::Selector::SimpleSelector::PseudoClass::Type::Is:

+ 39 - 0
Userland/Libraries/LibWeb/DOM/Element.cpp

@@ -24,7 +24,15 @@
 #include <LibWeb/HTML/BrowsingContext.h>
 #include <LibWeb/HTML/EventLoop/EventLoop.h>
 #include <LibWeb/HTML/HTMLBodyElement.h>
+#include <LibWeb/HTML/HTMLButtonElement.h>
+#include <LibWeb/HTML/HTMLFieldSetElement.h>
+#include <LibWeb/HTML/HTMLFrameSetElement.h>
 #include <LibWeb/HTML/HTMLHtmlElement.h>
+#include <LibWeb/HTML/HTMLInputElement.h>
+#include <LibWeb/HTML/HTMLOptGroupElement.h>
+#include <LibWeb/HTML/HTMLOptionElement.h>
+#include <LibWeb/HTML/HTMLSelectElement.h>
+#include <LibWeb/HTML/HTMLTextAreaElement.h>
 #include <LibWeb/HTML/Parser/HTMLParser.h>
 #include <LibWeb/Layout/BlockContainer.h>
 #include <LibWeb/Layout/InitialContainingBlock.h>
@@ -728,6 +736,37 @@ void Element::serialize_pseudo_elements_as_json(JsonArraySerializer<StringBuilde
     }
 }
 
+// https://html.spec.whatwg.org/multipage/semantics-other.html#concept-element-disabled
+bool Element::is_actually_disabled() const
+{
+    // An element is said to be actually disabled if it is one of the following:
+    // - a button element that is disabled
+    // - an input element that is disabled
+    // - a select element that is disabled
+    // - a textarea element that is disabled
+    if (is<HTML::HTMLButtonElement>(this) || is<HTML::HTMLInputElement>(this) || is<HTML::HTMLSelectElement>(this) || is<HTML::HTMLTextAreaElement>(this)) {
+        auto const* form_associated_element = dynamic_cast<HTML::FormAssociatedElement const*>(this);
+        VERIFY(form_associated_element);
+
+        return !form_associated_element->enabled();
+    }
+
+    // - an optgroup element that has a disabled attribute
+    if (is<HTML::HTMLOptGroupElement>(this))
+        return has_attribute(HTML::AttributeNames::disabled);
+
+    // - an option element that is disabled
+    if (is<HTML::HTMLOptionElement>(this))
+        return static_cast<HTML::HTMLOptionElement const&>(*this).disabled();
+
+    // - a fieldset element that is a disabled fieldset
+    if (is<HTML::HTMLFieldSetElement>(this))
+        return static_cast<HTML::HTMLFieldSetElement const&>(*this).is_disabled();
+
+    // FIXME: - a form-associated custom element that is disabled
+    return false;
+}
+
 // https://w3c.github.io/DOM-Parsing/#dom-element-insertadjacenthtml
 WebIDL::ExceptionOr<void> Element::insert_adjacent_html(String position, String text)
 {

+ 2 - 0
Userland/Libraries/LibWeb/DOM/Element.h

@@ -142,6 +142,8 @@ public:
     void clear_pseudo_element_nodes(Badge<Layout::TreeBuilder>);
     void serialize_pseudo_elements_as_json(JsonArraySerializer<StringBuilder>& children_array) const;
 
+    bool is_actually_disabled() const;
+
 protected:
     Element(Document&, DOM::QualifiedName);
     virtual void initialize(JS::Realm&) override;

+ 22 - 0
Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.cpp

@@ -5,6 +5,7 @@
  */
 
 #include <LibWeb/HTML/HTMLFieldSetElement.h>
+#include <LibWeb/HTML/HTMLLegendElement.h>
 #include <LibWeb/HTML/Window.h>
 
 namespace Web::HTML {
@@ -16,4 +17,25 @@ HTMLFieldSetElement::HTMLFieldSetElement(DOM::Document& document, DOM::Qualified
 }
 
 HTMLFieldSetElement::~HTMLFieldSetElement() = default;
+
+// https://html.spec.whatwg.org/multipage/form-elements.html#concept-fieldset-disabled
+bool HTMLFieldSetElement::is_disabled() const
+{
+    // A fieldset element is a disabled fieldset if it matches any of the following conditions:
+    // - Its disabled attribute is specified
+    if (has_attribute(AttributeNames::disabled))
+        return true;
+
+    // - It is a descendant of another fieldset element whose disabled attribute is specified, and is not a descendant of that fieldset element's first legend element child, if any.
+    for (auto* fieldset_ancestor = first_ancestor_of_type<HTMLFieldSetElement>(); fieldset_ancestor; fieldset_ancestor = fieldset_ancestor->first_ancestor_of_type<HTMLFieldSetElement>()) {
+        if (fieldset_ancestor->has_attribute(HTML::AttributeNames::disabled)) {
+            auto* first_legend_element_child = fieldset_ancestor->first_child_of_type<HTMLLegendElement>();
+            if (!first_legend_element_child || !is_descendant_of(*first_legend_element_child))
+                return true;
+        }
+    }
+
+    return false;
+}
+
 }

+ 2 - 0
Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.h

@@ -26,6 +26,8 @@ public:
         return fieldset;
     }
 
+    bool is_disabled() const;
+
     // ^FormAssociatedElement
     // https://html.spec.whatwg.org/multipage/forms.html#category-listed
     virtual bool is_listed() const override { return true; }

+ 9 - 0
Userland/Libraries/LibWeb/HTML/HTMLOptionElement.cpp

@@ -8,6 +8,7 @@
 #include <AK/StringBuilder.h>
 #include <LibWeb/DOM/Node.h>
 #include <LibWeb/DOM/Text.h>
+#include <LibWeb/HTML/HTMLOptGroupElement.h>
 #include <LibWeb/HTML/HTMLOptionElement.h>
 #include <LibWeb/HTML/HTMLScriptElement.h>
 #include <LibWeb/HTML/HTMLSelectElement.h>
@@ -156,4 +157,12 @@ void HTMLOptionElement::ask_for_a_reset()
     // FIXME: Implement this operation.
 }
 
+// https://html.spec.whatwg.org/multipage/form-elements.html#concept-option-disabled
+bool HTMLOptionElement::disabled() const
+{
+    // An option element is disabled if its disabled attribute is present or if it is a child of an optgroup element whose disabled attribute is present.
+    return has_attribute(AttributeNames::disabled)
+        || (parent() && is<HTMLOptGroupElement>(parent()) && static_cast<HTMLOptGroupElement const&>(*parent()).has_attribute(AttributeNames::disabled));
+}
+
 }

+ 2 - 0
Userland/Libraries/LibWeb/HTML/HTMLOptionElement.h

@@ -28,6 +28,8 @@ public:
 
     int index() const;
 
+    bool disabled() const;
+
 private:
     friend class Bindings::OptionConstructor;
     friend class HTMLSelectElement;