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
396 lines
11 KiB
HTML
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>
|