diff --git a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp index 7e4bf1f8b0ed4..52063b6088997 100644 --- a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp @@ -1,10 +1,12 @@ /* * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2024, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ #include +#include #include #include #include @@ -17,6 +19,17 @@ namespace Web::HTML { +static SelectionDirection string_to_selection_direction(Optional value) +{ + if (!value.has_value()) + return SelectionDirection::None; + if (value.value() == "forward"sv) + return SelectionDirection::Forward; + if (value.value() == "backward"sv) + return SelectionDirection::Backward; + return SelectionDirection::None; +} + void FormAssociatedElement::set_form(HTMLFormElement* form) { if (m_form) @@ -152,60 +165,242 @@ void FormAssociatedElement::reset_form_owner() } } +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value +String FormAssociatedElement::relevant_value() const +{ + auto& html_element = form_associated_element_to_html_element(); + if (is(html_element)) + return static_cast(html_element).value(); + if (is(html_element)) + return static_cast(html_element).api_value(); + ASSERT_NOT_REACHED(); +} + +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value +void FormAssociatedElement::relevant_value_was_changed(JS::GCPtr text_node) +{ + auto the_relevant_value = relevant_value(); + auto relevant_value_length = the_relevant_value.code_points().length(); + + // 1. If the element has a selection: + if (m_selection_start < m_selection_end) { + // 1. If the start of the selection is now past the end of the relevant value, set it to + // the end of the relevant value. + if (m_selection_start > relevant_value_length) + m_selection_start = relevant_value_length; + + // 2. If the end of the selection is now past the end of the relevant value, set it to the + // end of the relevant value. + if (m_selection_end > relevant_value_length) + m_selection_end = relevant_value_length; + + // 3. If the user agent does not support empty selection, and both the start and end of the + // selection are now pointing to the end of the relevant value, then instead set the + // element's text entry cursor position to the end of the relevant value, removing any + // selection. + // NOTE: We support empty selections. + return; + } + + // 2. Otherwise, the element must have a text entry cursor position position. If it is now past + // the end of the relevant value, set it to the end of the relevant value. + auto& document = form_associated_element_to_html_element().document(); + auto const current_cursor_position = document.cursor_position(); + if (current_cursor_position && text_node + && current_cursor_position->node() == text_node + && current_cursor_position->offset() > relevant_value_length) { + document.set_cursor_position(DOM::Position::create(document.realm(), *text_node, relevant_value_length)); + } +} + +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-select +WebIDL::ExceptionOr FormAssociatedElement::select() +{ + // 1. If this element is an input element, and either select() does not apply to this element + // or the corresponding control has no selectable text, return. + auto& html_element = form_associated_element_to_html_element(); + if (is(html_element)) { + auto& input_element = static_cast(html_element); + if (!input_element.select_applies() || input_element.value().is_empty()) + return {}; + } + + // 2. Set the selection range with 0 and infinity. + return set_selection_range(0, NumericLimits::max(), {}); +} + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectionstart -WebIDL::UnsignedLong FormAssociatedElement::selection_start() const +Optional FormAssociatedElement::selection_start() const { // 1. If this element is an input element, and selectionStart does not apply to this element, return null. - // NOTE: This is done by HTMLInputElement before calling this function + auto const& html_element = form_associated_element_to_html_element(); + if (is(html_element)) { + auto const& input_element = static_cast(html_element); + if (!input_element.selection_or_range_applies()) + return {}; + } // 2. If there is no selection, return the code unit offset within the relevant value to the character that // immediately follows the text entry cursor. - if (auto cursor = form_associated_element_to_html_element().document().cursor_position()) - return cursor->offset(); + if (m_selection_start == m_selection_end) { + if (auto cursor = form_associated_element_to_html_element().document().cursor_position()) + return cursor->offset(); + } - // FIXME: 3. Return the code unit offset within the relevant value to the character that immediately follows the start of - // the selection. - return 0; + // 3. Return the code unit offset within the relevant value to the character that immediately follows the start of + // the selection. + return m_selection_start; } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#textFieldSelection:dom-textarea/input-selectionstart-2 -WebIDL::ExceptionOr FormAssociatedElement::set_selection_start(Optional const&) +WebIDL::ExceptionOr FormAssociatedElement::set_selection_start(Optional const& value) { - // 1. If this element is an input element, and selectionStart does not apply to this element, throw an - // "InvalidStateError" DOMException. - // NOTE: This is done by HTMLInputElement before calling this function + // 1. If this element is an input element, and selectionStart does not apply to this element, + // throw an "InvalidStateError" DOMException. + auto& html_element = form_associated_element_to_html_element(); + if (is(html_element)) { + auto& input_element = static_cast(html_element); + if (!input_element.selection_or_range_applies()) + return WebIDL::InvalidStateError::create(html_element.realm(), "setSelectionStart does not apply to this input type"_fly_string); + } - // FIXME: 2. Let end be the value of this element's selectionEnd attribute. - // FIXME: 3. If end is less than the given value, set end to the given value. - // FIXME: 4. Set the selection range with the given value, end, and the value of this element's selectionDirection attribute. - return {}; + // 2. Let end be the value of this element's selectionEnd attribute. + auto end = m_selection_end; + + // 3. If end is less than the given value, set end to the given value. + if (value.has_value() && end < value.value()) + end = value.value(); + + // 4. Set the selection range with the given value, end, and the value of this element's + // selectionDirection attribute. + return set_selection_range(value, end, selection_direction()); } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectionend -WebIDL::UnsignedLong FormAssociatedElement::selection_end() const +Optional FormAssociatedElement::selection_end() const { - // 1. If this element is an input element, and selectionEnd does not apply to this element, return null. - // NOTE: This is done by HTMLInputElement before calling this function + // 1. If this element is an input element, and selectionEnd does not apply to this element, return + // null. + auto const& html_element = form_associated_element_to_html_element(); + if (is(html_element)) { + auto const& input_element = static_cast(html_element); + if (!input_element.selection_or_range_applies()) + return {}; + } - // 2. If there is no selection, return the code unit offset within the relevant value to the character that - // immediately follows the text entry cursor. - if (auto cursor = form_associated_element_to_html_element().document().cursor_position()) - return cursor->offset(); + // 2. If there is no selection, return the code unit offset within the relevant value to the + // character that immediately follows the text entry cursor. + if (m_selection_start == m_selection_end) { + if (auto cursor = form_associated_element_to_html_element().document().cursor_position()) + return cursor->offset(); + } - // FIXME: 3. Return the code unit offset within the relevant value to the character that immediately follows the end of - // the selection. - return 0; + // 3. Return the code unit offset within the relevant value to the character that immediately + // follows the end of the selection. + return m_selection_end; } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#textFieldSelection:dom-textarea/input-selectionend-3 -WebIDL::ExceptionOr FormAssociatedElement::set_selection_end(Optional const&) +WebIDL::ExceptionOr FormAssociatedElement::set_selection_end(Optional const& value) { - // 1. If this element is an input element, and selectionEnd does not apply to this element, throw an - // "InvalidStateError" DOMException. - // NOTE: This is done by HTMLInputElement before calling this function + // 1. If this element is an input element, and selectionEnd does not apply to this element, + // throw an "InvalidStateError" DOMException. + auto& html_element = form_associated_element_to_html_element(); + if (is(html_element)) { + auto& input_element = static_cast(html_element); + if (!input_element.selection_or_range_applies()) + return WebIDL::InvalidStateError::create(html_element.realm(), "setSelectionEnd does not apply to this input type"_fly_string); + } + + // 2. Set the selection range with the value of this element's selectionStart attribute, the + // given value, and the value of this element's selectionDirection attribute. + return set_selection_range(m_selection_start, value, selection_direction()); +} + +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#selection-direction +Optional FormAssociatedElement::selection_direction() const +{ + switch (m_selection_direction) { + case SelectionDirection::Forward: + return "forward"_string; + case SelectionDirection::Backward: + return "backward"_string; + case SelectionDirection::None: + return "none"_string; + default: + ASSERT_NOT_REACHED(); + } +} + +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#set-the-selection-direction +void FormAssociatedElement::set_selection_direction(Optional direction) +{ + // To set the selection direction of an element to a given direction, update the element's + // selection direction to the given direction, unless the direction is "none" and the + // platform does not support that direction; in that case, update the element's selection + // direction to "forward". + m_selection_direction = string_to_selection_direction(direction); +} + +WebIDL::ExceptionOr FormAssociatedElement::set_selection_range(Optional start, Optional end, Optional direction) +{ + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setselectionrange + + // 1. If this element is an input element, and setSelectionRange() 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(), "setSelectionRange does not apply to this input type"_fly_string); + + // 2. Set the selection range with start, end, and direction. + + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#set-the-selection-range + + // 1. If start is null, let start be zero. + start = start.value_or(0); + + // 2. If end is null, let end be zero. + end = end.value_or(0); + + // 3. Set the selection of the text control to the sequence of code units within the relevant + // value starting with the code unit at the startth position (in logical order) and ending + // with the code unit at the (end-1)th position. Arguments greater than the length of the + // relevant value of the text control (including the special value infinity) must be treated + // as pointing at the end of the text control. + auto the_relevant_value = relevant_value(); + auto relevant_value_length = the_relevant_value.code_points().length(); + auto new_selection_start = AK::min(start.value(), relevant_value_length); + auto new_selection_end = AK::min(end.value(), relevant_value_length); + + // If end is less than or equal to start then the start of the selection and the end of the + // selection must both be placed immediately before the character with offset end. In UAs + // where there is no concept of an empty selection, this must set the cursor to be just + // before the character with offset end. + new_selection_start = AK::min(new_selection_start, new_selection_end); + + bool was_modified = m_selection_start != new_selection_start || m_selection_end != new_selection_end; + m_selection_start = new_selection_start; + m_selection_end = new_selection_end; + + // 4. If direction is not identical to either "backward" or "forward", or if the direction + // argument was not given, set direction to "none". + auto new_direction = string_to_selection_direction(direction); + + // 5. Set the selection direction of the text control to direction. + was_modified |= m_selection_direction != new_direction; + m_selection_direction = new_direction; + + // 6. If the previous steps caused the selection of the text control to be modified (in either + // extent or direction), then queue an element task on the user interaction task source + // given the element to fire an event named select at the element, with the bubbles attribute + // initialized to true. + if (was_modified) { + html_element.queue_an_element_task(Task::Source::UserInteraction, [&html_element] { + auto select_event = DOM::Event::create(html_element.realm(), EventNames::select, { .bubbles = true }); + static_cast(&html_element)->dispatch_event(select_event); + }); + } - // FIXME: 2. Set the selection range with the value of this element's selectionStart attribute, the given value, and the - // value of this element's selectionDirection attribute. return {}; } diff --git a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h index 8a48047323427..b9dfb938b1573 100644 --- a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h +++ b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h @@ -1,11 +1,13 @@ /* * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2024, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once +#include #include #include #include @@ -49,6 +51,13 @@ private: form_associated_element_attribute_changed(name, value); \ } +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#selection-direction +enum class SelectionDirection { + Forward, + Backward, + None, +}; + class FormAssociatedElement { public: HTMLFormElement* form() { return m_form; } @@ -83,18 +92,33 @@ class FormAssociatedElement { virtual String value() const { return String {}; } + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value + String relevant_value() const; + virtual HTMLElement& form_associated_element_to_html_element() = 0; HTMLElement const& form_associated_element_to_html_element() const { return const_cast(*this).form_associated_element_to_html_element(); } // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-form-reset-control virtual void reset_algorithm() {}; - WebIDL::UnsignedLong selection_start() const; + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-select + 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&); - WebIDL::UnsignedLong selection_end() 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&); + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectiondirection + Optional selection_direction() const; + void set_selection_direction(Optional direction); + + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setselectionrange + WebIDL::ExceptionOr set_selection_range(Optional start, Optional end, Optional direction = {}); + protected: FormAssociatedElement() = default; virtual ~FormAssociatedElement() = default; @@ -107,13 +131,21 @@ class FormAssociatedElement { void form_node_was_removed(); void form_node_attribute_changed(FlyString const&, Optional const&); + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value + void relevant_value_was_changed(JS::GCPtr); + private: + void reset_form_owner(); + WeakPtr m_form; // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#parser-inserted-flag bool m_parser_inserted { false }; - void reset_form_owner(); + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-selection + WebIDL::UnsignedLong m_selection_start { 0 }; + WebIDL::UnsignedLong m_selection_end { 0 }; + SelectionDirection m_selection_direction { SelectionDirection::None }; }; } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp index 23e5a3143b0b5..7a59c6726dc1c 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -4,6 +4,7 @@ * Copyright (c) 2022, Andrew Kaster * Copyright (c) 2023-2024, Shannon Booth * Copyright (c) 2023, Bastiaan van der Plaat + * Copyright (c) 2024, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ @@ -410,11 +411,15 @@ WebIDL::ExceptionOr HTMLInputElement::run_input_activation_behavior(DOM::E void HTMLInputElement::did_edit_text_node(Badge) { // An input element's dirty value flag must be set to true whenever the user interacts with the control in a way that changes the value. + auto old_value = move(m_value); m_value = value_sanitization_algorithm(m_text_node->data()); m_dirty_value = true; m_has_uncommitted_changes = true; + if (m_value != old_value) + relevant_value_was_changed(m_text_node); + update_placeholder_visibility(); user_interaction_did_change_input_value(); @@ -550,6 +555,8 @@ WebIDL::ExceptionOr HTMLInputElement::set_value(String const& value) // and the element has a text entry cursor position, move the text entry cursor position to the end of the // text control, unselecting any selected text and resetting the selection direction to "none". if (m_value != old_value) { + relevant_value_was_changed(m_text_node); + if (m_text_node) { m_text_node->set_data(m_value); update_placeholder_visibility(); @@ -1183,12 +1190,16 @@ void HTMLInputElement::form_associated_element_attribute_changed(FlyString const } else if (name == HTML::AttributeNames::value) { if (!m_dirty_value) { + auto old_value = move(m_value); if (!value.has_value()) { m_value = String {}; } else { m_value = value_sanitization_algorithm(*value); } + if (m_value != old_value) + relevant_value_was_changed(m_text_node); + update_shadow_tree(); } } else if (name == HTML::AttributeNames::placeholder) { @@ -1417,6 +1428,7 @@ void HTMLInputElement::reset_algorithm() m_dirty_checkedness = false; // set the value of the element to the value of the value content attribute, if there is one, or the empty string otherwise, + auto old_value = move(m_value); m_value = get_attribute_value(AttributeNames::value); // set the checkedness of the element to true if the element has a checked content attribute and false if it does not, @@ -1428,6 +1440,9 @@ void HTMLInputElement::reset_algorithm() // and then invoke the value sanitization algorithm, if the type attribute's current state defines one. m_value = value_sanitization_algorithm(m_value); + if (m_value != old_value) + relevant_value_was_changed(m_text_node); + if (m_text_node) { m_text_node->set_data(m_value); update_placeholder_visibility(); @@ -2057,66 +2072,6 @@ void HTMLInputElement::set_custom_validity(String const& error) return; } -// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-select -WebIDL::ExceptionOr HTMLInputElement::select() -{ - dbgln("(STUBBED) HTMLInputElement::select(). Called on: {}", debug_description()); - return {}; -} - -// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setselectionrange -WebIDL::ExceptionOr HTMLInputElement::set_selection_range(u32 start, u32 end, Optional const& direction) -{ - dbgln("(STUBBED) HTMLInputElement::set_selection_range(start={}, end={}, direction='{}'). Called on: {}", start, end, direction, debug_description()); - return {}; -} - -// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#textFieldSelection:dom-textarea/input-selectionstart-2 -WebIDL::ExceptionOr HTMLInputElement::set_selection_start_for_bindings(Optional const& value) -{ - // 1. If this element is an input element, and selectionStart does not apply to this element, throw an - // "InvalidStateError" DOMException. - if (!selection_or_range_applies()) - return WebIDL::InvalidStateError::create(realm(), "setSelectionStart does not apply to this input type"_fly_string); - - // NOTE: Steps continued below: - return set_selection_start(value); -} - -// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectionstart -Optional HTMLInputElement::selection_start_for_bindings() const -{ - // 1. If this element is an input element, and selectionStart does not apply to this element, return null. - if (!selection_or_range_applies()) - return {}; - - // NOTE: Steps continued below: - return selection_start(); -} - -// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#textFieldSelection:dom-textarea/input-selectionend-3 -WebIDL::ExceptionOr HTMLInputElement::set_selection_end_for_bindings(Optional const& value) -{ - // 1. If this element is an input element, and selectionEnd does not apply to this element, throw an - // "InvalidStateError" DOMException. - if (!selection_or_range_applies()) - return WebIDL::InvalidStateError::create(realm(), "setSelectionEnd does not apply to this input type"_fly_string); - - // NOTE: Steps continued below: - return set_selection_end(value); -} - -// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectionend -Optional HTMLInputElement::selection_end_for_bindings() const -{ - // 1. If this element is an input element, and selectionEnd does not apply to this element, return null. - if (!selection_or_range_applies()) - return {}; - - // NOTE: Steps continued below: - return selection_end(); -} - Optional HTMLInputElement::default_role() const { // https://www.w3.org/TR/html-aria/#el-input-button @@ -2245,6 +2200,24 @@ bool HTMLInputElement::has_input_activation_behavior() const } } +// https://html.spec.whatwg.org/multipage/input.html#do-not-apply +bool HTMLInputElement::select_applies() const +{ + switch (type_state()) { + case TypeAttributeState::Button: + case TypeAttributeState::Checkbox: + case TypeAttributeState::Hidden: + case TypeAttributeState::ImageButton: + case TypeAttributeState::RadioButton: + case TypeAttributeState::Range: + case TypeAttributeState::ResetButton: + case TypeAttributeState::SubmitButton: + return false; + default: + return true; + } +} + // https://html.spec.whatwg.org/multipage/input.html#do-not-apply bool HTMLInputElement::selection_or_range_applies() const { diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h index 28a1569a50b50..1f172f2302989 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h @@ -2,6 +2,7 @@ * Copyright (c) 2018-2022, Andreas Kling * Copyright (c) 2022, Adam Hodgen * Copyright (c) 2023, Bastiaan van der Plaat + * Copyright (c) 2024, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ @@ -137,9 +138,6 @@ class HTMLInputElement final WebIDL::ExceptionOr report_validity(); void set_custom_validity(String const&); - WebIDL::ExceptionOr select(); - WebIDL::ExceptionOr set_selection_range(u32 start, u32 end, Optional const& direction = {}); - WebIDL::ExceptionOr show_picker(); // ^DOM::EditableTextNodeOwner @@ -196,14 +194,9 @@ class HTMLInputElement final bool value_as_number_applies() const; bool step_applies() const; bool step_up_or_down_applies() const; + bool select_applies() const; bool selection_or_range_applies() const; - WebIDL::ExceptionOr set_selection_start_for_bindings(Optional const&); - Optional selection_start_for_bindings() const; - - WebIDL::ExceptionOr set_selection_end_for_bindings(Optional const&); - Optional selection_end_for_bindings() const; - private: HTMLInputElement(DOM::Document&, DOM::QualifiedName); diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl index c6ab07ab27cd7..c94ec2ff31442 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl +++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl @@ -58,9 +58,9 @@ interface HTMLInputElement : HTMLElement { readonly attribute NodeList? labels; undefined select(); - [ImplementedAs=selection_start_for_bindings] attribute unsigned long? selectionStart; - [ImplementedAs=selection_end_for_bindings] attribute unsigned long? selectionEnd; - [FIXME] attribute DOMString? selectionDirection; + attribute unsigned long? selectionStart; + attribute unsigned long? selectionEnd; + attribute DOMString? selectionDirection; [FIXME] undefined setRangeText(DOMString replacement); [FIXME] 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/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp index d0d2242ab8773..e0a7f354e0ef0 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp @@ -2,6 +2,7 @@ * Copyright (c) 2020, the SerenityOS developers. * Copyright (c) 2023, Sam Atkins * Copyright (c) 2024, Bastiaan van der Plaat + * Copyright (c) 2024, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ @@ -175,8 +176,12 @@ void HTMLTextAreaElement::set_value(String const& value) void HTMLTextAreaElement::set_raw_value(String value) { + auto old_raw_value = m_raw_value; m_raw_value = move(value); m_api_value.clear(); + + if (m_raw_value != old_raw_value) + relevant_value_was_changed(m_text_node); } // https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element:concept-fe-api-value-3 @@ -281,6 +286,36 @@ WebIDL::ExceptionOr HTMLTextAreaElement::set_rows(unsigned rows) return set_attribute(HTML::AttributeNames::rows, MUST(String::number(rows))); } +WebIDL::UnsignedLong HTMLTextAreaElement::selection_start_binding() const +{ + return selection_start().value(); +} + +WebIDL::ExceptionOr HTMLTextAreaElement::set_selection_start_binding(WebIDL::UnsignedLong const& value) +{ + return set_selection_start(value); +} + +WebIDL::UnsignedLong HTMLTextAreaElement::selection_end_binding() const +{ + return selection_end().value(); +} + +WebIDL::ExceptionOr HTMLTextAreaElement::set_selection_end_binding(WebIDL::UnsignedLong const& value) +{ + return set_selection_end(value); +} + +String HTMLTextAreaElement::selection_direction_binding() const +{ + return selection_direction().value(); +} + +void HTMLTextAreaElement::set_selection_direction_binding(String direction) +{ + set_selection_direction(direction); +} + void HTMLTextAreaElement::create_shadow_tree_if_needed() { if (shadow_root()) diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h index 0ef2fba9cd62d..df18ca5b2c805 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h @@ -2,6 +2,7 @@ * Copyright (c) 2020, the SerenityOS developers. * Copyright (c) 2022, Luke Wilde * Copyright (c) 2024, Bastiaan van der Plaat + * Copyright (c) 2024, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ @@ -78,6 +79,9 @@ class HTMLTextAreaElement final String value() const override; void set_value(String const&); + // https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element:concept-fe-api-value-3 + String api_value() const; + u32 text_length() const; bool check_validity(); @@ -96,6 +100,18 @@ class HTMLTextAreaElement final unsigned rows() const; WebIDL::ExceptionOr set_rows(unsigned); + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectionstart + WebIDL::UnsignedLong selection_start_binding() const; + WebIDL::ExceptionOr set_selection_start_binding(WebIDL::UnsignedLong const&); + + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectionend + WebIDL::UnsignedLong selection_end_binding() const; + WebIDL::ExceptionOr set_selection_end_binding(WebIDL::UnsignedLong const&); + + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectiondirection + String selection_direction_binding() const; + void set_selection_direction_binding(String direction); + private: HTMLTextAreaElement(DOM::Document&, DOM::QualifiedName); @@ -103,7 +119,6 @@ class HTMLTextAreaElement final virtual void visit_edges(Cell::Visitor&) override; void set_raw_value(String); - String api_value() const; // ^DOM::Element virtual i32 default_tab_index_value() const override; @@ -116,6 +131,7 @@ class HTMLTextAreaElement final void queue_firing_input_event(); void update_placeholder_visibility(); + JS::GCPtr m_placeholder_element; JS::GCPtr m_placeholder_text_node; diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl index 6371de62c2154..194212c90aeba 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl +++ b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl @@ -34,11 +34,11 @@ interface HTMLTextAreaElement : HTMLElement { readonly attribute NodeList labels; - [FIXME] undefined select(); - attribute unsigned long selectionStart; - attribute unsigned long selectionEnd; - [FIXME] attribute DOMString selectionDirection; + undefined select(); + [ImplementedAs=selection_start_binding] attribute unsigned long selectionStart; + [ImplementedAs=selection_end_binding] attribute unsigned long selectionEnd; + [ImplementedAs=selection_direction_binding] attribute DOMString selectionDirection; [FIXME] undefined setRangeText(DOMString replacement); [FIXME] undefined setRangeText(DOMString replacement, unsigned long start, unsigned long end, optional SelectionMode selectionMode = "preserve"); - [FIXME] undefined setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction); + undefined setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction); };