From 2133b7d58ae2b971c346d28f8c092d3d2e39ec4c Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 30 Sep 2022 16:21:34 +0100 Subject: [PATCH] LibWeb: Implement :enabled and :disabled pseudo classes to spec Previously we only considered an element disabled if it was an element with the disabled attribute, but there's way more elements that apply with more nuanced disabled/enabled rules. --- .../Libraries/LibWeb/CSS/SelectorEngine.cpp | 16 +++----- Userland/Libraries/LibWeb/DOM/Element.cpp | 39 +++++++++++++++++++ Userland/Libraries/LibWeb/DOM/Element.h | 2 + .../LibWeb/HTML/HTMLFieldSetElement.cpp | 22 +++++++++++ .../LibWeb/HTML/HTMLFieldSetElement.h | 2 + .../LibWeb/HTML/HTMLOptionElement.cpp | 9 +++++ .../Libraries/LibWeb/HTML/HTMLOptionElement.h | 2 + 7 files changed, 82 insertions(+), 10 deletions(-) diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp index 265b8df8aa..1752676817 100644 --- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/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(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(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: diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp index dd9461e574..b9d8a62d9c 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.cpp +++ b/Userland/Libraries/LibWeb/DOM/Element.cpp @@ -24,7 +24,15 @@ #include #include #include +#include +#include +#include #include +#include +#include +#include +#include +#include #include #include #include @@ -728,6 +736,37 @@ void Element::serialize_pseudo_elements_as_json(JsonArraySerializer(this) || is(this) || is(this) || is(this)) { + auto const* form_associated_element = dynamic_cast(this); + VERIFY(form_associated_element); + + return !form_associated_element->enabled(); + } + + // - an optgroup element that has a disabled attribute + if (is(this)) + return has_attribute(HTML::AttributeNames::disabled); + + // - an option element that is disabled + if (is(this)) + return static_cast(*this).disabled(); + + // - a fieldset element that is a disabled fieldset + if (is(this)) + return static_cast(*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 Element::insert_adjacent_html(String position, String text) { diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h index d7d77f2c4e..451dc3daea 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.h +++ b/Userland/Libraries/LibWeb/DOM/Element.h @@ -142,6 +142,8 @@ public: void clear_pseudo_element_nodes(Badge); void serialize_pseudo_elements_as_json(JsonArraySerializer& children_array) const; + bool is_actually_disabled() const; + protected: Element(Document&, DOM::QualifiedName); virtual void initialize(JS::Realm&) override; diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.cpp index 4090cb0c07..ece4176a8d 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.cpp @@ -5,6 +5,7 @@ */ #include +#include #include 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(); fieldset_ancestor; fieldset_ancestor = fieldset_ancestor->first_ancestor_of_type()) { + if (fieldset_ancestor->has_attribute(HTML::AttributeNames::disabled)) { + auto* first_legend_element_child = fieldset_ancestor->first_child_of_type(); + if (!first_legend_element_child || !is_descendant_of(*first_legend_element_child)) + return true; + } + } + + return false; +} + } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.h b/Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.h index 39f971cd65..0619f9350e 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLFieldSetElement.h +++ b/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; } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.cpp index 2c9ffd9594..56555d0cdb 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -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(parent()) && static_cast(*parent()).has_attribute(AttributeNames::disabled)); +} + } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.h b/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.h index 836ca42190..86eab9a942 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLOptionElement.h +++ b/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;