|
@@ -1,6 +1,7 @@
|
|
|
/*
|
|
|
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
|
|
* Copyright (c) 2024, Jelle Raaijmakers <jelle@gmta.nl>
|
|
|
+ * Copyright (c) 2024, Tim Ledbetter <tim.ledbetter@ladybird.org>
|
|
|
*
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
*/
|
|
@@ -345,6 +346,132 @@ void FormAssociatedTextControlElement::set_selection_direction(Optional<String>
|
|
|
m_selection_direction = string_to_selection_direction(direction);
|
|
|
}
|
|
|
|
|
|
+// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setrangetext
|
|
|
+WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_range_text(String const& replacement)
|
|
|
+{
|
|
|
+ return set_range_text(replacement, m_selection_start, m_selection_end);
|
|
|
+}
|
|
|
+
|
|
|
+// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setrangetext
|
|
|
+WebIDL::ExceptionOr<void> 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<HTMLInputElement>(html_element) && !static_cast<HTMLInputElement&>(html_element).selection_or_range_applies())
|
|
|
+ return WebIDL::InvalidStateError::create(html_element.realm(), "setRangeText does not apply to this input type"_fly_string);
|
|
|
+
|
|
|
+ // 2. Set this element's dirty value flag to true.
|
|
|
+ set_dirty_value_flag(true);
|
|
|
+
|
|
|
+ // 3. If the method has only one argument, then let start and end have the values of the selectionStart attribute and the selectionEnd attribute respectively.
|
|
|
+ // Otherwise, let start, end have the values of the second and third arguments respectively.
|
|
|
+ // NOTE: This is handled by the caller.
|
|
|
+
|
|
|
+ // 4. If start is greater than end, then throw an "IndexSizeError" DOMException.
|
|
|
+ if (start > end)
|
|
|
+ return WebIDL::IndexSizeError::create(html_element.realm(), "The start argument must be less than or equal to the end argument"_fly_string);
|
|
|
+
|
|
|
+ // 5. If start is greater than the length of the relevant value of the text control, then set it to the length of the relevant value of the text control.
|
|
|
+ auto the_relevant_value = relevant_value();
|
|
|
+ auto relevant_value_length = the_relevant_value.code_points().length();
|
|
|
+ if (start > relevant_value_length)
|
|
|
+ start = relevant_value_length;
|
|
|
+
|
|
|
+ // 6. If end is greater than the length of the relevant value of the text control, then set it to the length of the relevant value of the text control.
|
|
|
+ if (end > relevant_value_length)
|
|
|
+ end = relevant_value_length;
|
|
|
+
|
|
|
+ // 7. Let selection start be the current value of the selectionStart attribute.
|
|
|
+ auto selection_start = m_selection_start;
|
|
|
+
|
|
|
+ // 8. Let selection end be the current value of the selectionEnd attribute.
|
|
|
+ auto selection_end = m_selection_end;
|
|
|
+
|
|
|
+ // 9. If start is less than end, delete the sequence of code units within the element's relevant value starting with
|
|
|
+ // the code unit at the startth position and ending with the code unit at the (end-1)th position.
|
|
|
+ if (start < end) {
|
|
|
+ StringBuilder builder;
|
|
|
+ auto before_removal_point_view = the_relevant_value.code_points().unicode_substring_view(0, start);
|
|
|
+ builder.append(before_removal_point_view.as_string());
|
|
|
+ auto after_removal_point_view = the_relevant_value.code_points().unicode_substring_view(end);
|
|
|
+ builder.append(after_removal_point_view.as_string());
|
|
|
+ the_relevant_value = MUST(builder.to_string());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 10. Insert the value of the first argument into the text of the relevant value of the text control, immediately before the startth code unit.
|
|
|
+ StringBuilder builder;
|
|
|
+ auto before_insertion_point_view = the_relevant_value.code_points().unicode_substring_view(0, start);
|
|
|
+ builder.append(before_insertion_point_view.as_string());
|
|
|
+ builder.append(replacement);
|
|
|
+ auto after_insertion_point_view = the_relevant_value.code_points().unicode_substring_view(start);
|
|
|
+ builder.append(after_insertion_point_view.as_string());
|
|
|
+ the_relevant_value = MUST(builder.to_string());
|
|
|
+ TRY(set_relevant_value(the_relevant_value));
|
|
|
+
|
|
|
+ // 11. Let new length be the length of the value of the first argument.
|
|
|
+ i64 new_length = replacement.code_points().length();
|
|
|
+
|
|
|
+ // 12. Let new end be the sum of start and new length.
|
|
|
+ auto new_end = start + new_length;
|
|
|
+
|
|
|
+ // 13. Run the appropriate set of substeps from the following list:
|
|
|
+ switch (selection_mode) {
|
|
|
+ // If the fourth argument's value is "select"
|
|
|
+ case Bindings::SelectionMode::Select:
|
|
|
+ // Let selection start be start.
|
|
|
+ selection_start = start;
|
|
|
+
|
|
|
+ // Let selection end be new end.
|
|
|
+ selection_end = new_end;
|
|
|
+ break;
|
|
|
+
|
|
|
+ // If the fourth argument's value is "start"
|
|
|
+ case Bindings::SelectionMode::Start:
|
|
|
+ // Let selection start and selection end be start.
|
|
|
+ selection_start = start;
|
|
|
+ selection_end = start;
|
|
|
+ break;
|
|
|
+
|
|
|
+ // If the fourth argument's value is "end"
|
|
|
+ case Bindings::SelectionMode::End:
|
|
|
+ selection_start = new_end;
|
|
|
+ selection_end = new_end;
|
|
|
+ break;
|
|
|
+
|
|
|
+ // If the fourth argument's value is "preserve"
|
|
|
+ case Bindings::SelectionMode::Preserve:
|
|
|
+ // 1. Let old length be end minus start.
|
|
|
+ auto old_length = end - start;
|
|
|
+
|
|
|
+ // 2. Let delta be new length minus old length.
|
|
|
+ auto delta = new_length - old_length;
|
|
|
+
|
|
|
+ // 3. If selection start is greater than end, then increment it by delta.
|
|
|
+ // (If delta is negative, i.e. the new text is shorter than the old text, then this will decrease the value of selection start.)
|
|
|
+ // Otherwise: if selection start is greater than start, then set it to start.
|
|
|
+ // (This snaps the start of the selection to the start of the new text if it was in the middle of the text that it replaced.)
|
|
|
+ if (selection_start > end)
|
|
|
+ selection_start += delta;
|
|
|
+ else if (selection_start > start)
|
|
|
+ selection_start = start;
|
|
|
+
|
|
|
+ // 4. If selection end is greater than end, then increment it by delta in the same way.
|
|
|
+ // Otherwise: if selection end is greater than start, then set it to new end.
|
|
|
+ // (This snaps the end of the selection to the end of the new text if it was in the middle of the text that it replaced.)
|
|
|
+ if (selection_end > end)
|
|
|
+ selection_end += delta;
|
|
|
+ else if (selection_end > start)
|
|
|
+ selection_end = new_end;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 14. Set the selection range with selection start and selection end.
|
|
|
+ set_the_selection_range(selection_start, selection_end);
|
|
|
+
|
|
|
+ return {};
|
|
|
+}
|
|
|
+
|
|
|
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setselectionrange
|
|
|
WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_selection_range(Optional<WebIDL::UnsignedLong> start, Optional<WebIDL::UnsignedLong> end, Optional<String> direction)
|
|
|
{
|