LibWeb: Fix partially selecting non-text nodes

Steps 4 and 5 were swapped since marking all the nodes between the start
and end of the selection now also marks the end node as full, even if it
should be marked as End.
There could be extra logic to avoid marking it if it is a text node, but
this seems easier.

As a whole, this fixes partially selected non-text nodes. In such cases,
where the selection starts or ends inside a node with descendants, it is
impossible to just select from the start node to the end node since that
would select all descendants of the start node and none of the end node.
Previously, this was only half considered and only if the start node was
a descendant of the end node.
This commit is contained in:
Psychpsyo 2025-07-04 22:21:34 +02:00 committed by Sam Atkins
parent 66a36050bd
commit 80b629578e
4 changed files with 34 additions and 17 deletions

View File

@ -370,30 +370,30 @@ void ViewportPaintable::recompute_selection_states(DOM::Range& range)
paintable->set_selection_state(SelectionState::Full);
}
// 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<DOM::Text>(*end_container))
paintable->set_selection_state(SelectionState::End);
else
paintable->set_selection_state(SelectionState::Full);
// 4. Mark the nodes between the start and end of the selection as Full.
auto* start_at = start_container->child_at_index(range.start_offset());
// If the start container has no child at that index, we need to start on the node right after the start container.
if (!start_at) {
if (auto* last_child = start_container->last_child()) {
start_at = last_child->next_in_pre_order();
} else {
start_at = start_container->next_in_pre_order();
}
}
// 5. Mark the nodes between start node and end node (in tree order) as Full.
// 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)) {
DOM::Node* stop_at = end_container->child_at_index(range.end_offset());
// Only stop at the end container if it has no children that may need to be included.
for (auto* node = start_at; node && (node != stop_at && !(node == end_container && !end_container->has_children())); node = node->next_in_pre_order(end_container)) {
if (node->is_inert())
continue;
if (auto* paintable = node->paintable())
paintable->set_selection_state(SelectionState::Full);
}
// 5. Mark the selection end node as End if it is a text node.
if (auto* paintable = end_container->paintable(); paintable && !range.end().node->is_inert() && is<DOM::Text>(*end_container)) {
paintable->set_selection_state(SelectionState::End);
}
}
bool ViewportPaintable::handle_mousewheel(Badge<EventHandler>, CSSPixelPoint, unsigned, unsigned, int, int)

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<style>
* {
margin: 0;
}
</style>
<img src="../images/selection-partial-nodes-ref.png">

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta name="fuzzy" content="maxDifference=0-1;totalPixels=0-250">
<link rel="match" href="../expected/selection-partial-nodes-ref.html" />
<div id="start">not selected <span>sel</span><span id="end">ected<span> and not selected</span></span></div>
<script>
let range = document.createRange();
range.setStart(start, 1);
range.setEnd(end, 1);
window.getSelection().addRange(range);
</script>