Browse Source

LibWeb+WebContent: Implement the Element Clear endpoint

Timothy Flynn 9 tháng trước cách đây
mục cha
commit
8598ed86fe

+ 97 - 0
Userland/Libraries/LibWeb/WebDriver/ElementReference.cpp

@@ -10,8 +10,13 @@
 #include <LibWeb/DOM/ShadowRoot.h>
 #include <LibWeb/Geometry/DOMRect.h>
 #include <LibWeb/Geometry/DOMRectList.h>
+#include <LibWeb/HTML/BrowsingContext.h>
 #include <LibWeb/HTML/HTMLBodyElement.h>
 #include <LibWeb/HTML/HTMLInputElement.h>
+#include <LibWeb/HTML/HTMLTextAreaElement.h>
+#include <LibWeb/HTML/TraversableNavigable.h>
+#include <LibWeb/Page/Page.h>
+#include <LibWeb/Painting/PaintableBox.h>
 #include <LibWeb/WebDriver/ElementReference.h>
 
 namespace Web::WebDriver {
@@ -125,6 +130,36 @@ bool is_element_stale(Web::DOM::Node const& element)
     return !element.document().is_active() || !element.is_connected();
 }
 
+// https://w3c.github.io/webdriver/#dfn-interactable
+bool is_element_interactable(Web::HTML::BrowsingContext const& browsing_context, Web::DOM::Element const& element)
+{
+    // An interactable element is an element which is either pointer-interactable or keyboard-interactable.
+    return is_element_keyboard_interactable(element) || is_element_pointer_interactable(browsing_context, element);
+}
+
+// https://w3c.github.io/webdriver/#dfn-pointer-interactable
+bool is_element_pointer_interactable(Web::HTML::BrowsingContext const& browsing_context, Web::DOM::Element const& element)
+{
+    // A pointer-interactable element is defined to be the first element, defined by the paint order found at the center
+    // point of its rectangle that is inside the viewport, excluding the size of any rendered scrollbars.
+    auto const* document = browsing_context.active_document();
+    if (!document)
+        return false;
+
+    auto const* paint_root = document->paintable_box();
+    if (!paint_root)
+        return false;
+
+    auto viewport = browsing_context.page().top_level_traversable()->viewport_rect();
+    auto center_point = in_view_center_point(element, viewport);
+
+    auto result = paint_root->hit_test(center_point, Painting::HitTestType::TextCursor);
+    if (!result.has_value())
+        return false;
+
+    return result->dom_node() == &element;
+}
+
 // https://w3c.github.io/webdriver/#dfn-keyboard-interactable
 bool is_element_keyboard_interactable(Web::DOM::Element const& element)
 {
@@ -132,6 +167,68 @@ bool is_element_keyboard_interactable(Web::DOM::Element const& element)
     return element.is_focusable() || is<HTML::HTMLBodyElement>(element) || element.is_document_element();
 }
 
+// https://w3c.github.io/webdriver/#dfn-editable
+bool is_element_editable(Web::DOM::Element const& element)
+{
+    // Editable elements are those that can be used for typing and clearing, and they fall into two subcategories:
+    // "Mutable form control elements" and "Mutable elements".
+    return is_element_mutable_form_control(element) || is_element_mutable(element);
+}
+
+// https://w3c.github.io/webdriver/#dfn-mutable-element
+bool is_element_mutable(Web::DOM::Element const& element)
+{
+    // Denotes elements that are editing hosts or content editable.
+    if (!is<HTML::HTMLElement>(element))
+        return false;
+
+    auto const& html_element = static_cast<HTML::HTMLElement const&>(element);
+    return html_element.is_editable();
+}
+
+// https://w3c.github.io/webdriver/#dfn-mutable-form-control-element
+bool is_element_mutable_form_control(Web::DOM::Element const& element)
+{
+    // Denotes input elements that are mutable (e.g. that are not read only or disabled) and whose type attribute is
+    // in one of the following states:
+    if (is<HTML::HTMLInputElement>(element)) {
+        auto const& input_element = static_cast<HTML::HTMLInputElement const&>(element);
+        if (!input_element.is_mutable() || !input_element.enabled())
+            return false;
+
+        // Text and Search, URL, Telephone, Email, Password, Date, Month, Week, Time, Local Date and Time, Number,
+        // Range, Color, File Upload
+        switch (input_element.type_state()) {
+        case HTML::HTMLInputElement::TypeAttributeState::Text:
+        case HTML::HTMLInputElement::TypeAttributeState::Search:
+        case HTML::HTMLInputElement::TypeAttributeState::URL:
+        case HTML::HTMLInputElement::TypeAttributeState::Telephone:
+        case HTML::HTMLInputElement::TypeAttributeState::Email:
+        case HTML::HTMLInputElement::TypeAttributeState::Password:
+        case HTML::HTMLInputElement::TypeAttributeState::Date:
+        case HTML::HTMLInputElement::TypeAttributeState::Month:
+        case HTML::HTMLInputElement::TypeAttributeState::Week:
+        case HTML::HTMLInputElement::TypeAttributeState::Time:
+        case HTML::HTMLInputElement::TypeAttributeState::LocalDateAndTime:
+        case HTML::HTMLInputElement::TypeAttributeState::Number:
+        case HTML::HTMLInputElement::TypeAttributeState::Range:
+        case HTML::HTMLInputElement::TypeAttributeState::Color:
+        case HTML::HTMLInputElement::TypeAttributeState::FileUpload:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    // And the textarea element.
+    if (is<HTML::HTMLTextAreaElement>(element)) {
+        auto const& text_area = static_cast<HTML::HTMLTextAreaElement const&>(element);
+        return text_area.enabled();
+    }
+
+    return false;
+}
+
 // https://w3c.github.io/webdriver/#dfn-non-typeable-form-control
 bool is_element_non_typeable_form_control(Web::DOM::Element const& element)
 {

+ 6 - 0
Userland/Libraries/LibWeb/WebDriver/ElementReference.h

@@ -25,7 +25,13 @@ ErrorOr<JS::NonnullGCPtr<Web::DOM::Element>, Web::WebDriver::Error> get_web_elem
 ErrorOr<Web::DOM::Element*, Web::WebDriver::Error> get_known_connected_element(StringView element_id);
 
 bool is_element_stale(Web::DOM::Node const& element);
+bool is_element_interactable(Web::HTML::BrowsingContext const&, Web::DOM::Element const&);
+bool is_element_pointer_interactable(Web::HTML::BrowsingContext const&, Web::DOM::Element const&);
 bool is_element_keyboard_interactable(Web::DOM::Element const&);
+
+bool is_element_editable(Web::DOM::Element const&);
+bool is_element_mutable(Web::DOM::Element const&);
+bool is_element_mutable_form_control(Web::DOM::Element const&);
 bool is_element_non_typeable_form_control(Web::DOM::Element const&);
 
 ByteString get_or_create_a_shadow_root_reference(Web::DOM::ShadowRoot const& shadow_root);

+ 85 - 45
Userland/Services/WebContent/WebDriverConnection.cpp

@@ -1475,40 +1475,75 @@ Messages::WebDriverClient::ElementClickResponse WebDriverConnection::element_cli
 // 12.5.2 Element Clear, https://w3c.github.io/webdriver/#dfn-element-clear
 Messages::WebDriverClient::ElementClearResponse WebDriverConnection::element_clear(String const& element_id)
 {
-    dbgln("FIXME: WebDriverConnection::element_clear({})", element_id);
+    // https://w3c.github.io/webdriver/#dfn-clear-a-content-editable-element
+    auto clear_content_editable_element = [&](Web::DOM::Element& element) {
+        // 1. If element's innerHTML IDL attribute is an empty string do nothing and return.
+        if (auto result = element.inner_html(); result.is_error() || result.value().is_empty())
+            return;
 
-    // To clear a content editable element:
-    {
-        // FIXME: 1. If element's innerHTML IDL attribute is an empty string do nothing and return.
-        // FIXME: 2. Run the focusing steps for element.
-        // FIXME: 3. Set element's innerHTML IDL attribute to an empty string.
-        // FIXME: 4. Run the unfocusing steps for the element.
-    }
+        // 2. Run the focusing steps for element.
+        Web::HTML::run_focusing_steps(&element);
 
-    // To clear a resettable element:
-    {
-        // FIXME: 1. Let empty be the result of the first matching condition:
-        {
+        // 3. Set element's innerHTML IDL attribute to an empty string.
+        (void)element.set_inner_html({});
+
+        // 4. Run the unfocusing steps for the element.
+        Web::HTML::run_unfocusing_steps(&element);
+    };
+
+    // https://w3c.github.io/webdriver/#dfn-clear-a-resettable-element
+    auto clear_resettable_element = [&](Web::DOM::Element& element) {
+        VERIFY(is<Web::HTML::FormAssociatedElement>(element));
+        auto& form_associated_element = dynamic_cast<Web::HTML::FormAssociatedElement&>(element);
+
+        // 1. Let empty be the result of the first matching condition:
+        auto empty = [&]() {
             // -> element is an input element whose type attribute is in the File Upload state
-            {
-                // True if the list of selected files has a length of 0, and false otherwise.
+            //    True if the list of selected files has a length of 0, and false otherwise
+            if (is<Web::HTML::HTMLInputElement>(element)) {
+                auto& input_element = static_cast<Web::HTML::HTMLInputElement&>(element);
+
+                if (input_element.type_state() == Web::HTML::HTMLInputElement::TypeAttributeState::FileUpload)
+                    return input_element.files()->length() == 0;
             }
+
             // -> otherwise
-            {
-                // True if its value IDL attribute is an empty string, and false otherwise.
-            }
-        }
-        // FIXME: 2. If element is a candidate for constraint validation it satisfies its constraints, and empty is true, abort these substeps.
-        // FIXME: 3. Invoke the focusing steps for element.
-        // FIXME: 4. Invoke the clear algorithm for element.
-        // FIXME: 5. Invoke the unfocusing steps for the element.
-    }
+            //    True if its value IDL attribute is an empty string, and false otherwise.
+            return form_associated_element.value().is_empty();
+        }();
+
+        // 2. If element is a candidate for constraint validation it satisfies its constraints, and empty is true,
+        //    abort these substeps.
+        // FIXME: Implement constraint validation.
+        if (empty)
+            return;
+
+        // 3. Invoke the focusing steps for element.
+        Web::HTML::run_focusing_steps(&element);
+
+        // 4. Invoke the clear algorithm for element.
+        form_associated_element.clear_algorithm();
+
+        // 5. Invoke the unfocusing steps for the element.
+        Web::HTML::run_unfocusing_steps(&element);
+    };
+
+    // 1. If session's current browsing context is no longer open, return error with error code no such window.
+    TRY(ensure_current_browsing_context_is_open());
+
+    // 2. Try to handle any user prompts with session.
+    TRY(handle_any_user_prompts());
+
+    // 3. Let element be the result of trying to get a known element with session and element id.
+    auto* element = TRY(Web::WebDriver::get_known_connected_element(element_id));
+
+    // 4. If element is not editable, return an error with error code invalid element state.
+    if (!Web::WebDriver::is_element_editable(*element))
+        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidElementState, "Element is not editable"sv);
+
+    // 5. Scroll into view the element.
+    TRY(scroll_element_into_view(*element));
 
-    // FIXME: 1. If session's current browsing context is no longer open, return error with error code no such window.
-    // FIXME: 2. Try to handle any user prompts with session.
-    // FIXME: 3. Let element be the result of trying to get a known element with session and element id.
-    // FIXME: 4. If element is not editable, return an error with error code invalid element state.
-    // FIXME: 5. Scroll into view the element.
     // FIXME: 6. Let timeout be session's session timeouts' implicit wait timeout.
     // FIXME: 7. Let timer be a new timer.
     // FIXME: 8. If timeout is not null:
@@ -1516,25 +1551,30 @@ Messages::WebDriverClient::ElementClearResponse WebDriverConnection::element_cle
         // FIXME: 1. Start the timer with timer and timeout.
     }
     // FIXME: 9. Wait for element to become interactable, or timer's timeout fired flag to be set, whichever occurs first.
-    // FIXME: 10. If element is not interactable, return error with error code element not interactable.
-    // FIXME: 11. Run the substeps of the first matching statement:
-    {
-        // -> element is a mutable form control element
-        {
-            // Invoke the steps to clear a resettable element.
-        }
-        // -> element is a mutable element
-        {
-            // Invoke the steps to clear a content editable element.
-        }
-        // -> otherwise
-        {
-            // Return error with error code invalid element state.
-        }
+
+    // 10. If element is not interactable, return error with error code element not interactable.
+    if (!Web::WebDriver::is_element_interactable(current_browsing_context(), *element))
+        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::ElementNotInteractable, "Element is not interactable"sv);
+
+    // 11. Run the substeps of the first matching statement:
+    // -> element is a mutable form control element
+    if (Web::WebDriver::is_element_mutable_form_control(*element)) {
+        // Invoke the steps to clear a resettable element.
+        clear_resettable_element(*element);
+    }
+    // -> element is a mutable element
+    else if (Web::WebDriver::is_element_mutable(*element)) {
+        // Invoke the steps to clear a content editable element.
+        clear_content_editable_element(*element);
+    }
+    // -> otherwise
+    else {
+        // Return error with error code invalid element state.
+        return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidElementState, "Element is not editable"sv);
     }
-    // FIXME: 12. Return success with data null.
 
-    return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnsupportedOperation, "element clear not implemented"sv);
+    // 12. Return success with data null.
+    return JsonValue {};
 }
 
 // 12.5.3 Element Send Keys, https://w3c.github.io/webdriver/#dfn-element-send-keys