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);
};