LibWeb: Fix ability to modify selection outside of inputs using keyboard

Fixes regression introduced in a8077f79cc
This commit is contained in:
Aliaksandr Kalenik 2024-11-01 16:34:28 +01:00 committed by Tim Flynn
parent 7dc11050f2
commit 4a1e109678
Notes: github-actions[bot] 2024-11-01 17:12:08 +00:00
6 changed files with 147 additions and 73 deletions

View file

@ -0,0 +1,2 @@
Lorem
Lorem ip

View file

@ -0,0 +1,25 @@
<script src="include.js"></script>
<style>
div {
font-size: 20px;
}
</style>
<div id="text">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sapien velit, sagittis vel mauris eget, placerat mattis arcu. Praesent ac pharetra dolor.</div>
<script>
test(() => {
// Select word "Lorem" by dispatching double click event
internals.doubleclick(20, 20);
// Should be equal to "Lorem"
println(window.getSelection());
const text = document.getElementById("text");
let modifiers = 1 << 2; // Mod_Shift
internals.sendKey(text, "Right", modifiers);
internals.sendKey(text, "Right", modifiers);
internals.sendKey(text, "Right", modifiers);
// Should be equal to "Lorem ip"
println(window.getSelection());
});
</script>

View file

@ -4,8 +4,6 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibUnicode/CharacterTypes.h>
#include <LibUnicode/Segmenter.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/EditingHostManager.h>
#include <LibWeb/DOM/Range.h>
@ -132,96 +130,33 @@ void EditingHostManager::move_cursor_to_end(CollapseSelection collapse)
void EditingHostManager::increment_cursor_position_offset(CollapseSelection collapse)
{
auto selection = m_document->get_selection();
auto node = selection->anchor_node();
if (!node || !is<DOM::Text>(*node))
if (!selection)
return;
auto& text_node = static_cast<DOM::Text&>(*node);
if (auto offset = text_node.grapheme_segmenter().next_boundary(selection->focus_offset()); offset.has_value()) {
if (collapse == CollapseSelection::Yes) {
MUST(selection->collapse(*node, *offset));
m_document->reset_cursor_blink_cycle();
} else {
MUST(selection->set_base_and_extent(*node, selection->anchor_offset(), *node, *offset));
}
}
selection->move_offset_to_next_character(collapse == CollapseSelection::Yes);
}
void EditingHostManager::decrement_cursor_position_offset(CollapseSelection collapse)
{
auto selection = m_document->get_selection();
auto node = selection->anchor_node();
if (!node || !is<DOM::Text>(*node)) {
if (!selection)
return;
}
auto& text_node = static_cast<DOM::Text&>(*node);
if (auto offset = text_node.grapheme_segmenter().previous_boundary(selection->focus_offset()); offset.has_value()) {
if (collapse == CollapseSelection::Yes) {
MUST(selection->collapse(*node, *offset));
m_document->reset_cursor_blink_cycle();
} else {
MUST(selection->set_base_and_extent(*node, selection->anchor_offset(), *node, *offset));
}
}
selection->move_offset_to_previous_character(collapse == CollapseSelection::Yes);
}
void EditingHostManager::increment_cursor_position_to_next_word(CollapseSelection collapse)
{
auto selection = m_document->get_selection();
auto node = selection->anchor_node();
if (!node || !is<DOM::Text>(*node)) {
if (!selection)
return;
}
auto& text_node = static_cast<DOM::Text&>(*node);
while (true) {
auto focus_offset = selection->focus_offset();
if (focus_offset == text_node.data().bytes_as_string_view().length()) {
return;
}
if (auto offset = text_node.word_segmenter().next_boundary(focus_offset); offset.has_value()) {
auto word = text_node.data().code_points().substring_view(focus_offset, *offset - focus_offset);
if (collapse == CollapseSelection::Yes) {
MUST(selection->collapse(node, *offset));
m_document->reset_cursor_blink_cycle();
} else {
MUST(selection->set_base_and_extent(*node, selection->anchor_offset(), *node, *offset));
}
if (Unicode::Segmenter::should_continue_beyond_word(word))
continue;
}
break;
}
selection->move_offset_to_next_character(collapse == CollapseSelection::Yes);
}
void EditingHostManager::decrement_cursor_position_to_previous_word(CollapseSelection collapse)
{
auto selection = m_document->get_selection();
auto node = selection->anchor_node();
if (!node || !is<DOM::Text>(*node)) {
if (!selection)
return;
}
auto& text_node = static_cast<DOM::Text&>(*node);
while (true) {
auto focus_offset = selection->focus_offset();
if (auto offset = text_node.word_segmenter().previous_boundary(focus_offset); offset.has_value()) {
auto word = text_node.data().code_points().substring_view(focus_offset, focus_offset - *offset);
if (collapse == CollapseSelection::Yes) {
MUST(selection->collapse(node, *offset));
m_document->reset_cursor_blink_cycle();
} else {
MUST(selection->set_base_and_extent(*node, selection->anchor_offset(), *node, *offset));
}
if (Unicode::Segmenter::should_continue_beyond_word(word))
continue;
}
break;
}
selection->move_offset_to_previous_word(collapse == CollapseSelection::Yes);
}
void EditingHostManager::handle_delete(DeleteDirection direction)

View file

@ -1023,6 +1023,22 @@ EventResult EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u
FIRE(input_event(UIEvents::EventNames::input, UIEvents::InputTypes::insertText, m_navigable, code_point));
return EventResult::Handled;
}
} else if (auto selection = document->get_selection(); selection && !selection->is_collapsed()) {
if (modifiers & UIEvents::Mod_Shift) {
if (key == UIEvents::KeyCode::Key_Right) {
if (modifiers & UIEvents::Mod_PlatformWordJump)
selection->move_offset_to_next_word(false);
else
selection->move_offset_to_next_character(false);
return EventResult::Handled;
} else if (key == UIEvents::KeyCode::Key_Left) {
if (modifiers & UIEvents::Mod_PlatformWordJump)
selection->move_offset_to_previous_word(false);
else
selection->move_offset_to_previous_character(false);
return EventResult::Handled;
}
}
}
// FIXME: Implement scroll by line and by page instead of approximating the behavior of other browsers.

View file

@ -4,11 +4,13 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibUnicode/Segmenter.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/SelectionPrototype.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Position.h>
#include <LibWeb/DOM/Range.h>
#include <LibWeb/DOM/Text.h>
#include <LibWeb/Selection/Selection.h>
namespace Web::Selection {
@ -484,4 +486,92 @@ JS::GCPtr<DOM::Position> Selection::cursor_position() const
return nullptr;
}
void Selection::move_offset_to_next_character(bool collapse_selection)
{
auto anchor_node = this->anchor_node();
if (!anchor_node || !is<DOM::Text>(*anchor_node))
return;
auto& text_node = static_cast<DOM::Text&>(*anchor_node);
if (auto offset = text_node.grapheme_segmenter().next_boundary(focus_offset()); offset.has_value()) {
if (collapse_selection) {
MUST(collapse(*anchor_node, *offset));
m_document->reset_cursor_blink_cycle();
} else {
MUST(set_base_and_extent(*anchor_node, anchor_offset(), *anchor_node, *offset));
}
}
}
void Selection::move_offset_to_previous_character(bool collapse_selection)
{
auto anchor_node = this->anchor_node();
if (!anchor_node || !is<DOM::Text>(*anchor_node))
return;
auto& text_node = static_cast<DOM::Text&>(*anchor_node);
if (auto offset = text_node.grapheme_segmenter().previous_boundary(focus_offset()); offset.has_value()) {
if (collapse_selection) {
MUST(collapse(*anchor_node, *offset));
m_document->reset_cursor_blink_cycle();
} else {
MUST(set_base_and_extent(*anchor_node, anchor_offset(), *anchor_node, *offset));
}
}
}
void Selection::move_offset_to_next_word(bool collapse_selection)
{
auto anchor_node = this->anchor_node();
if (!anchor_node || !is<DOM::Text>(*anchor_node)) {
return;
}
auto& text_node = static_cast<DOM::Text&>(*anchor_node);
while (true) {
auto focus_offset = this->focus_offset();
if (focus_offset == text_node.data().bytes_as_string_view().length()) {
return;
}
if (auto offset = text_node.word_segmenter().next_boundary(focus_offset); offset.has_value()) {
auto word = text_node.data().code_points().substring_view(focus_offset, *offset - focus_offset);
if (collapse_selection) {
MUST(collapse(anchor_node, *offset));
m_document->reset_cursor_blink_cycle();
} else {
MUST(set_base_and_extent(*anchor_node, this->anchor_offset(), *anchor_node, *offset));
}
if (Unicode::Segmenter::should_continue_beyond_word(word))
continue;
}
break;
}
}
void Selection::move_offset_to_previous_word(bool collapse_selection)
{
auto anchor_node = this->anchor_node();
if (!anchor_node || !is<DOM::Text>(*anchor_node)) {
return;
}
auto& text_node = static_cast<DOM::Text&>(*anchor_node);
while (true) {
auto focus_offset = this->focus_offset();
if (auto offset = text_node.word_segmenter().previous_boundary(focus_offset); offset.has_value()) {
auto word = text_node.data().code_points().substring_view(focus_offset, focus_offset - *offset);
if (collapse_selection) {
MUST(collapse(anchor_node, *offset));
m_document->reset_cursor_blink_cycle();
} else {
MUST(set_base_and_extent(*anchor_node, anchor_offset(), *anchor_node, *offset));
}
if (Unicode::Segmenter::should_continue_beyond_word(word))
continue;
}
break;
}
}
}

View file

@ -61,6 +61,12 @@ public:
// Non-standard
JS::GCPtr<DOM::Position> cursor_position() const;
// Non-standard
void move_offset_to_next_character(bool collapse_selection);
void move_offset_to_previous_character(bool collapse_selection);
void move_offset_to_next_word(bool collapse_selection);
void move_offset_to_previous_word(bool collapse_selection);
private:
Selection(JS::NonnullGCPtr<JS::Realm>, JS::NonnullGCPtr<DOM::Document>);