From db2444040331bd8fea220dc10fc3d91315afd797 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Fri, 27 Dec 2024 17:03:16 +0000 Subject: [PATCH] LibWeb: Allow keyboard input to alter email inputs Previously, the`HTMLInputElement.selectinStart` and `HTMLInputElement.selectionEnd` IDL setters, and the `setRangeText()` IDL method were used when updating an input's value on keyboard input. These methods can't be used for this purpose, since selection doesn't apply to email type inputs. Therefore, this change introduces internal-use only methods that don't check whether selection applies to the given input. --- .../LibWeb/HTML/FormAssociatedElement.cpp | 57 ++++++++++++------- Libraries/LibWeb/HTML/FormAssociatedElement.h | 13 +++-- Libraries/LibWeb/HTML/HTMLInputElement.idl | 8 +-- Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp | 8 +-- Libraries/LibWeb/HTML/HTMLTextAreaElement.idl | 4 +- .../LibWeb/Painting/PaintableFragment.cpp | 4 +- .../expected/HTMLInputElement-edit-value.txt | 6 ++ .../input/HTMLInputElement-edit-value.html | 23 ++++++++ 8 files changed, 83 insertions(+), 40 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/HTMLInputElement-edit-value.txt create mode 100644 Tests/LibWeb/Text/input/HTMLInputElement-edit-value.html diff --git a/Libraries/LibWeb/HTML/FormAssociatedElement.cpp b/Libraries/LibWeb/HTML/FormAssociatedElement.cpp index f5ab649075d..170e86204e0 100644 --- a/Libraries/LibWeb/HTML/FormAssociatedElement.cpp +++ b/Libraries/LibWeb/HTML/FormAssociatedElement.cpp @@ -252,7 +252,7 @@ WebIDL::ExceptionOr FormAssociatedTextControlElement::select() } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectionstart -Optional FormAssociatedTextControlElement::selection_start() const +Optional FormAssociatedTextControlElement::selection_start_binding() const { // 1. If this element is an input element, and selectionStart does not apply to this element, return null. auto const& html_element = form_associated_element_to_html_element(); @@ -273,8 +273,13 @@ Optional FormAssociatedTextControlElement::selection_start return m_selection_start < m_selection_end ? m_selection_start : m_selection_end; } +WebIDL::UnsignedLong FormAssociatedTextControlElement::selection_start() const +{ + return m_selection_start < m_selection_end ? m_selection_start : m_selection_end; +} + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#textFieldSelection:dom-textarea/input-selectionstart-2 -WebIDL::ExceptionOr FormAssociatedTextControlElement::set_selection_start(Optional const& value) +WebIDL::ExceptionOr FormAssociatedTextControlElement::set_selection_start_binding(Optional const& value) { // 1. If this element is an input element, and selectionStart does not apply to this element, // throw an "InvalidStateError" DOMException. @@ -299,7 +304,7 @@ WebIDL::ExceptionOr FormAssociatedTextControlElement::set_selection_start( } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectionend -Optional FormAssociatedTextControlElement::selection_end() const +Optional FormAssociatedTextControlElement::selection_end_binding() const { // 1. If this element is an input element, and selectionEnd does not apply to this element, return // null. @@ -321,8 +326,13 @@ Optional FormAssociatedTextControlElement::selection_end() return m_selection_start < m_selection_end ? m_selection_end : m_selection_start; } +WebIDL::UnsignedLong FormAssociatedTextControlElement::selection_end() const +{ + return m_selection_start < m_selection_end ? m_selection_end : m_selection_start; +} + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#textFieldSelection:dom-textarea/input-selectionend-3 -WebIDL::ExceptionOr FormAssociatedTextControlElement::set_selection_end(Optional const& value) +WebIDL::ExceptionOr FormAssociatedTextControlElement::set_selection_end_binding(Optional const& value) { // 1. If this element is an input element, and selectionEnd does not apply to this element, // throw an "InvalidStateError" DOMException. @@ -391,19 +401,28 @@ WebIDL::ExceptionOr FormAssociatedTextControlElement::set_selection_direct } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setrangetext -WebIDL::ExceptionOr FormAssociatedTextControlElement::set_range_text(String const& replacement) +WebIDL::ExceptionOr FormAssociatedTextControlElement::set_range_text_binding(String const& replacement) { - return set_range_text(replacement, m_selection_start, m_selection_end); + return set_range_text_binding(replacement, m_selection_start, m_selection_end); +} + +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setrangetext +WebIDL::ExceptionOr FormAssociatedTextControlElement::set_range_text_binding(String const& replacement, WebIDL::UnsignedLong start, WebIDL::UnsignedLong end, Bindings::SelectionMode selection_mode) +{ + auto& html_element = form_associated_element_to_html_element(); + + // 1. If this element is an input element, and setRangeText() does not apply to this element, + // throw an "InvalidStateError" DOMException. + if (is(html_element) && !static_cast(html_element).selection_or_range_applies()) + return WebIDL::InvalidStateError::create(html_element.realm(), "setRangeText does not apply to this input type"_string); + + return set_range_text(replacement, start, end, selection_mode); } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setrangetext WebIDL::ExceptionOr FormAssociatedTextControlElement::set_range_text(String const& replacement, WebIDL::UnsignedLong start, WebIDL::UnsignedLong end, Bindings::SelectionMode selection_mode) { - // 1. If this element is an input element, and setRangeText() does not apply to this element, - // throw an "InvalidStateError" DOMException. auto& html_element = form_associated_element_to_html_element(); - if (is(html_element) && !static_cast(html_element).selection_or_range_applies()) - return WebIDL::InvalidStateError::create(html_element.realm(), "setRangeText does not apply to this input type"_string); // 2. Set this element's dirty value flag to true. set_dirty_value_flag(true); @@ -603,10 +622,7 @@ void FormAssociatedTextControlElement::handle_insert(String const& data) } auto selection_start = this->selection_start(); auto selection_end = this->selection_end(); - if (!selection_start.has_value() || !selection_end.has_value()) { - return; - } - MUST(set_range_text(data_for_insertion, selection_start.value(), selection_end.value(), Bindings::SelectionMode::End)); + MUST(set_range_text(data_for_insertion, selection_start, selection_end, Bindings::SelectionMode::End)); text_node->invalidate_style(DOM::StyleInvalidationReason::EditingInsertion); did_edit_text_node(); @@ -619,22 +635,19 @@ void FormAssociatedTextControlElement::handle_delete(DeleteDirection direction) return; auto selection_start = this->selection_start(); auto selection_end = this->selection_end(); - if (!selection_start.has_value() || !selection_end.has_value()) { - return; - } if (selection_start == selection_end) { if (direction == DeleteDirection::Backward) { - if (selection_start.value() > 0) { - MUST(set_range_text(String {}, selection_start.value() - 1, selection_end.value(), Bindings::SelectionMode::End)); + if (selection_start > 0) { + MUST(set_range_text(String {}, selection_start - 1, selection_end, Bindings::SelectionMode::End)); } } else { - if (selection_start.value() < text_node->data().code_points().length()) { - MUST(set_range_text(String {}, selection_start.value(), selection_end.value() + 1, Bindings::SelectionMode::End)); + if (selection_start < text_node->data().code_points().length()) { + MUST(set_range_text(String {}, selection_start, selection_end + 1, Bindings::SelectionMode::End)); } } return; } - MUST(set_range_text(String {}, selection_start.value(), selection_end.value(), Bindings::SelectionMode::End)); + MUST(set_range_text(String {}, selection_start, selection_end, Bindings::SelectionMode::End)); } void FormAssociatedTextControlElement::handle_return_key() diff --git a/Libraries/LibWeb/HTML/FormAssociatedElement.h b/Libraries/LibWeb/HTML/FormAssociatedElement.h index 3e02ad553d4..7fee0ca378d 100644 --- a/Libraries/LibWeb/HTML/FormAssociatedElement.h +++ b/Libraries/LibWeb/HTML/FormAssociatedElement.h @@ -143,12 +143,14 @@ public: WebIDL::ExceptionOr select(); // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectionstart - Optional selection_start() const; - WebIDL::ExceptionOr set_selection_start(Optional const&); + Optional selection_start_binding() const; + WebIDL::ExceptionOr set_selection_start_binding(Optional const&); + WebIDL::UnsignedLong selection_start() const; // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectionend - Optional selection_end() const; - WebIDL::ExceptionOr set_selection_end(Optional const&); + Optional selection_end_binding() const; + WebIDL::ExceptionOr set_selection_end_binding(Optional const&); + WebIDL::UnsignedLong selection_end() const; // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectiondirection Optional selection_direction() const; @@ -157,7 +159,8 @@ public: SelectionDirection selection_direction_state() const { return m_selection_direction; } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setrangetext - WebIDL::ExceptionOr set_range_text(String const& replacement); + WebIDL::ExceptionOr set_range_text_binding(String const& replacement); + WebIDL::ExceptionOr set_range_text_binding(String const& replacement, WebIDL::UnsignedLong start, WebIDL::UnsignedLong end, Bindings::SelectionMode = Bindings::SelectionMode::Preserve); WebIDL::ExceptionOr set_range_text(String const& replacement, WebIDL::UnsignedLong start, WebIDL::UnsignedLong end, Bindings::SelectionMode = Bindings::SelectionMode::Preserve); // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setselectionrange diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.idl b/Libraries/LibWeb/HTML/HTMLInputElement.idl index 709d16c98d2..4e7b46af29f 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.idl +++ b/Libraries/LibWeb/HTML/HTMLInputElement.idl @@ -60,11 +60,11 @@ interface HTMLInputElement : HTMLElement { readonly attribute NodeList? labels; undefined select(); - attribute unsigned long? selectionStart; - attribute unsigned long? selectionEnd; + [ImplementedAs=selection_start_binding] attribute unsigned long? selectionStart; + [ImplementedAs=selection_end_binding] attribute unsigned long? selectionEnd; [ImplementedAs=selection_direction_binding] attribute DOMString? selectionDirection; - undefined setRangeText(DOMString replacement); - undefined setRangeText(DOMString replacement, unsigned long start, unsigned long end, optional SelectionMode selectionMode = "preserve"); + [ImplementedAs=set_range_text_binding] undefined setRangeText(DOMString replacement); + [ImplementedAs=set_range_text_binding] undefined setRangeText(DOMString replacement, unsigned long start, unsigned long end, optional SelectionMode selectionMode = "preserve"); undefined setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction); undefined showPicker(); diff --git a/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp b/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp index 1542c82a947..e601e02f19e 100644 --- a/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp @@ -328,22 +328,22 @@ WebIDL::ExceptionOr HTMLTextAreaElement::set_rows(WebIDL::UnsignedLong row WebIDL::UnsignedLong HTMLTextAreaElement::selection_start_binding() const { - return selection_start().value(); + return FormAssociatedTextControlElement::selection_start_binding().value(); } WebIDL::ExceptionOr HTMLTextAreaElement::set_selection_start_binding(WebIDL::UnsignedLong const& value) { - return set_selection_start(value); + return FormAssociatedTextControlElement::set_selection_start_binding(value); } WebIDL::UnsignedLong HTMLTextAreaElement::selection_end_binding() const { - return selection_end().value(); + return FormAssociatedTextControlElement::selection_end_binding().value(); } WebIDL::ExceptionOr HTMLTextAreaElement::set_selection_end_binding(WebIDL::UnsignedLong const& value) { - return set_selection_end(value); + return FormAssociatedTextControlElement::set_selection_end_binding(value); } String HTMLTextAreaElement::selection_direction_binding() const diff --git a/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl b/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl index b60f2544a02..6b8bb932749 100644 --- a/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl +++ b/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl @@ -38,7 +38,7 @@ interface HTMLTextAreaElement : HTMLElement { [ImplementedAs=selection_start_binding] attribute unsigned long selectionStart; [ImplementedAs=selection_end_binding] attribute unsigned long selectionEnd; [ImplementedAs=selection_direction_binding] attribute DOMString selectionDirection; - undefined setRangeText(DOMString replacement); - undefined setRangeText(DOMString replacement, unsigned long start, unsigned long end, optional SelectionMode selectionMode = "preserve"); + [ImplementedAs=set_range_text_binding] undefined setRangeText(DOMString replacement); + [ImplementedAs=set_range_text_binding] undefined setRangeText(DOMString replacement, unsigned long start, unsigned long end, optional SelectionMode selectionMode = "preserve"); undefined setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction); }; diff --git a/Libraries/LibWeb/Painting/PaintableFragment.cpp b/Libraries/LibWeb/Painting/PaintableFragment.cpp index 7e7fccf73d7..147776675c0 100644 --- a/Libraries/LibWeb/Painting/PaintableFragment.cpp +++ b/Libraries/LibWeb/Painting/PaintableFragment.cpp @@ -200,9 +200,7 @@ CSSPixelRect PaintableFragment::selection_rect() const } auto selection_start = text_control_element->selection_start(); auto selection_end = text_control_element->selection_end(); - if (!selection_start.has_value() || !selection_end.has_value()) - return {}; - return range_rect(selection_start.value(), selection_end.value()); + return range_rect(selection_start, selection_end); } auto selection = paintable().document().get_selection(); if (!selection) diff --git a/Tests/LibWeb/Text/expected/HTMLInputElement-edit-value.txt b/Tests/LibWeb/Text/expected/HTMLInputElement-edit-value.txt new file mode 100644 index 00000000000..e114373c9bd --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTMLInputElement-edit-value.txt @@ -0,0 +1,6 @@ +input[type=text] value: PASS +input[type=search] value: PASS +input[type=tel] value: PASS +input[type=url] value: PASS +input[type=email] value: PASS +input[type=password] value: PASS diff --git a/Tests/LibWeb/Text/input/HTMLInputElement-edit-value.html b/Tests/LibWeb/Text/input/HTMLInputElement-edit-value.html new file mode 100644 index 00000000000..64e5e5ad371 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTMLInputElement-edit-value.html @@ -0,0 +1,23 @@ + + +