Skip to content

Commit

Permalink
LibWeb+WebContent: Various UTF-16 related changes
Browse files Browse the repository at this point in the history
  • Loading branch information
rmg-x committed Oct 19, 2024
1 parent 913bc16 commit f09202a
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 77 deletions.
1 change: 1 addition & 0 deletions Userland/Libraries/LibWeb/DOM/Document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5459,6 +5459,7 @@ void Document::set_cursor_position(JS::NonnullGCPtr<DOM::Position> position)
m_cursor_position->node()->paintable()->set_needs_display();

m_cursor_position = position;
m_cursor_position->set_offset(position->offset());

if (m_cursor_position && m_cursor_position->node()->paintable())
m_cursor_position->node()->paintable()->set_needs_display();
Expand Down
41 changes: 16 additions & 25 deletions Userland/Libraries/LibWeb/DOM/Position.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,31 +40,23 @@ bool Position::increment_offset()
if (!is<DOM::Text>(*m_node))
return false;

auto& node = verify_cast<DOM::Text>(*m_node);
auto const& node = verify_cast<DOM::Text>(*m_node);
if (auto const length = node.length_in_utf16_code_units(); m_offset < length)
++m_offset;

if (auto offset = node.grapheme_segmenter().next_boundary(m_offset); offset.has_value()) {
m_offset = *offset;
return true;
}

// NOTE: Already at end of current node.
return false;
return true;
}

bool Position::decrement_offset()
{
if (!is<DOM::Text>(*m_node))
return false;

auto& node = verify_cast<DOM::Text>(*m_node);
auto const& node = verify_cast<DOM::Text>(*m_node);
if (auto const length = node.length_in_utf16_code_units(); length > 0 && m_offset > 0)
--m_offset;

if (auto offset = node.grapheme_segmenter().previous_boundary(m_offset); offset.has_value()) {
m_offset = *offset;
return true;
}

// NOTE: Already at beginning of current node.
return false;
return true;
}

static bool should_continue_beyond_word(Utf8View const& word)
Expand All @@ -82,14 +74,14 @@ bool Position::increment_offset_to_next_word()
if (!is<DOM::Text>(*m_node) || offset_is_at_end_of_node())
return false;

auto& node = static_cast<DOM::Text&>(*m_node);
auto const& node = static_cast<DOM::Text&>(*m_node);

while (true) {
if (auto offset = node.word_segmenter().next_boundary(m_offset); offset.has_value()) {
auto word = node.data().code_points().substring_view(m_offset, *offset - m_offset);
auto word = MUST(node.data().substring_from_code_unit_offset(m_offset, *offset - m_offset));
m_offset = *offset;

if (should_continue_beyond_word(word))
if (should_continue_beyond_word(word.code_points()))
continue;
}

Expand All @@ -104,14 +96,14 @@ bool Position::decrement_offset_to_previous_word()
if (!is<DOM::Text>(*m_node) || m_offset == 0)
return false;

auto& node = static_cast<DOM::Text&>(*m_node);
auto const& node = static_cast<DOM::Text&>(*m_node);

while (true) {
if (auto offset = node.word_segmenter().previous_boundary(m_offset); offset.has_value()) {
auto word = node.data().code_points().substring_view(*offset, m_offset - *offset);
auto word = MUST(node.data().substring_from_code_unit_offset(*offset, m_offset - *offset));
m_offset = *offset;

if (should_continue_beyond_word(word))
if (should_continue_beyond_word(word.code_points()))
continue;
}

Expand All @@ -126,9 +118,8 @@ bool Position::offset_is_at_end_of_node() const
if (!is<DOM::Text>(*m_node))
return false;

auto& node = verify_cast<DOM::Text>(*m_node);
auto text = node.data();
return m_offset == text.bytes_as_string_view().length();
auto const& node = verify_cast<DOM::Text>(*m_node);
return m_offset == node.length_in_utf16_code_units();
}

}
2 changes: 1 addition & 1 deletion Userland/Libraries/LibWeb/DOM/Position.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Position final : public JS::Cell {

unsigned offset() const { return m_offset; }
bool offset_is_at_end_of_node() const;
void set_offset(unsigned value) { m_offset = value; }
void set_offset(unsigned const value) { m_offset = value; }
bool increment_offset();
bool decrement_offset();

Expand Down
26 changes: 13 additions & 13 deletions Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,8 @@ WebIDL::ExceptionOr<void> FormAssociatedElement::set_form_action(String const& v
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value
void FormAssociatedTextControlElement::relevant_value_was_changed(JS::GCPtr<DOM::Text> text_node)
{
auto the_relevant_value = relevant_value();
auto relevant_value_length = the_relevant_value.code_points().length();
auto const the_relevant_value = relevant_value();
auto const relevant_value_length = Utf16View { MUST(AK::utf8_to_utf16(the_relevant_value)) }.length_in_code_units();

// 1. If the element has a selection:
if (m_selection_start < m_selection_end) {
Expand Down Expand Up @@ -418,7 +418,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 = Utf16View { MUST(AK::utf8_to_utf16(the_relevant_value)) }.length_in_code_units();
if (start > relevant_value_length)
start = relevant_value_length;

Expand All @@ -436,25 +436,25 @@ WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_range_text(Strin
// 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());
auto before_removal_point_view = MUST(the_relevant_value.substring_from_code_unit_offset(0, start));
builder.append(before_removal_point_view);
auto after_removal_point_view = MUST(the_relevant_value.substring_from_code_unit_offset(end));
builder.append(after_removal_point_view);
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());
auto before_insertion_point_view = MUST(the_relevant_value.substring_from_code_unit_offset(0, start));
builder.append(before_insertion_point_view);
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());
auto after_insertion_point_view = MUST(the_relevant_value.substring_from_code_unit_offset(start));
builder.append(after_insertion_point_view);
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();
size_t new_length = Utf16View { MUST(AK::utf8_to_utf16(replacement)) }.length_in_code_units();

// 12. Let new end be the sum of start and new length.
auto new_end = start + new_length;
Expand Down Expand Up @@ -545,7 +545,7 @@ 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 = Utf16View { MUST(AK::utf8_to_utf16(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);

Expand Down
9 changes: 6 additions & 3 deletions Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,8 @@ WebIDL::ExceptionOr<void> HTMLInputElement::set_value(String const& value)
m_text_node->set_data(m_value);
update_placeholder_visibility();

set_the_selection_range(m_text_node->length(), m_text_node->length());
auto const text_node_length = m_text_node->length_in_utf16_code_units();
set_the_selection_range(text_node_length, text_node_length);
}

update_shadow_tree();
Expand Down Expand Up @@ -1177,8 +1178,10 @@ void HTMLInputElement::did_receive_focus()
if (m_placeholder_text_node)
m_placeholder_text_node->invalidate_style(DOM::StyleInvalidationReason::DidReceiveFocus);

if (auto cursor = document().cursor_position(); !cursor || m_text_node != cursor->node())
document().set_cursor_position(DOM::Position::create(realm(), *m_text_node, m_text_node->length()));
if (auto cursor = document().cursor_position(); !cursor || m_text_node != cursor->node()) {
auto const code_unit_length = m_text_node->length_in_utf16_code_units();
document().set_cursor_position(DOM::Position::create(realm(), *m_text_node, code_unit_length));
}
}

void HTMLInputElement::did_lose_focus()
Expand Down
3 changes: 2 additions & 1 deletion Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ void HTMLTextAreaElement::set_value(String const& value)
m_text_node->set_data(m_raw_value);
update_placeholder_visibility();

set_the_selection_range(m_text_node->length(), m_text_node->length());
auto const text_node_length = m_text_node->length_in_utf16_code_units();
set_the_selection_range(text_node_length, text_node_length);
}
}
}
Expand Down
36 changes: 16 additions & 20 deletions Userland/Libraries/LibWeb/Page/EditEventHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,12 @@ namespace Web {
void EditEventHandler::handle_delete_character_after(JS::NonnullGCPtr<DOM::Document> document, JS::NonnullGCPtr<DOM::Position> cursor_position)
{
auto& node = verify_cast<DOM::Text>(*cursor_position->node());
auto& text = node.data();

auto next_offset = node.grapheme_segmenter().next_boundary(cursor_position->offset());
if (!next_offset.has_value()) {
// FIXME: Move to the next node and delete the first character there.
return;
}
auto const& text = node.data();
auto const next_offset = node.length_in_utf16_code_units();

StringBuilder builder;
builder.append(text.bytes_as_string_view().substring_view(0, cursor_position->offset()));
builder.append(text.bytes_as_string_view().substring_view(*next_offset));
builder.append(MUST(text.substring_from_code_unit_offset(0, cursor_position->offset())));
builder.append(MUST(text.substring_from_code_unit_offset(next_offset)));
node.set_data(MUST(builder.to_string()));

document->user_did_edit_document_text({});
Expand All @@ -44,8 +39,8 @@ void EditEventHandler::handle_delete(JS::NonnullGCPtr<DOM::Document> document, D

if (start == end) {
StringBuilder builder;
builder.append(start->data().bytes_as_string_view().substring_view(0, range.start_offset()));
builder.append(end->data().bytes_as_string_view().substring_view(range.end_offset()));
builder.append(MUST(start->data().substring_from_code_unit_offset(0, range.start_offset())));
builder.append(MUST(end->data().substring_from_code_unit_offset(range.end_offset())));

start->set_data(MUST(builder.to_string()));
} else {
Expand Down Expand Up @@ -82,8 +77,8 @@ void EditEventHandler::handle_delete(JS::NonnullGCPtr<DOM::Document> document, D

// Join the start and end nodes.
StringBuilder builder;
builder.append(start->data().bytes_as_string_view().substring_view(0, range.start_offset()));
builder.append(end->data().bytes_as_string_view().substring_view(range.end_offset()));
builder.append(MUST(start->data().substring_from_code_unit_offset(0, range.start_offset())));
builder.append(MUST(end->data().substring_from_code_unit_offset(range.end_offset())));

start->set_data(MUST(builder.to_string()));
end->remove();
Expand All @@ -99,22 +94,23 @@ void EditEventHandler::handle_insert(JS::NonnullGCPtr<DOM::Document> document, J
handle_insert(document, position, MUST(builder.to_string()));
}

void EditEventHandler::handle_insert(JS::NonnullGCPtr<DOM::Document> document, JS::NonnullGCPtr<DOM::Position> position, String data)
void EditEventHandler::handle_insert(JS::NonnullGCPtr<DOM::Document> document, JS::NonnullGCPtr<DOM::Position> position, String const& data)
{
if (is<DOM::Text>(*position->node())) {
auto& node = verify_cast<DOM::Text>(*position->node());

StringBuilder builder;
builder.append(node.data().bytes_as_string_view().substring_view(0, position->offset()));
builder.append(MUST(node.data().substring_from_code_unit_offset(0, position->offset())));
builder.append(data);
builder.append(node.data().bytes_as_string_view().substring_view(position->offset()));
builder.append(MUST(node.data().substring_from_code_unit_offset(position->offset())));

auto const built_value = MUST(builder.to_string());

// Cut string by max length
// FIXME: Cut by UTF-16 code units instead of raw bytes
if (auto max_length = node.max_length(); max_length.has_value() && builder.string_view().length() > *max_length) {
node.set_data(MUST(String::from_utf8(builder.string_view().substring_view(0, *max_length))));
if (auto max_length = node.max_length(); max_length.has_value() && Utf16View { MUST(AK::utf8_to_utf16(built_value)) }.length_in_code_units() > *max_length) {
node.set_data(MUST(built_value.substring_from_code_unit_offset(0, *max_length)));
} else {
node.set_data(MUST(builder.to_string()));
node.set_data(built_value);
}
node.invalidate_style(DOM::StyleInvalidationReason::EditingInsertion);
} else {
Expand Down
2 changes: 1 addition & 1 deletion Userland/Libraries/LibWeb/Page/EditEventHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class EditEventHandler {
void handle_delete_character_after(JS::NonnullGCPtr<DOM::Document>, JS::NonnullGCPtr<DOM::Position>);
void handle_delete(JS::NonnullGCPtr<DOM::Document>, DOM::Range&);
void handle_insert(JS::NonnullGCPtr<DOM::Document>, JS::NonnullGCPtr<DOM::Position>, u32 code_point);
void handle_insert(JS::NonnullGCPtr<DOM::Document>, JS::NonnullGCPtr<DOM::Position>, String);
void handle_insert(JS::NonnullGCPtr<DOM::Document>, JS::NonnullGCPtr<DOM::Position>, String const&);
};

}
19 changes: 11 additions & 8 deletions Userland/Libraries/LibWeb/Page/EventHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,7 @@ EventResult EventHandler::handle_doubleclick(CSSPixelPoint viewport_position, CS
segmenter.set_segmented_text(text_for_rendering);

auto previous_boundary = segmenter.previous_boundary(result->index_in_node, Unicode::Segmenter::Inclusive::Yes).value_or(0);
auto next_boundary = segmenter.next_boundary(result->index_in_node).value_or(text_for_rendering.byte_count());
auto next_boundary = segmenter.next_boundary(result->index_in_node).value_or(Utf16View { MUST(AK::utf8_to_utf16(text_for_rendering)) }.length_in_code_units());

auto& realm = node->document().realm();
document.set_cursor_position(DOM::Position::create(realm, hit_dom_node, next_boundary));
Expand Down Expand Up @@ -1027,15 +1027,16 @@ EventResult EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u
return EventResult::Handled;
}

if (key == UIEvents::KeyCode::Key_Home || key == UIEvents::KeyCode::Key_End) {
auto cursor_edge = key == UIEvents::KeyCode::Key_Home ? 0uz : node.length();
if ((key == UIEvents::KeyCode::Key_Home || key == UIEvents::KeyCode::Key_End) && is<DOM::Text>(node)) {
auto const& text_node = verify_cast<DOM::Text>(node);
auto const cursor_edge = key == UIEvents::KeyCode::Key_Home ? 0 : text_node.length_in_utf16_code_units();

if ((modifiers & UIEvents::Mod_Shift) != 0) {
auto previous_position = document->cursor_position()->offset();
auto should_udpdate_selection = previous_position != cursor_edge;
auto const previous_position = document->cursor_position()->offset();
auto const should_update_selection = previous_position != cursor_edge;

if (should_udpdate_selection && selection) {
auto selection_start = range ? selection->anchor_offset() : previous_position;
if (should_update_selection && selection) {
auto const selection_start = range ? selection->anchor_offset() : previous_position;
(void)selection->set_base_and_extent(node, selection_start, node, cursor_edge);
}
} else if (node.is_editable()) {
Expand Down Expand Up @@ -1139,7 +1140,9 @@ void EventHandler::handle_paste(String const& text)
return;
active_document->update_layout();
m_edit_event_handler->handle_insert(*active_document, *cursor_position, text);
cursor_position->set_offset(cursor_position->offset() + text.code_points().length());

auto const code_unit_length = Utf16View { MUST(AK::utf8_to_utf16(text)) }.length_in_code_units();
cursor_position->set_offset(cursor_position->offset() + code_unit_length);
}
}

Expand Down
7 changes: 2 additions & 5 deletions Userland/Services/WebContent/WebDriverConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1727,11 +1727,8 @@ Messages::WebDriverClient::ElementSendKeysResponse WebDriverConnection::element_
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();
auto const api_value = target->relevant_value();
current_text_length = Utf16View { MUST(AK::utf8_to_utf16(api_value)) }.length_in_code_units();
}

// 2. Set the text insertion caret using set selection range using current text length for both the start
Expand Down

0 comments on commit f09202a

Please sign in to comment.