diff --git a/Tests/LibWeb/Text/expected/HTML/form-implicit-submission.txt b/Tests/LibWeb/Text/expected/HTML/form-implicit-submission.txt new file mode 100644 index 0000000000..0a16d9b326 --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/form-implicit-submission.txt @@ -0,0 +1,28 @@ + wfh :^) PASS wfh :^) wfh :^) FAIL PASS FAIL wfh :^) FAIL FAIL PASS PASS wfh :^) wfh :^) PASS PASSwfh :^) FAIL wfh :^) FAIL wfh :^) wfh :^) FAIL wfh :^) wfh :^) defaultButton: click button=PASS +defaultButton: submit +defaultButton: handledEvent=true +defaultButtonAsInput: click button=PASS +defaultButtonAsInput: submit +defaultButtonAsInput: handledEvent=true +defaultButtonIsSecond: click button=PASS +defaultButtonIsSecond: submit +defaultButtonIsSecond: handledEvent=true +defaultButtonIsLast: click button=PASS +defaultButtonIsLast: submit +defaultButtonIsLast: handledEvent=true +defaultButtonIsBeforeForm: click button=PASS +defaultButtonIsBeforeForm: submit +defaultButtonIsBeforeForm: handledEvent=true +defaultButtonIsAfterForm: click button=PASS +defaultButtonIsAfterForm: submit +defaultButtonIsAfterForm: handledEvent=true +defaultButtonIsDynamicallyInserted: click button=PASS +defaultButtonIsDynamicallyInserted: submit +defaultButtonIsDynamicallyInserted: handledEvent=true +defaultButtonIsDisabled: handledEvent=false +noButton: submit +noButton: handledEvent=true +noDefaultButton: submit +noDefaultButton: handledEvent=true +excessiveBlockingElements1: handledEvent=false +excessiveBlockingElements2: handledEvent=false diff --git a/Tests/LibWeb/Text/input/HTML/form-implicit-submission.html b/Tests/LibWeb/Text/input/HTML/form-implicit-submission.html new file mode 100644 index 0000000000..90ab912e84 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/form-implicit-submission.html @@ -0,0 +1,94 @@ +
+ + +
+
+ + +
+
+ + + + +
+
+ + + + +
+ +
+ +
+
+ +
+ +
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+ + diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFormElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLFormElement.cpp index 71d1f815af..acb117e87b 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLFormElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLFormElement.cpp @@ -62,6 +62,34 @@ void HTMLFormElement::visit_edges(Cell::Visitor& visitor) visitor.visit(element); } +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#implicit-submission +WebIDL::ExceptionOr HTMLFormElement::implicitly_submit_form() +{ + // If the user agent supports letting the user submit a form implicitly (for example, on some platforms hitting the + // "enter" key while a text control is focused implicitly submits the form), then doing so for a form, whose default + // button has activation behavior and is not disabled, must cause the user agent to fire a click event at that + // default button. + if (auto* default_button = this->default_button()) { + auto& default_button_element = default_button->form_associated_element_to_html_element(); + + if (default_button_element.has_activation_behavior() && default_button->enabled()) + default_button_element.click(); + + return {}; + } + + // If the form has no submit button, then the implicit submission mechanism must perform the following steps: + + // 1. If the form has more than one field that blocks implicit submission, then return. + if (number_of_fields_blocking_implicit_submission() > 1) + return {}; + + // 2. Submit the form element from the form element itself with userInvolvement set to "activation". + TRY(submit_form(*this, { .user_involvement = UserNavigationInvolvement::Activation })); + + return {}; +} + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-form-submit WebIDL::ExceptionOr HTMLFormElement::submit_form(JS::NonnullGCPtr submitter, SubmitFormOptions options) { @@ -1012,4 +1040,66 @@ WebIDL::ExceptionOr HTMLFormElement::named_item_value(FlyString const return node; } +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#default-button +FormAssociatedElement* HTMLFormElement::default_button() +{ + // A form element's default button is the first submit button in tree order whose form owner is that form element. + FormAssociatedElement* default_button = nullptr; + + root().for_each_in_subtree([&](auto& node) { + auto* form_associated_element = dynamic_cast(&node); + if (!form_associated_element) + return IterationDecision::Continue; + + if (form_associated_element->form() == this && form_associated_element->is_submit_button()) { + default_button = form_associated_element; + return IterationDecision::Break; + } + + return IterationDecision::Continue; + }); + + return default_button; +} + +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#field-that-blocks-implicit-submission +size_t HTMLFormElement::number_of_fields_blocking_implicit_submission() const +{ + // For the purpose of the previous paragraph, an element is a field that blocks implicit submission of a form + // element if it is an input element whose form owner is that form element and whose type attribute is in one of + // the following states: Text, Search, Telephone, URL, Email, Password, Date, Month, Week, Time, + // Local Date and Time, Number. + size_t count = 0; + + for (auto element : m_associated_elements) { + if (!is(*element)) + continue; + + auto const& input = static_cast(*element); + using enum HTMLInputElement::TypeAttributeState; + + switch (input.type_state()) { + case Text: + case Search: + case Telephone: + case URL: + case Email: + case Password: + case Date: + case Month: + case Week: + case Time: + case LocalDateAndTime: + case Number: + ++count; + break; + + default: + break; + } + }; + + return count; +} + } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLFormElement.h b/Userland/Libraries/LibWeb/HTML/HTMLFormElement.h index 4bb43e72cc..736e3b0890 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLFormElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLFormElement.h @@ -59,6 +59,7 @@ public: UserNavigationInvolvement user_involvement = { UserNavigationInvolvement::None }; }; WebIDL::ExceptionOr submit_form(JS::NonnullGCPtr submitter, SubmitFormOptions); + WebIDL::ExceptionOr implicitly_submit_form(); void reset_form(); @@ -117,6 +118,9 @@ private: ErrorOr mail_as_body(AK::URL parsed_action, Vector entry_list, EncodingTypeAttributeState encoding_type, String encoding, JS::NonnullGCPtr target_navigable, Bindings::NavigationHistoryBehavior history_handling, UserNavigationInvolvement user_involvement); void plan_to_navigate_to(AK::URL url, Variant post_resource, JS::NonnullGCPtr target_navigable, Bindings::NavigationHistoryBehavior history_handling, UserNavigationInvolvement user_involvement); + FormAssociatedElement* default_button(); + size_t number_of_fields_blocking_implicit_submission() const; + bool m_firing_submission_events { false }; // https://html.spec.whatwg.org/multipage/forms.html#locked-for-reset diff --git a/Userland/Libraries/LibWeb/Page/EventHandler.cpp b/Userland/Libraries/LibWeb/Page/EventHandler.cpp index 738c08ef13..3aa382b6e6 100644 --- a/Userland/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Userland/Libraries/LibWeb/Page/EventHandler.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -816,10 +817,18 @@ bool EventHandler::handle_keydown(KeyCode key, u32 modifiers, u32 code_point) m_browsing_context->set_cursor_position(DOM::Position::create(realm, node, (unsigned)node.data().bytes().size())); return true; } - if (key == KeyCode::Key_Return && is(node.editable_text_node_owner())) { - auto& input_element = static_cast(*node.editable_text_node_owner()); - input_element.commit_pending_changes(); - return true; + if (key == KeyCode::Key_Return) { + if (is(node.editable_text_node_owner())) { + auto& input_element = static_cast(*node.editable_text_node_owner()); + + if (auto* form = input_element.form()) { + form->implicitly_submit_form().release_value_but_fixme_should_propagate_errors(); + return true; + } + + input_element.commit_pending_changes(); + return true; + } } // FIXME: Text editing shortcut keys (copy/paste etc.) should be handled here. if (!should_ignore_keydown_event(code_point, modifiers)) {