Files
sousa-gecko/dom/base/test/test_user_select.html
T
Masayuki Nakano 82a139a8f0 Bug 1998858 - part 2: Make Selection stop splitting user selected range to exclude non-selectable nodes r=smaug,emilio,edgar,TYLin,layout-reviewers
The other browsers do not support multiple selection ranges.  Therefore,
they never split selection ranges to exclude non-selectable nodes.
To align the behavior, we should do so.  However, to keep current
look of `Selection` which is similar to Chrome's behavior, we should
make the selection painters ignore normal selections if their content
is not selectable.

Note that this patch allows painting selected text when there are some
IME selections even if `user-select` is `none` because the normal
selection is also a part of IME selections.  Although our widget
currently do not use non-collapsed normal selection during a
composition, it may be changed to support new IME in the future.

Differential Revision: https://phabricator.services.mozilla.com/D272187
2025-11-21 00:16:55 +00:00

396 lines
11 KiB
HTML

<!DOCTYPE>
<html>
<head>
<title>user-select selection tests</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<style type="text/css">
@font-face {
font-family: Ahem;
src: url("Ahem.ttf");
}
body { font-family: Ahem; font-size: 20px; }
s, .non-selectable { user-select: none; }
n { display: none; }
a { position:absolute; bottom: 0; right:0; }
.text { user-select: text; }
</style>
</head>
<body>
<div id="test1">aaaaaaa<s>bbbbbbbb</s>ccccccc</div>
<div id="test2"><s>aaaaaaa</s>bbbbbbbbccccccc</div>
<div id="test3">aaaaaaabbbbbbbb<s>ccccccc</s></div>
<div id="test4">aaaaaaa<x><s>bbbbbbbb</s></x>ccccccc</div>
<div id="test5"><x><s>aaaaaaa</s></x>bbbbbbbbccccccc</div>
<div id="test6">aaaaaaabbbbbbbb<x><s>ccccccc</s></x></div>
<div id="test7">aaaaaaa<x><s><n>bbbb</n>bbbb</s></x>ccccccc</div>
<div id="test8"><x><s>aa<n>aaa</n>aa</s></x>bbbbbbbbccccccc</div>
<div id="test9">aaaaaaabbbbbbbb<x><s>cc<n>ccccc</n></s></x></div>
<div id="testA">aaaaaaa<n>bbb<s>bbbbb</s></n>ccccccc</div>
<div id="testB"><n><s>aaaa</s>aaa</n>bbbbbbbbccccccc</div>
<div id="testC">aaaaaaabbbbbbbb<n>cc<s>c</s>cccc</n></div>
<div id="testE">aaa<s id="testEc1">aaaa<a class="text">bbbb</a>dd<a>cccc</a>ddddddd</s>eeee</div>
<div id="testI">aaa<span contenteditable="true" spellcheck="false">bbb</span><s>ccc</s>ddd</div>
<div id="testF">aaaa
<div class="non-selectable">x</div>
<div class="non-selectable">x</div>
<div class="non-selectable">x</div>
bbbb</div>
<div id="testG" style="white-space:pre">aaaa
<div class="non-selectable">x</div>
<div class="non-selectable">x</div>
<div class="non-selectable">x</div>
bbbb</div>
<div id="testH" style="white-space:pre">aaaa
<div class="non-selectable">x</div><input>
bbbbbbb</div>
<iframe id="testD" srcdoc="<body>aaaa<span style='user-select:none'>bbbb</span>cccc"></iframe>
<pre id="test">
<script class="testbody" type="text/javascript">
function test()
{
const excludeNonSelectableNodes = SpecialPowers.getBoolPref("dom.selection.exclude_non_selectable_nodes");
function clear(w)
{
var sel = (w ? w : window).getSelection();
sel.removeAllRanges();
}
function doneTest(e)
{
// We hide the elements we're done with so that later tests
// are inside the rather narrow iframe mochitest gives us.
// It matters for synthesizeMouse event tests.
e.style.display = 'none';
e.offsetHeight;
}
function dragSelect(e, x1, x2, x3)
{
dir = x2 > x1 ? 1 : -1;
synthesizeMouse(e, x1, 5, { type: "mousedown" });
synthesizeMouse(e, x1 + dir, 5, { type: "mousemove" });
if (x3)
synthesizeMouse(e, x3, 5, { type: "mousemove" });
synthesizeMouse(e, x2 - dir, 5, { type: "mousemove" });
synthesizeMouse(e, x2, 5, { type: "mouseup" });
}
function shiftClick(e, x)
{
synthesizeMouse(e, x, 5, { type: "mousedown", shiftKey: true });
synthesizeMouse(e, x, 5, { type: "mouseup", shiftKey: true });
}
function init(arr, e)
{
clear();
var sel = window.getSelection();
for (i = 0; i < arr.length; ++i) {
var data = arr[i];
var r = new Range()
r.setStart(node(e, data[0]), data[1]);
r.setEnd(node(e, data[2]), data[3]);
sel.addRange(r);
}
}
function NL(s) { return s.replace(/(\r\n|\n\r|\r)/g, '\n'); }
function checkText(text, e)
{
var sel = window.getSelection();
is(NL(sel.toString()), text, e.id + ": selected text")
}
function checkRangeText(text, index)
{
var r = window.getSelection().getRangeAt(index);
is(NL(r.toString()), text, e.id + ": range["+index+"].toString()")
}
function node(e, arg)
{
if (typeof arg == "number")
return arg == -1 ? e : e.childNodes[arg];
return arg;
}
function checkRangeCount(n, e)
{
var sel = window.getSelection();
is(sel.rangeCount, n, e.id + ": Selection range count");
}
function checkRange(i, expected, e) {
var sel = window.getSelection();
var r = sel.getRangeAt(i);
is(r.startContainer, node(e, expected[0]), e.id + ": range["+i+"].startContainer");
is(r.startOffset, expected[1], e.id + ": range["+i+"].startOffset");
is(r.endContainer, node(e, expected[2]), e.id + ": range["+i+"].endContainer");
is(r.endOffset, expected[3], e.id + ": range["+i+"].endOffset");
}
function checkRanges(arr, e)
{
checkRangeCount(arr.length, e);
for (i = 0; i < arr.length; ++i) {
var expected = arr[i];
checkRange(i, expected, e);
}
}
// ======================================================
// ================== dragSelect tests ==================
// ======================================================
var e = document.getElementById('test1');
dragSelect(e, 20, 340);
checkText('aaaaaacc', e);
checkRanges(
excludeNonSelectableNodes ? [[0,1,-1,1], [2,0,2,2]] : [[0,1,2,2]],
e
);
clear();
dragSelect(e, 20, 260, 120);
checkText('aaaaa', e);
checkRanges([[0,1,0,6]], e);
doneTest(e);
clear();
e = document.getElementById('test2');
dragSelect(e, 20, 340);
checkText('', e);
checkRanges([], e);
clear();
dragSelect(e, 340, 20, 141);
checkText('bbbbbbbbcc', e);
checkRanges([[1,0,1,10]], e);
// #test2 is used again below
clear();
e = document.getElementById('test3');
dragSelect(e, 20, 340, 295);
checkText('aaaaaabbbbbbbb', e);
checkRanges([[0,1,0,15]], e);
// #test3 is used again below
clear();
e = document.getElementById('test4');
dragSelect(e, 20, 340);
checkText('aaaaaacc', e);
checkRanges(
excludeNonSelectableNodes ? [[0,1,1,0], [2,0,2,2]] : [[0,1,2,2]],
e
);
doneTest(e);
clear();
e = document.getElementById('test5');
dragSelect(e, 340, 20, 141);
checkText('bbbbbbbbcc', e);
checkRanges([[1,0,1,10]], e);
doneTest(e);
clear();
e = document.getElementById('test6');
dragSelect(e, 20, 340, 295);
checkText('aaaaaabbbbbbbb', e);
checkRanges([[0,1,0,15]], e);
doneTest(e);
clear();
e = document.getElementById('test7');
dragSelect(e, 20, 340);
checkText('aaaaaacccccc', e);
checkRanges(
excludeNonSelectableNodes ? [[0,1,1,0], [2,0,2,6]] : [[0,1,2,6]],
e
);
doneTest(e);
clear();
e = document.getElementById('test8');
dragSelect(e, 340, 20, 140);
checkText('bbbbbccccc', e);
checkRanges([[1,3,1,13]], e);
doneTest(e);
clear();
e = document.getElementById('test9');
dragSelect(e, 20, 340, 295);
checkText('aaaaaabbbbbbbb', e);
checkRanges([[0,1,0,15]], e);
doneTest(e);
clear();
e = document.getElementById('testA');
dragSelect(e, 20, 340);
checkText('aaaaaaccccccc', e);
checkRanges([[0,1,2,7]], e);
checkRangeText('aaaaaabbbbbbbbccccccc', 0);
doneTest(e);
clear();
e = document.getElementById('testB');
dragSelect(e, 340, 20, 140);
checkText('bbbbbbbccccccc', e);
checkRanges([[1,1,1,15]], e);
doneTest(e);
clear();
e = document.getElementById('testE');
dragSelect(e, 20, 360, 295);
checkText('aa\nbbbb\nee', e);
if (excludeNonSelectableNodes) {
checkRangeCount(3, e);
checkRange(0, [0,1,-1,1], e);
checkRange(1, [1,0,-1,2], e.children[0]);
checkRange(2, [2,0,2,2], e);
} else {
checkRangeCount(1, e);
checkRanges([[0,1,2,2]], e);
}
doneTest(e);
clear();
e = document.getElementById('testI');
dragSelect(e, 200, 80);
checkText('bbd', e);
checkRangeCount(excludeNonSelectableNodes ? 2 : 1, e);
doneTest(e);
// ======================================================
// ================== shift+click tests =================
// ======================================================
// test extending a selection that starts in a -moz-user-select:none node
clear();
e = document.getElementById('test2');
init([[0,0,0,1]], e);
checkRangeText('aaaaaaa', 0);
checkText('', e);
shiftClick(e, 340);
checkText('bbbbbbbbcc', e);
if (excludeNonSelectableNodes) {
checkRangeText('bbbbbbbbcc', 0);
checkRanges([[-1,1,1,10]], e);
} else {
checkRangeText('aaaaaaabbbbbbbbcc', 0);
checkRanges([[0,0,1,10]], e);
}
doneTest(e);
// test extending a selection that end in a -moz-user-select:none node
clear();
e = document.getElementById('test3');
init([[1,0,1,1]], e);
checkRangeText('ccccccc', 0);
checkText('', e);
shiftClick(e, 20);
checkRangeText('aaaaaabbbbbbbb', 0);
checkText('aaaaaabbbbbbbb', e);
checkRanges(
excludeNonSelectableNodes ? [[0,1,-1,1]] : [[0,1,1,0]],
e
);
doneTest(e);
clear();
e = document.getElementById('testF');
synthesizeMouse(e, 1, 1, {});
synthesizeMouse(e, 400, 100, { shiftKey: true });
checkText("aaaa bbbb", e);
checkRanges(
excludeNonSelectableNodes ? [[0,0,-1,1],[6,0,6,5]] : [[0,0,6,5]],
e
);
doneTest(e);
clear();
e = document.getElementById('testG');
synthesizeMouse(e, 1, 1, {});
synthesizeMouse(e, 400, 180, { shiftKey: true });
checkText("aaaa\n\n\n\nbbbb", e);
checkRanges(
excludeNonSelectableNodes ? [[0,0,-1,1],[2,0,-1,3],[4,0,-1,5],[6,0,6,5]] : [[0,0,6,5]],
e
);
doneTest(e);
clear();
e = document.getElementById('testH');
synthesizeMouse(e, 1, 1, {});
synthesizeMouse(e, 30, 90, { shiftKey: true });
synthesizeMouse(e, 50, 90, { shiftKey: true });
synthesizeMouse(e, 70, 90, { shiftKey: true });
checkText("aaaa\n\nbbb", e);
checkRanges(
excludeNonSelectableNodes ? [[0,0,-1,1],[-1,2,3,4]] : [[0,0,3,4]],
e
);
doneTest(e);
// ======================================================
// ==================== Script tests ====================
// ======================================================
clear();
e = document.getElementById('testD');
clear(e.contentWindow);
sel = e.contentWindow.getSelection();
sel.selectAllChildren(e.contentDocument.body);
is(window.getSelection().rangeCount, 0, "testD: no selection in outer window");
is(sel.toString(), 'aaaacccc', "testD: scripted selection");
is(sel.rangeCount, 1, "testD: scripted selection isn't filtered");
is(sel.getRangeAt(0).toString(), 'aaaabbbbcccc', "testD: scripted selection isn't filtered");
// ======================================================
// ================== Kbd command tests =================
// ======================================================
clear();
e = document.getElementById('testD');
clear(e.contentWindow);
e.contentWindow.focus();
synthesizeKey("a", { accelKey:true }, e.contentWindow);
sel = e.contentWindow.getSelection();
is(window.getSelection().rangeCount, 0, "testD: no selection in outer window");
is(sel.toString(), 'aaaacccc', "testD: kbd selection");
if (excludeNonSelectableNodes) {
is(sel.rangeCount, 2, "testD: kbd selection is filtered");
is(sel.getRangeAt(0).toString(), 'aaaa', "testD: kbd selection is filtered");
is(sel.getRangeAt(1).toString(), 'cccc', "testD: kbd selection is filtered");
} else {
is(sel.rangeCount, 1, "testD: kbd selection is filtered");
is(sel.getRangeAt(0).toString(), 'aaaabbbbcccc', "testD: kbd selection is filtered");
}
doneTest(e);
clear();
SimpleTest.finish();
}
// These tests depends on the Ahem font being loaded and rendered so wait for
// font to load, then wait a frame for them to be rendered too.
window.onload = function() {
document.fonts.ready.then(function() {
requestAnimationFrame(test);
});
};
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>