LibWeb+WebContent: Port FormAssociatedTextControlElement APIs to UTF-16

This commit is contained in:
Timothy Flynn 2025-07-25 15:04:25 -04:00 committed by Jelle Raaijmakers
commit 017a6cc687
Notes: github-actions[bot] 2025-07-25 22:42:01 +00:00
12 changed files with 58 additions and 64 deletions

View file

@ -34,7 +34,7 @@ void EditingHostManager::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_active_contenteditable_element);
}
void EditingHostManager::handle_insert(String const& value)
void EditingHostManager::handle_insert(Utf16String const& value)
{
// https://w3c.github.io/editing/docs/execCommand/#additional-requirements
// When the user instructs the user agent to insert text inside an editing host, such as by typing on the keyboard
@ -43,7 +43,7 @@ void EditingHostManager::handle_insert(String const& value)
// once or in quick succession, this specification does not define whether it is treated as one insertion or several
// consecutive insertions.
auto editing_result = m_document->exec_command(Editing::CommandNames::insertText, false, value);
auto editing_result = m_document->exec_command(Editing::CommandNames::insertText, false, value.to_utf8_but_should_be_ported_to_utf16());
if (editing_result.is_exception())
dbgln("handle_insert(): editing resulted in exception: {}", editing_result.exception());
}

View file

@ -23,7 +23,7 @@ class EditingHostManager
public:
[[nodiscard]] static GC::Ref<EditingHostManager> create(JS::Realm&, GC::Ref<Document>);
virtual void handle_insert(String const&) override;
virtual void handle_insert(Utf16String const&) override;
virtual void handle_delete(DeleteDirection) override;
virtual EventResult handle_return_key(FlyString const& ui_input_type) override;
virtual void select_all() override;

View file

@ -18,7 +18,7 @@ public:
virtual GC::Ref<JS::Cell> as_cell() = 0;
virtual void handle_insert(String const&) = 0;
virtual void handle_insert(Utf16String const&) = 0;
virtual EventResult handle_return_key(FlyString const& ui_input_type) = 0;
enum class DeleteDirection {

View file

@ -397,7 +397,7 @@ bool FormAssociatedElement::suffering_from_a_custom_error() const
void FormAssociatedTextControlElement::relevant_value_was_changed()
{
auto the_relevant_value = relevant_value();
auto relevant_value_length = the_relevant_value.code_points().length();
auto relevant_value_length = the_relevant_value.length_in_code_units();
// 1. If the element has a selection:
if (m_selection_start < m_selection_end) {
@ -592,13 +592,13 @@ WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_selection_direct
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setrangetext
WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_range_text_binding(String const& replacement)
WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_range_text_binding(Utf16String const& replacement)
{
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<void> FormAssociatedTextControlElement::set_range_text_binding(String const& replacement, WebIDL::UnsignedLong start, WebIDL::UnsignedLong end, Bindings::SelectionMode selection_mode)
WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_range_text_binding(Utf16String const& replacement, WebIDL::UnsignedLong start, WebIDL::UnsignedLong end, Bindings::SelectionMode selection_mode)
{
auto& html_element = form_associated_element_to_html_element();
@ -611,7 +611,7 @@ WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_range_text_bindi
}
// 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)
WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_range_text(Utf16String const& replacement, WebIDL::UnsignedLong start, WebIDL::UnsignedLong end, Bindings::SelectionMode selection_mode)
{
auto& html_element = form_associated_element_to_html_element();
@ -628,7 +628,7 @@ WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_range_text(Strin
// 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();
auto relevant_value_length = the_relevant_value.length_in_code_units();
if (start > relevant_value_length)
start = relevant_value_length;
@ -645,26 +645,24 @@ WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_range_text(Strin
// 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());
StringBuilder builder(StringBuilder::Mode::UTF16, the_relevant_value.length_in_code_units() - (end - start));
builder.append(the_relevant_value.substring_view(0, start));
builder.append(the_relevant_value.substring_view(end));
the_relevant_value = builder.to_utf16_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());
StringBuilder builder(StringBuilder::Mode::UTF16, the_relevant_value.length_in_code_units() + replacement.length_in_code_units());
builder.append(the_relevant_value.substring_view(0, start));
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());
builder.append(the_relevant_value.substring_view(start));
the_relevant_value = builder.to_utf16_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();
auto new_length = replacement.length_in_code_units();
// 12. Let new end be the sum of start and new length.
auto new_end = start + new_length;
@ -755,7 +753,8 @@ void FormAssociatedTextControlElement::set_the_selection_range(Optional<WebIDL::
// 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 relevant_value_length = the_relevant_value.length_in_code_units();
auto new_selection_start = AK::min(start.value(), relevant_value_length);
auto new_selection_end = AK::min(end.value(), relevant_value_length);
@ -797,20 +796,20 @@ void FormAssociatedTextControlElement::set_the_selection_range(Optional<WebIDL::
}
}
void FormAssociatedTextControlElement::handle_insert(String const& data)
void FormAssociatedTextControlElement::handle_insert(Utf16String const& data)
{
auto text_node = form_associated_element_to_text_node();
if (!text_node || !is_mutable())
return;
String data_for_insertion = data;
// FIXME: Cut by UTF-16 code units instead of raw bytes
auto data_for_insertion = data;
if (auto max_length = text_node->max_length(); max_length.has_value()) {
auto remaining_length = *max_length - text_node->data().length_in_code_points();
if (remaining_length < data.code_points().length()) {
data_for_insertion = MUST(data.substring_from_byte_offset(0, remaining_length));
}
auto remaining_length = *max_length - text_node->length_in_utf16_code_units();
if (remaining_length < data.length_in_code_units())
data_for_insertion = Utf16String::from_utf16_without_validation(data.substring_view(0, remaining_length));
}
auto selection_start = this->selection_start();
auto selection_end = this->selection_end();
MUST(set_range_text(data_for_insertion, selection_start, selection_end, Bindings::SelectionMode::End));
@ -824,21 +823,22 @@ void FormAssociatedTextControlElement::handle_delete(DeleteDirection direction)
auto text_node = form_associated_element_to_text_node();
if (!text_node || !is_mutable())
return;
auto selection_start = this->selection_start();
auto selection_end = this->selection_end();
if (selection_start == selection_end) {
if (direction == DeleteDirection::Backward) {
if (selection_start > 0) {
MUST(set_range_text(String {}, selection_start - 1, selection_end, Bindings::SelectionMode::End));
}
if (selection_start > 0)
MUST(set_range_text({}, selection_start - 1, selection_end, Bindings::SelectionMode::End));
} else {
if (selection_start < text_node->data().length_in_code_points()) {
MUST(set_range_text(String {}, selection_start, selection_end + 1, Bindings::SelectionMode::End));
}
if (selection_start < text_node->length_in_utf16_code_units())
MUST(set_range_text({}, selection_start, selection_end + 1, Bindings::SelectionMode::End));
}
return;
}
MUST(set_range_text(String {}, selection_start, selection_end, Bindings::SelectionMode::End));
MUST(set_range_text({}, selection_start, selection_end, Bindings::SelectionMode::End));
}
EventResult FormAssociatedTextControlElement::handle_return_key(FlyString const&)

View file

@ -181,8 +181,8 @@ class FormAssociatedTextControlElement
, public InputEventsTarget {
public:
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value
virtual String relevant_value() = 0;
virtual WebIDL::ExceptionOr<void> set_relevant_value(String const&) = 0;
virtual Utf16String relevant_value() = 0;
virtual WebIDL::ExceptionOr<void> set_relevant_value(Utf16String const&) = 0;
virtual void set_dirty_value_flag(bool flag) = 0;
@ -206,9 +206,9 @@ 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<void> set_range_text_binding(String const& replacement);
WebIDL::ExceptionOr<void> set_range_text_binding(String const& replacement, WebIDL::UnsignedLong start, WebIDL::UnsignedLong end, Bindings::SelectionMode = Bindings::SelectionMode::Preserve);
WebIDL::ExceptionOr<void> set_range_text(String const& replacement, WebIDL::UnsignedLong start, WebIDL::UnsignedLong end, Bindings::SelectionMode = Bindings::SelectionMode::Preserve);
WebIDL::ExceptionOr<void> set_range_text_binding(Utf16String const& replacement);
WebIDL::ExceptionOr<void> set_range_text_binding(Utf16String const& replacement, WebIDL::UnsignedLong start, WebIDL::UnsignedLong end, Bindings::SelectionMode = Bindings::SelectionMode::Preserve);
WebIDL::ExceptionOr<void> set_range_text(Utf16String 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
void set_the_selection_range(Optional<WebIDL::UnsignedLong> start, Optional<WebIDL::UnsignedLong> end, SelectionDirection direction = SelectionDirection::None, SelectionSource source = SelectionSource::DOM);
@ -228,7 +228,7 @@ public:
virtual GC::Ptr<DOM::Text> form_associated_element_to_text_node() = 0;
virtual GC::Ptr<DOM::Text const> form_associated_element_to_text_node() const { return const_cast<FormAssociatedTextControlElement&>(*this).form_associated_element_to_text_node(); }
virtual void handle_insert(String const&) override;
virtual void handle_insert(Utf16String const&) override;
virtual void handle_delete(DeleteDirection) override;
virtual EventResult handle_return_key(FlyString const& ui_input_type) override;
virtual void select_all() override;

View file

@ -84,8 +84,8 @@ public:
WebIDL::ExceptionOr<void> set_value(String const&);
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value
virtual String relevant_value() override { return value(); }
WebIDL::ExceptionOr<void> set_relevant_value(String const& value) override { return set_value(value); }
virtual Utf16String relevant_value() override { return Utf16String::from_utf8(value()); }
WebIDL::ExceptionOr<void> set_relevant_value(Utf16String const& value) override { return set_value(value.to_utf8_but_should_be_ported_to_utf16()); }
virtual void set_dirty_value_flag(bool flag) override { m_dirty_value = flag; }

View file

@ -65,8 +65,8 @@ interface HTMLInputElement : HTMLElement {
[ImplementedAs=selection_start_binding] attribute unsigned long? selectionStart;
[ImplementedAs=selection_end_binding] attribute unsigned long? selectionEnd;
[ImplementedAs=selection_direction_binding] attribute DOMString? selectionDirection;
[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");
[ImplementedAs=set_range_text_binding] undefined setRangeText(Utf16DOMString replacement);
[ImplementedAs=set_range_text_binding] undefined setRangeText(Utf16DOMString replacement, unsigned long start, unsigned long end, optional SelectionMode selectionMode = "preserve");
undefined setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction);
undefined showPicker();

View file

@ -219,9 +219,9 @@ String HTMLTextAreaElement::api_value() const
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value
WebIDL::ExceptionOr<void> HTMLTextAreaElement::set_relevant_value(String const& value)
WebIDL::ExceptionOr<void> HTMLTextAreaElement::set_relevant_value(Utf16String const& value)
{
set_value(value);
set_value(value.to_utf8_but_should_be_ported_to_utf16());
return {};
}

View file

@ -87,8 +87,8 @@ public:
String api_value() const;
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value
virtual String relevant_value() override { return api_value(); }
virtual WebIDL::ExceptionOr<void> set_relevant_value(String const& value) override;
virtual Utf16String relevant_value() override { return Utf16String::from_utf8(api_value()); }
virtual WebIDL::ExceptionOr<void> set_relevant_value(Utf16String const& value) override;
virtual void set_dirty_value_flag(bool flag) override { m_dirty_value = flag; }

View file

@ -39,7 +39,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;
[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");
[ImplementedAs=set_range_text_binding] undefined setRangeText(Utf16DOMString replacement);
[ImplementedAs=set_range_text_binding] undefined setRangeText(Utf16DOMString replacement, unsigned long start, unsigned long end, optional SelectionMode selectionMode = "preserve");
undefined setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction);
};

View file

@ -1288,7 +1288,7 @@ EventResult EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u
FIRE(input_event(UIEvents::EventNames::beforeinput, input_type, m_navigable, code_point));
if (target->handle_return_key(input_type) != EventResult::Handled)
target->handle_insert(String::from_code_point(code_point));
target->handle_insert(Utf16String::from_code_point(code_point));
return EventResult::Handled;
}
@ -1296,7 +1296,7 @@ EventResult EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u
// FIXME: Text editing shortcut keys (copy/paste etc.) should be handled here.
if (!should_ignore_keydown_event(code_point, modifiers)) {
FIRE(input_event(UIEvents::EventNames::beforeinput, UIEvents::InputTypes::insertText, m_navigable, code_point));
target->handle_insert(String::from_code_point(code_point));
target->handle_insert(Utf16String::from_code_point(code_point));
return EventResult::Handled;
}
} else if (auto selection = document->get_selection(); selection && !selection->is_collapsed()) {
@ -1386,7 +1386,7 @@ EventResult EventHandler::handle_paste(String const& text)
return EventResult::Dropped;
FIRE(input_event(UIEvents::EventNames::beforeinput, UIEvents::InputTypes::insertFromPaste, m_navigable, text));
target->handle_insert(text);
target->handle_insert(Utf16String::from_utf8(text));
return EventResult::Handled;
}

View file

@ -1978,14 +1978,8 @@ Web::WebDriver::Response WebDriverConnection::element_send_keys_impl(StringView
if (target.has_value()) {
// 1. If element does not currently have focus, let current text length be the length of element's API value.
Optional<Web::WebIDL::UnsignedLong> current_text_length;
if (!element->is_focused()) {
auto api_value = target->relevant_value();
// FIXME: This should be a UTF-16 code unit length, but `set_the_selection_range` is also currently
// implemented in terms of code point length.
current_text_length = api_value.code_points().length();
}
if (!element->is_focused())
current_text_length = target->relevant_value().length_in_code_units();
// 2. Set the text insertion caret using set selection range using current text length for both the start
// and end parameters.