From baf2063e31163ce502c5e40bf39af1e6ac86139f Mon Sep 17 00:00:00 2001 From: Psychpsyo Date: Thu, 3 Jul 2025 18:33:11 +0200 Subject: [PATCH] LibWeb: Fix selection when start node is inside end node Fixes a regression introduced in bc8870d019ee7a7087cc2c784058ed1313dc6011 (in a performant way this time) --- .../LibWeb/Painting/ViewportPaintable.cpp | 22 +++++++++++------- .../selection-start-in-end-node-ref.html | 7 ++++++ .../selection-start-in-end-node-ref.png | Bin 0 -> 6814 bytes .../input/selection-start-in-end-node-2.html | 10 ++++++++ .../input/selection-start-in-end-node-3.html | 10 ++++++++ .../input/selection-start-in-end-node.html | 10 ++++++++ 6 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 Tests/LibWeb/Screenshot/expected/selection-start-in-end-node-ref.html create mode 100644 Tests/LibWeb/Screenshot/images/selection-start-in-end-node-ref.png create mode 100644 Tests/LibWeb/Screenshot/input/selection-start-in-end-node-2.html create mode 100644 Tests/LibWeb/Screenshot/input/selection-start-in-end-node-3.html create mode 100644 Tests/LibWeb/Screenshot/input/selection-start-in-end-node.html diff --git a/Libraries/LibWeb/Painting/ViewportPaintable.cpp b/Libraries/LibWeb/Painting/ViewportPaintable.cpp index 547baa09ce6..22b30d0af93 100644 --- a/Libraries/LibWeb/Painting/ViewportPaintable.cpp +++ b/Libraries/LibWeb/Painting/ViewportPaintable.cpp @@ -362,19 +362,23 @@ void ViewportPaintable::recompute_selection_states(DOM::Range& range) // 4. Mark the selection end node as End (if text) or Full (if anything else). if (auto* paintable = end_container->paintable(); paintable && !range.end().node->is_inert()) { - if (is(*end_container) || end_container->is_ancestor_of(start_container)) { + if (is(*end_container)) paintable->set_selection_state(SelectionState::End); - } else { - paintable->for_each_in_inclusive_subtree([&](auto& layout_node) { - if (!layout_node.dom_node() || !layout_node.dom_node()->is_inert()) - layout_node.set_selection_state(SelectionState::Full); - return TraversalDecision::Continue; - }); - } + else + paintable->set_selection_state(SelectionState::Full); } // 5. Mark the nodes between start node and end node (in tree order) as Full. - for (auto* node = start_container->next_in_pre_order(); node && node != end_container; node = node->next_in_pre_order()) { + // NOTE: If the start node is a descendant of the end node, we cannot traverse from it to the end node since the end node is before it in tree order. + // Instead, we need to stop traversal somewhere inside the end node, or right after it. + DOM::Node* stop_at; + if (start_container->is_descendant_of(end_container)) { + stop_at = end_container->child_at_index(range.end_offset()); + } else { + stop_at = end_container; + } + + for (auto* node = start_container->next_in_pre_order(); node && node != stop_at; node = node->next_in_pre_order(end_container)) { if (node->is_inert()) continue; if (auto* paintable = node->paintable()) diff --git a/Tests/LibWeb/Screenshot/expected/selection-start-in-end-node-ref.html b/Tests/LibWeb/Screenshot/expected/selection-start-in-end-node-ref.html new file mode 100644 index 00000000000..d647397b779 --- /dev/null +++ b/Tests/LibWeb/Screenshot/expected/selection-start-in-end-node-ref.html @@ -0,0 +1,7 @@ + + + diff --git a/Tests/LibWeb/Screenshot/images/selection-start-in-end-node-ref.png b/Tests/LibWeb/Screenshot/images/selection-start-in-end-node-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..7fc8f2747c682d75c7dd6220a369b2e149f3e0cb GIT binary patch literal 6814 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 + + +End Node Start Node NOT SELECTED + diff --git a/Tests/LibWeb/Screenshot/input/selection-start-in-end-node-3.html b/Tests/LibWeb/Screenshot/input/selection-start-in-end-node-3.html new file mode 100644 index 00000000000..ecbc40c1d2a --- /dev/null +++ b/Tests/LibWeb/Screenshot/input/selection-start-in-end-node-3.html @@ -0,0 +1,10 @@ + + + +End Node Start Node NOT SELECTED + diff --git a/Tests/LibWeb/Screenshot/input/selection-start-in-end-node.html b/Tests/LibWeb/Screenshot/input/selection-start-in-end-node.html new file mode 100644 index 00000000000..56741b36c51 --- /dev/null +++ b/Tests/LibWeb/Screenshot/input/selection-start-in-end-node.html @@ -0,0 +1,10 @@ + + + +End Node Start Node NOT SELECTED +