Skip to content

Commit

Permalink
DOM: Preserve selection during moveBefore()
Browse files Browse the repository at this point in the history
This CL preserves visual selection computation when a child is removed
from the DOM *during* a state-preserving atomic move.

This is particularly relevant when the moved node is either the anchor
or focus node of the selection. This CL also adds a bunch of tests to
confirm that selection is properly preserved when:
 - Selection anchor is moved in the DOM to include more nodes
 - Selection focus is moved in the DOM to exclude once-intersecting
   nodes
 - Nodes that are entirely within a selection (not anchor or focus) are
   rearranged/moved still within that selection

[email protected]

Bug: 40150299
Change-Id: Ib4a4cc8531f954d9864bd62399b297b5c8aa870b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5961984
Commit-Queue: Dominic Farolino <[email protected]>
Reviewed-by: Noam Rosenthal <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1375807}
  • Loading branch information
domfarolino authored and chromium-wpt-export-bot committed Oct 30, 2024
1 parent 64d86b8 commit 22b9fd9
Showing 1 changed file with 66 additions and 3 deletions.
69 changes: 66 additions & 3 deletions dom/nodes/moveBefore/tentative/selection-preserve.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
</body>

<script>
'use strict';
'use strict';

async function select_range(t, anchorNode, extentNode) {
async function select_range(t, anchorNode, focusNode) {
await new test_driver.Actions()
.pointerMove(0, 0, {origin: anchorNode})
.pointerDown()
.pointerMove(extentNode.clientWidth, extentNode.clientHeight, {origin: extentNode})
.pointerMove(focusNode.clientWidth, focusNode.clientHeight, {origin: focusNode})
.pointerUp()
.send();
}
Expand Down Expand Up @@ -71,5 +71,68 @@
new_parent.moveBefore(text, null);
assert_equals(getSelection().anchorNode, text.firstChild);
}, "moveBefore should not reset selection with preceding and following text");

const kHTML = `
<div id=grandparentDiv>
<span id=grandparentParagraph>Grandparent paragraph</span>
<div id=parentDiv>
<span id=parentParagraph>Parent paragraph</span>
<div id=childDiv>
<span id=childParagraph1>Child paragraph one</span>
<span id=childParagraph2>Paragraph two</span>
</div>
</div>
</div>
`;

// Selection spans parent->child.
promise_test(async t => {
document.body.innerHTML = kHTML;
getSelection().removeAllRanges();
await select_range(t, parentParagraph, childParagraph1);

grandparentDiv.moveBefore(parentDiv, grandparentParagraph);
assert_equals(getSelection().anchorNode, parentParagraph.firstChild);
assert_equals(getSelection().focusNode, childParagraph1.firstChild);
}, "moveBefore should not reset selection that enters a subtree, when the " +
"whole selection is moved");

// Selection anchor node is moved upwards in the DOM, to suddenly intersect more
// nodes.
promise_test(async t => {
document.body.innerHTML = kHTML;
getSelection().removeAllRanges();
await select_range(t, parentParagraph, childParagraph1);

grandparentDiv.moveBefore(parentParagraph, grandparentParagraph);
assert_equals(getSelection().anchorNode, parentParagraph.firstChild);
assert_equals(getSelection().focusNode, childParagraph1.firstChild);
assert_true(getSelection().getRangeAt(0).intersectsNode(grandparentParagraph));
}, "moveBefore anchor node moved up to expand selection and absorb nodes");

// Intersecting nodes are moved *out* of the selection.
promise_test(async t => {
document.body.innerHTML = kHTML;
getSelection().removeAllRanges();
await select_range(t, grandparentParagraph, childParagraph2);

grandparentDiv.moveBefore(childParagraph1, grandparentParagraph);
assert_equals(getSelection().anchorNode, grandparentParagraph.firstChild);
assert_equals(getSelection().focusNode, childParagraph2.firstChild);
assert_false(getSelection().getRangeAt(0).intersectsNode(childParagraph1));
}, "moveBefore move intersecting nodes out of a selection");

// Selection focus node is moved upwards in the DOM, shrinking the selection and
// excluding once-intersecting nodes.
promise_test(async t => {
document.body.innerHTML = kHTML;
getSelection().removeAllRanges();
await select_range(t, grandparentParagraph, childParagraph2);

parentDiv.moveBefore(childDiv, parentParagraph);
assert_equals(getSelection().anchorNode, grandparentParagraph.firstChild);
assert_equals(getSelection().focusNode, childParagraph2.firstChild);
assert_false(getSelection().getRangeAt(0).intersectsNode(parentParagraph));
}, "moveBefore focus node moved up to shrink selection and exclude nodes");
</script>
</html>

0 comments on commit 22b9fd9

Please sign in to comment.