mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-30 04:39:06 +00:00
LibWeb: Fix ability to modify selection outside of inputs using keyboard
Fixes regression introduced in a8077f79cc
This commit is contained in:
parent
7dc11050f2
commit
4a1e109678
Notes:
github-actions[bot]
2024-11-01 17:12:08 +00:00
Author: https://github.com/kalenikaliaksandr
Commit: 4a1e109678
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2108
Reviewed-by: https://github.com/trflynn89 ✅
6 changed files with 147 additions and 73 deletions
2
Tests/LibWeb/Text/expected/select-text.txt
Normal file
2
Tests/LibWeb/Text/expected/select-text.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Lorem
|
||||||
|
Lorem ip
|
25
Tests/LibWeb/Text/input/select-text.html
Normal file
25
Tests/LibWeb/Text/input/select-text.html
Normal 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>
|
|
@ -4,8 +4,6 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <LibUnicode/CharacterTypes.h>
|
|
||||||
#include <LibUnicode/Segmenter.h>
|
|
||||||
#include <LibWeb/DOM/Document.h>
|
#include <LibWeb/DOM/Document.h>
|
||||||
#include <LibWeb/DOM/EditingHostManager.h>
|
#include <LibWeb/DOM/EditingHostManager.h>
|
||||||
#include <LibWeb/DOM/Range.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)
|
void EditingHostManager::increment_cursor_position_offset(CollapseSelection collapse)
|
||||||
{
|
{
|
||||||
auto selection = m_document->get_selection();
|
auto selection = m_document->get_selection();
|
||||||
auto node = selection->anchor_node();
|
if (!selection)
|
||||||
if (!node || !is<DOM::Text>(*node))
|
|
||||||
return;
|
return;
|
||||||
|
selection->move_offset_to_next_character(collapse == CollapseSelection::Yes);
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditingHostManager::decrement_cursor_position_offset(CollapseSelection collapse)
|
void EditingHostManager::decrement_cursor_position_offset(CollapseSelection collapse)
|
||||||
{
|
{
|
||||||
auto selection = m_document->get_selection();
|
auto selection = m_document->get_selection();
|
||||||
auto node = selection->anchor_node();
|
if (!selection)
|
||||||
if (!node || !is<DOM::Text>(*node)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
selection->move_offset_to_previous_character(collapse == CollapseSelection::Yes);
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditingHostManager::increment_cursor_position_to_next_word(CollapseSelection collapse)
|
void EditingHostManager::increment_cursor_position_to_next_word(CollapseSelection collapse)
|
||||||
{
|
{
|
||||||
auto selection = m_document->get_selection();
|
auto selection = m_document->get_selection();
|
||||||
auto node = selection->anchor_node();
|
if (!selection)
|
||||||
if (!node || !is<DOM::Text>(*node)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
selection->move_offset_to_next_character(collapse == CollapseSelection::Yes);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditingHostManager::decrement_cursor_position_to_previous_word(CollapseSelection collapse)
|
void EditingHostManager::decrement_cursor_position_to_previous_word(CollapseSelection collapse)
|
||||||
{
|
{
|
||||||
auto selection = m_document->get_selection();
|
auto selection = m_document->get_selection();
|
||||||
auto node = selection->anchor_node();
|
if (!selection)
|
||||||
if (!node || !is<DOM::Text>(*node)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
selection->move_offset_to_previous_word(collapse == CollapseSelection::Yes);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditingHostManager::handle_delete(DeleteDirection direction)
|
void EditingHostManager::handle_delete(DeleteDirection direction)
|
||||||
|
|
|
@ -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));
|
FIRE(input_event(UIEvents::EventNames::input, UIEvents::InputTypes::insertText, m_navigable, code_point));
|
||||||
return EventResult::Handled;
|
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.
|
// FIXME: Implement scroll by line and by page instead of approximating the behavior of other browsers.
|
||||||
|
|
|
@ -4,11 +4,13 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <LibUnicode/Segmenter.h>
|
||||||
#include <LibWeb/Bindings/Intrinsics.h>
|
#include <LibWeb/Bindings/Intrinsics.h>
|
||||||
#include <LibWeb/Bindings/SelectionPrototype.h>
|
#include <LibWeb/Bindings/SelectionPrototype.h>
|
||||||
#include <LibWeb/DOM/Document.h>
|
#include <LibWeb/DOM/Document.h>
|
||||||
#include <LibWeb/DOM/Position.h>
|
#include <LibWeb/DOM/Position.h>
|
||||||
#include <LibWeb/DOM/Range.h>
|
#include <LibWeb/DOM/Range.h>
|
||||||
|
#include <LibWeb/DOM/Text.h>
|
||||||
#include <LibWeb/Selection/Selection.h>
|
#include <LibWeb/Selection/Selection.h>
|
||||||
|
|
||||||
namespace Web::Selection {
|
namespace Web::Selection {
|
||||||
|
@ -484,4 +486,92 @@ JS::GCPtr<DOM::Position> Selection::cursor_position() const
|
||||||
return nullptr;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,12 @@ public:
|
||||||
// Non-standard
|
// Non-standard
|
||||||
JS::GCPtr<DOM::Position> cursor_position() const;
|
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:
|
private:
|
||||||
Selection(JS::NonnullGCPtr<JS::Realm>, JS::NonnullGCPtr<DOM::Document>);
|
Selection(JS::NonnullGCPtr<JS::Realm>, JS::NonnullGCPtr<DOM::Document>);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue