From ee8095ec41b333901b419f4cb4ccaf080d924f57 Mon Sep 17 00:00:00 2001 From: Noah Bright Date: Wed, 25 Sep 2024 17:46:26 -0400 Subject: [PATCH] WebDriver: Implement helper functions for ElementClear endpoint Fill out some low hanging fruit within the endpoint itself, and (mostly) implement the helper functions clear a content editable element, clear a resettable element, element is a candidate for constraint validation, and element satisfies its constraints (just a stub). Some of these should probably be associated with Element and its subclasses There are several architecture issues that make it difficult/not expressive to implement these functions, like needing to track whether certain elements have ancestor nodes of a certain type. It might take a pretty close review to pick them all out :/ but they're all marked with FIXME --- .../Libraries/LibWeb/HTML/HTMLInputElement.h | 1 + .../LibWeb/HTML/HTMLTextAreaElement.h | 4 +- .../WebContent/WebDriverConnection.cpp | 207 +++++++++++++++--- 3 files changed, 179 insertions(+), 33 deletions(-) diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h index bc2ea4d7ad8d..1b6f8f831d04 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h @@ -82,6 +82,7 @@ class HTMLInputElement final WebIDL::ExceptionOr set_relevant_value(String const& value) override { return set_value(value); } virtual void set_dirty_value_flag(bool flag) override { m_dirty_value = flag; } + virtual void set_dirty_checkedness(bool flag) { m_dirty_checkedness = flag; } void commit_pending_changes(); diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h index ab0c300ff09c..314db3b6b820 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h @@ -122,6 +122,8 @@ class HTMLTextAreaElement final void set_dirty_value_flag(Badge, bool flag) { m_dirty_value = flag; } + void set_raw_value(String); + protected: void selection_was_changed(size_t selection_start, size_t selection_end) override; @@ -131,8 +133,6 @@ class HTMLTextAreaElement final virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; - void set_raw_value(String); - // ^DOM::Element virtual i32 default_tab_index_value() const override; diff --git a/Userland/Services/WebContent/WebDriverConnection.cpp b/Userland/Services/WebContent/WebDriverConnection.cpp index a6e7f1f6e4f4..b5da36d2869e 100644 --- a/Userland/Services/WebContent/WebDriverConnection.cpp +++ b/Userland/Services/WebContent/WebDriverConnection.cpp @@ -36,7 +36,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -46,6 +48,7 @@ #include #include #include +#include #include namespace WebContent { @@ -1447,58 +1450,199 @@ Messages::WebDriverClient::ElementClickResponse WebDriverConnection::element_cli return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::UnsupportedOperation, "Click not implemented"sv); } +// https://w3c.github.io/webdriver/#dfn-clear-algorithm +static void invoke_the_clear_algorithm_for_element(Web::DOM::Element* element) +{ + using namespace Web::HTML; + // Some resettable elements define their own clear algorithm. Unlike their associated reset algorithms, + // changes made to form controls as part of these algorithms do count as changes caused by the user + // (and thus, e.g. do cause input events to fire). When the clear algorithm is invoked for an element + // that does not define its own clear algorithm, its reset algorithm must be invoked instead. + + // The clear algorithm for input elements is to + if (is(*element)) { + auto& input = static_cast(*element); + // set the dirty value flag and dirty checkedness flag back to false, + input.set_dirty_value_flag(false); + input.set_dirty_checkedness(false); + // set the value of the element to an empty string, + (void)input.set_value({}); + // set the checkedness of the element to true if the element has a checked content attribute and false if it does not, + // FIXME: check the if^ + input.set_checked(true); + // empty the list of selected files, + input.set_files({}); + + // and then invoke the value sanitization algorithm iff the type attribute's current state defines one. + // FIXME: this is currently private + // it could make sense to define a public overload taking no parameters so we can + // simply "invoke the value sanitization algorithm" + // input.value_sanitization_algorithm({}); + return; + } + + // The clear algorithm for textarea elements is to set the dirty value flag back to false, + // and set the raw value of element to an empty string. + if (is(*element)) { + auto& input = static_cast(*element); + input.set_dirty_value_flag(false); + input.set_raw_value({}); + return; + } + + // The clear algorithm for output elements is set the element's value mode flag to default and then + // to set the element's textContent IDL attribute to an empty string (thus clearing the element's child nodes). + if (is(*element)) { + auto& input = static_cast(*element); + // FIXME: Mode flag? + input.set_text_content({}); + return; + } + + // FIXME: From first paragraph in this function: + // "When the clear algorithm is invoked for an element + // that does not define its own clear algorithm, its reset algorithm must be invoked instead." + // Resettable elements listed here: https://html.spec.whatwg.org/#category-reset + // select and form-associated custom elements not accounted for above + + if (is(*element)) + verify_cast(*element).reset_algorithm(); + + // FIXME: https://html.spec.whatwg.org/#autonomous-custom-element + // autonomous elements don't seem to have much infrastructure yet + // at some point along the line want to check that element's + // m_custom_element_definition.m_form_associated == true + // but not clear if you'd want to do that from a generic DOM::Element +} + // 12.5.2 Element Clear, https://w3c.github.io/webdriver/#dfn-element-clear -Messages::WebDriverClient::ElementClearResponse WebDriverConnection::element_clear(String const&) +Messages::WebDriverClient::ElementClearResponse WebDriverConnection::element_clear(String const& element_id) { dbgln("FIXME: WebDriverConnection::element_clear()"); - // FIXME: 1. If element's innerHTML IDL attribute is an empty string do nothing and return. + // To clear a content editable element: + auto clear_a_content_editable_element = [](Web::DOM::Element* element) { + // 1. If element's innerHTML IDL attribute is an empty string do nothing and return. + if (element->inner_html().value() == "") + return; - // FIXME: 2. Run the focusing steps for element. + // 2. Run the focusing steps for element. + Web::HTML::run_focusing_steps(element); - // FIXME: 3. Set element's innerHTML IDL attribute to an empty string. + // 3. Set element's innerHTML IDL attribute to an empty string. + (void)element->set_inner_html(""sv); - // FIXME: 4. Run the unfocusing steps for the element. + // 4. Run the unfocusing steps for the element. + Web::HTML::run_unfocusing_steps(element); + }; // To clear a resettable element: + auto clear_a_resettable_element = [](Web::DOM::Element* element) { + // 1. Let empty be the result of the first matching condition: + bool empty {}; - // FIXME: 1. Let empty be the result of the first matching condition: - - { // -> 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. + if (is(*element)) { + + auto& input = static_cast(*element); + + if (input.type_state() == Web::HTML::HTMLInputElement::TypeAttributeState::FileUpload) + // True if the list of selected files has a length of 0, and false otherwise. + empty = (input.files()->length() == 0); + } // -> otherwise - // True if its value IDL attribute is an empty string, and false otherwise. - } + else + // True if its value IDL attribute is an empty string, and false otherwise. + empty = (element->node_value() == ""); + + // FIXME: 2. If element is a candidate for constraint validation, it satisfies its constraints, and empty is true, abort these substeps. + // + // Candidate for constraint violation: + // https://html.spec.whatwg.org/#candidate-for-constraint-validation + // + // Candidate for constraint validation depends on element_is_barred_from_constraint_validation: + // https://html.spec.whatwg.org/#barred-from-constraint-validation + // conditions defined throughout this link ^ + // ctrl-f looks like the best way to search through them all + // + auto element_is_barred_from_constraint_validation = [&]() { + using namespace Web::HTML; + if (is(*element)) { + auto const& input = verify_cast(*element); + auto const type_state = input.type_state(); + + if (type_state == HTMLInputElement::TypeAttributeState::Hidden // If an input element's type attribute is in the Hidden state, it is barred from constraint validation. + || type_state == HTMLInputElement::TypeAttributeState::ResetButton // 4.10.5.1.20 Reset Button state (type=reset) : The element is barred from constraint validation. + || type_state == HTMLInputElement::TypeAttributeState::Button // 4.10.5.1.20 Reset Button state (type=reset): The element is barred from constraint validation. + // FIXME: If the readonly attribute is specified on an input element, the element is barred from constraint validation. + // FIXME: If an element has a datalist element ancestor... + // FIXME: If the readonly attribute is specified on a textarea element... + // FIXME: If an element is disabled... + // FIXME: If the readonly attribute is specified on a form-associated custom element... + ) + + return true; + } - // FIXME: 2. If element is a candidate for constraint validation it satisfies its constraints, and empty is true, abort these substeps. + return false; + }; - // FIXME: 3. Invoke the focusing steps for element. + // https://html.spec.whatwg.org/#concept-fv-valid + // FIXME: implement element_satisfies_its_constraints + auto element_satisfies_its_constraints = []() { return false; }; + + // https://html.spec.whatwg.org/#barred-from-constraint-validation + auto element_is_a_candidate_for_constraint_violation = [&]() { + // FIXME: multiple inheritance heirarchy here makes casting from an element ptr + // to a FormAssociatedElement, which defines is_submittable(), hard + if (is(*element)) { + // auto& input = verify_cast(*element); + return (/*input.is_submittable() &&*/ element_is_barred_from_constraint_validation()); + } + return false; + }; - // FIXME: 4. Invoke the clear algorithm for element. + if (element_is_a_candidate_for_constraint_violation() && element_satisfies_its_constraints() && empty) + return; - // FIXME: 5. Invoke the unfocusing steps for the element. + // 3. Invoke the focusing steps for element. + Web::HTML::run_focusing_steps(element); + + // 4. Invoke the clear algorithm for element. + invoke_the_clear_algorithm_for_element(element); + + // 5. Invoke the unfocusing steps for the element. + Web::HTML::run_unfocusing_steps(element); + }; // The remote end steps, given session, URL variables and parameters are: - // FIXME: 1. If session's current browsing context is no longer open, return error with error code no such window. + // 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()); - // FIXME: 2. Try to handle any user prompts with session. + // 2. Try to handle any user prompts with session. + TRY(handle_any_user_prompts()); - // FIXME: 3. Let element be the result of trying to get a known element with session and element id. + // 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)); // FIXME: 4. If element is not editable, return an error with error code invalid element state. + if (!element->is_editable()) { } - // FIXME: 5. Scroll into view the element. + // 5. Scroll into view the element. + (void)scroll_element_into_view(*element); - // FIXME: 6. Let timeout be session's session timeouts' implicit wait timeout. + // 6. Let timeout be session's session timeouts' implicit wait timeout. + auto timeout = m_timeouts_configuration.implicit_wait_timeout; // FIXME: 7. Let timer be a new timer. + // might want this to be a one shot + auto timer = Core::Timer::create(); - // FIXME: 8. If timeout is not null: - - { + // 8. If timeout is not null: + if (timeout != 0u) { // FIXME: 1. Start the timer with timer and timeout. + timer->start(); } // FIXME: 9. Wait for element to become interactable, or timer's timeout fired flag to be set, whichever occurs first. @@ -1509,16 +1653,17 @@ Messages::WebDriverClient::ElementClearResponse WebDriverConnection::element_cle { // -> element is a mutable form control element - - // Invoke the steps to clear a resettable element. - + if (true) + // Invoke the steps to clear a resettable element. + clear_a_resettable_element(element); // -> element is a mutable element - - // Invoke the steps to clear a content editable element. - + else if (false) + // Invoke the steps to clear a content editable element. + clear_a_content_editable_element(element); // -> otherwise - - // Return error with error code invalid element state. + else + // Return error with error code invalid element state. + return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidElementState, "element clear: element not matching statement"sv); } // FIXME: 12. Return success with data null.