Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- Manifest: dom/events/test/mochitest.toml
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Tests for checking the behavior of right click clicking outside selection</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
</head>
<body>
<div>Non editable text</div>
<div contenteditable>Editable text</div>
<input value="input value" style="width: 100%" />
<iframe srcdoc="<span>abc</span>"></iframe>
<script>
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(async () => {
function getRangeDescription(range) {
function getNodeDescription(node) {
if (!node) {
return "null";
}
switch (node.nodeType) {
case Node.TEXT_NODE:
case Node.COMMENT_NODE:
case Node.CDATA_SECTION_NODE:
return `${node.nodeName} "${node.data}"`;
case Node.ELEMENT_NODE:
return `<${node.nodeName.toLowerCase()}>`;
default:
return `${node.nodeName}`;
}
}
if (range === null) {
return "null";
}
if (range === undefined) {
return "undefined";
}
return range.startContainer == range.endContainer &&
range.startOffset == range.endOffset
? `(${getNodeDescription(range.startContainer)}, ${range.startOffset})`
: `(${getNodeDescription(range.startContainer)}, ${
range.startOffset
}) - (${getNodeDescription(range.endContainer)}, ${range.endOffset})`;
}
document.addEventListener(
"contextmenu",
event => event.preventDefault(),
{ capture: true }
);
const nonEditableDiv = document.querySelector("div");
const editableDiv = document.querySelector("div[contenteditable]");
const input = document.querySelector("input");
// See also modifying-selection-with-non-primary-mouse-button.tentative.html?secondary for the basic behavior check.
await SpecialPowers.pushPrefEnv({
set: [
["ui.mouse.right_click.collapse_selection.stop_if_non_collapsed_selection", false],
["ui.mouse.right_click.collapse_selection.stop_if_non_editable_node", false],
["ui.mouse.right_click.move_caret.stop_if_in_focused_editable_node", false],
],
});
getSelection().setBaseAndExtent(
nonEditableDiv.firstChild, "Non editable ".length,
nonEditableDiv.firstChild, "Non editable text".length
);
synthesizeMouse(nonEditableDiv, 10, 10, {button: 2});
ok(
getSelection().isCollapsed,
`Selection should be collapsed by a right click when stop_if_non_collapsed_selection pref is false in non-editable text node (${
getRangeDescription(getSelection().getRangeAt(0))
})`
);
getSelection().setBaseAndExtent(
editableDiv.firstChild, "Editable ".length,
editableDiv.firstChild, "Editable text".length
);
synthesizeMouse(editableDiv, 10, 10, {button: 2});
ok(
getSelection().isCollapsed,
`Selection should be collapsed by a right click when stop_if_non_collapsed_selection pref is false in editable text node (${
getRangeDescription(getSelection().getRangeAt(0))
})`
);
input.focus();
input.setSelectionRange("input ".length, "input value".length);
synthesizeMouse(input, 10, 10, {button: 2});
ok(
input.selectionStart == input.selectionEnd,
`Selection in <input> should be collapsed by a right click when stop_if_non_collapsed_selection pref is false (got: ${input.selectionStart} - ${input.selectionEnd})`
);
input.blur();
await SpecialPowers.pushPrefEnv({
set: [
["ui.mouse.right_click.collapse_selection.stop_if_non_collapsed_selection", false],
["ui.mouse.right_click.collapse_selection.stop_if_non_editable_node", true],
["ui.mouse.right_click.move_caret.stop_if_in_focused_editable_node", false],
],
});
getSelection().setBaseAndExtent(
nonEditableDiv.firstChild, "Non editable ".length,
nonEditableDiv.firstChild, "Non editable text".length
);
synthesizeMouse(nonEditableDiv, 10, 10, {button: 2});
ok(
!getSelection().isCollapsed,
`Selection should not be collapsed by a right click when stop_if_non_editable_node pref is true in non-editable text node (${
getRangeDescription(getSelection().getRangeAt(0))
})`
);
getSelection().setBaseAndExtent(
editableDiv.firstChild, "Editable ".length,
editableDiv.firstChild, "Editable text".length
);
synthesizeMouse(editableDiv, 10, 10, {button: 2});
ok(
getSelection().isCollapsed,
`Selection should be collapsed by a right click even when stop_if_non_editable_node pref is true in editable text node (${
getRangeDescription(getSelection().getRangeAt(0))
})`
);
input.focus();
input.setSelectionRange("input ".length, "input value".length);
synthesizeMouse(input, 10, 10, {button: 2});
ok(
input.selectionStart == input.selectionEnd,
`Selection in <input> should be collapsed by a right click even when stop_if_non_editable_node pref is true (got: ${input.selectionStart} - ${input.selectionEnd})`
);
input.blur();
await SpecialPowers.pushPrefEnv({
set: [
["ui.mouse.right_click.collapse_selection.stop_if_non_collapsed_selection", true],
["ui.mouse.right_click.collapse_selection.stop_if_non_editable_node", false],
["ui.mouse.right_click.move_caret.stop_if_in_focused_editable_node", false],
],
});
getSelection().setBaseAndExtent(
nonEditableDiv.firstChild, "Non editable ".length,
nonEditableDiv.firstChild, "Non editable text".length
);
synthesizeMouse(nonEditableDiv, 10, 10, {button: 2});
ok(
!getSelection().isCollapsed,
`Selection should not be collapsed by a right click when stop_if_non_collapsed_selection pref is true in non-editable text node (${
getRangeDescription(getSelection().getRangeAt(0))
})`
);
getSelection().setBaseAndExtent(
editableDiv.firstChild, "Editable ".length,
editableDiv.firstChild, "Editable text".length
);
synthesizeMouse(editableDiv, 10, 10, {button: 2});
ok(
!getSelection().isCollapsed,
`Selection should be collapsed by a right click when stop_if_non_collapsed_selection pref is true in editable text node (${
getRangeDescription(getSelection().getRangeAt(0))
})`
);
input.focus();
input.setSelectionRange("input ".length, "input value".length);
synthesizeMouse(input, 10, 10, {button: 2});
ok(
input.selectionStart != input.selectionEnd,
`Selection in <input> should not be collapsed by a right click when stop_if_non_collapsed_selection pref is true (got: ${input.selectionStart} - ${input.selectionEnd})`
);
input.blur();
// When the right click changes the selection ancestor limit, it should be
// handled correctly after nsIFrame does nothing.
getSelection().setBaseAndExtent(
nonEditableDiv.firstChild, "Non editable ".length,
nonEditableDiv.firstChild, "Non editable text".length
);
synthesizeMouse(editableDiv, 10, 10, {button: 2});
is(
document.activeElement,
editableDiv,
"Right clicking in editable <div> when selection selects some text outside the <div> should move focus into the editor"
);
ok(
getSelection().focusNode == editableDiv.firstChild &&
getSelection().focusOffset > 0,
`Right clicking in editable <div> when selection selects some text outside the <div> should not cause collapsing selection to start of the editor (${
getRangeDescription(getSelection().getRangeAt(0))
})`
);
getSelection().setBaseAndExtent(
editableDiv.firstChild, "Editable ".length,
editableDiv.firstChild, "Editable text".length
);
synthesizeMouse(nonEditableDiv, 10, 10, {button: 2});
isnot(
document.activeElement,
editableDiv,
"Right clicking outside the editable <div> should blur from it"
);
ok(
getSelection().focusNode == nonEditableDiv.firstChild &&
getSelection().focusOffset > 0,
`Right clicking outside the editable <div> should collapse selection at the clicked content (${
getRangeDescription(getSelection().getRangeAt(0))
})`
);
// If clicking in a selection range, the range should never be collapsed.
const iframe = document.querySelector("iframe");
const doc = iframe.contentDocument;
doc.addEventListener("contextmenu", event => event.preventDefault(), {capture: true});
await SpecialPowers.pushPrefEnv({
set: [
["ui.mouse.right_click.collapse_selection.stop_if_non_collapsed_selection", false],
["ui.mouse.right_click.collapse_selection.stop_if_non_editable_node", false],
["ui.mouse.right_click.move_caret.stop_if_in_focused_editable_node", false],
],
});
iframe.focus();
doc.getSelection().selectAllChildren(doc.body);
synthesizeMouseAtCenter(doc.body.firstChild, {button: 2}, iframe.contentWindow);
ok(
!doc.getSelection().isCollapsed,
"Right click in a selection range should not cause collapsing selection"
);
iframe.blur();
doc.activeElement?.blur();
doc.documentElement.setAttribute("contenteditable", "");
iframe.focus();
doc.getSelection().selectAllChildren(doc.body);
synthesizeMouseAtCenter(doc.body.firstChild, {button: 2}, iframe.contentWindow);
ok(
!doc.getSelection().isCollapsed,
"Right click in a selection range in a editable-root-element should not cause collapsing selection"
);
iframe.blur();
doc.activeElement?.blur();
await SpecialPowers.pushPrefEnv({
set: [
["ui.mouse.right_click.collapse_selection.stop_if_non_collapsed_selection", true],
["ui.mouse.right_click.collapse_selection.stop_if_non_editable_node", false],
["ui.mouse.right_click.move_caret.stop_if_in_focused_editable_node", false],
],
});
doc.designMode = "on";
iframe.focus();
doc.getSelection().selectAllChildren(doc.body);
synthesizeMouseAtCenter(doc.body.firstChild, {button: 2}, iframe.contentWindow);
ok(
!doc.getSelection().isCollapsed,
"Right click in a selection range in a sub-editable-document should not cause collapsing selection"
);
doc.designMode = "off";
// If selection is collapsed and `ui.mouse.right_click.move_caret.stop_if_in_focused_editable_node` is
// false, the caret should be moved at clicked point.
await SpecialPowers.pushPrefEnv({
set: [
["ui.mouse.right_click.collapse_selection.stop_if_non_collapsed_selection", true],
["ui.mouse.right_click.collapse_selection.stop_if_non_editable_node", false],
["ui.mouse.right_click.move_caret.stop_if_in_focused_editable_node", false],
],
});
editableDiv.innerHTML = "<b>Bold text</b><i>Italic text</i>";
editableDiv.focus();
getSelection().collapse(
editableDiv.querySelector("b").firstChild,
"Bold".length
);
synthesizeMouseAtCenter(editableDiv.querySelector("i"), {button: 2});
ok(
getSelection().isCollapsed,
"Right clicking in editable <div> should keep Selection collapsed if the pref is unset"
);
is(
getSelection().focusNode.parentNode,
editableDiv.querySelector("i"),
`Right clicking in editable <div> should collapse Selection at the clicked point (${
getRangeDescription(getSelection().getRangeAt(0))
}) if the pref is unset`
);
input.focus();
input.setSelectionRange(0, 0);
synthesizeMouseAtCenter(input, {button: 2});
ok(
input.selectionStart == input.selectionEnd,
"Right click in <input> should keep Selection collapsed if the pref is unset"
);
ok(
input.selectionStart > 0,
"Right click in <input> should move caret if the pref is unset"
);
input.blur();
// If selection is collapsed and `ui.mouse.right_click.move_caret.stop_if_in_focused_editable_node` is
// true, the caret should not be moved at clicked point unless focus is changed by the click.
await SpecialPowers.pushPrefEnv({
set: [
["ui.mouse.right_click.collapse_selection.stop_if_non_collapsed_selection", true],
["ui.mouse.right_click.collapse_selection.stop_if_non_editable_node", false],
["ui.mouse.right_click.move_caret.stop_if_in_focused_editable_node", true],
],
});
editableDiv.innerHTML = "<b>Bold text</b><i>Italic text</i>";
editableDiv.focus();
getSelection().collapse(
editableDiv.querySelector("b").firstChild,
"Bold".length
);
synthesizeMouseAtCenter(editableDiv.querySelector("i"), {button: 2});
ok(
getSelection().isCollapsed,
"Right clicking in editable <div> should keep Selection collapsed even if the pref is set"
);
is(
getSelection().focusNode.parentNode,
editableDiv.querySelector("b"),
`Right clicking in editable <div> should not move caret to the clicked point (${
getRangeDescription(getSelection().getRangeAt(0))
}) if the pref is set`
);
input.focus();
input.setSelectionRange(0, 0);
synthesizeMouseAtCenter(input, {button: 2});
ok(
input.selectionStart == input.selectionEnd,
"Right click in <input> should keep Selection collapsed even if the pref is set"
);
is(
input.selectionStart,
0,
"Right click in <input> should not move caret if the pref is set"
);
input.blur();
input.setSelectionRange(0, 0);
editableDiv.focus();
synthesizeMouseAtCenter(input, {button: 2});
ok(
input.selectionStart == input.selectionEnd,
"Right click in <input> which is not focused should keep Selection collapsed"
);
ok(
input.selectionStart > 0,
"Right click in <input> which is not focused should move caret"
);
editableDiv.focus();
getSelection().collapse(
editableDiv.querySelector("b").firstChild,
"Bold".length
);
input.focus();
synthesizeMouseAtCenter(editableDiv.querySelector("i"), {button: 2});
ok(
getSelection().isCollapsed,
"Right clicking in editable <div> which is not focused should collapse selection"
);
is(
getSelection().focusNode.parentNode,
editableDiv.querySelector("i"),
`Right clicking in editable <div> which is not focused should collapse Selection at the clicked point (${
getRangeDescription(getSelection().getRangeAt(0))
})`
);
// If Shift + right click should forcibly open context menu, users may want the click to work as
// same as without Shift.
await SpecialPowers.pushPrefEnv({
set: [
["ui.mouse.right_click.collapse_selection.stop_if_non_collapsed_selection", true],
["ui.mouse.right_click.collapse_selection.stop_if_non_editable_node", false],
["ui.mouse.right_click.move_caret.stop_if_in_focused_editable_node", false],
["dom.event.contextmenu.shift_suppresses_event", true],
],
});
nonEditableDiv.innerHTML = "<b>bold</b> <i>italic</i>";
getSelection().collapse(nonEditableDiv.querySelector("b").firstChild, 0);
synthesizeMouseAtCenter(nonEditableDiv.querySelector("i"), {shiftKey: true, button: 2});
ok(
getSelection().isCollapsed,
`Selection should be collapsed by a Shift + right click on non-editable node when it does not open context menu (${
getRangeDescription(getSelection().getRangeAt(0))
})`
);
is(
getSelection().focusNode,
nonEditableDiv.querySelector("i").firstChild,
`Selection should be collapsed at the click point by a Shift + right click on non-editable node when it does not open context menu (${
getRangeDescription(getSelection().getRangeAt(0))
})`
);
editableDiv.innerHTML = "<b>bold</b> <i>italic</i>";
getSelection().collapse(editableDiv.querySelector("b").firstChild, 0);
synthesizeMouseAtCenter(editableDiv.querySelector("i"), {shiftKey: true, button: 2});
ok(
getSelection().isCollapsed,
`Selection should be collapsed by a Shift + right click on editable node when it does not open context menu (${
getRangeDescription(getSelection().getRangeAt(0))
})`
);
is(
getSelection().focusNode,
editableDiv.querySelector("i").firstChild,
`Selection should be collapsed at the click point by a Shift + right click on editable node when it does not open context menu (${
getRangeDescription(getSelection().getRangeAt(0))
})`
);
input.focus();
input.setSelectionRange(0, 0);
synthesizeMouseAtCenter(input, {shiftKey: true, button: 2});
ok(
input.selectionStart == input.selectionEnd,
`Selection in <input> should be collapsed by a Shift + right click when it does not open context menu (got: ${
input.selectionStart
} - ${input.selectionEnd})`
);
isnot(
input.selectionStart,
0,
`Selection in <input> should be collapsed at the click point by a Shift + right click when it does not open context menu (got: ${
input.selectionStart
} - ${input.selectionEnd})`
);
input.blur();
// If Shift + right click should open context menu, users may want the click to work as
// same as a left click.
await SpecialPowers.pushPrefEnv({
set: [
["ui.mouse.right_click.collapse_selection.stop_if_non_collapsed_selection", true],
["ui.mouse.right_click.collapse_selection.stop_if_non_editable_node", false],
["ui.mouse.right_click.move_caret.stop_if_in_focused_editable_node", false],
["dom.event.contextmenu.shift_suppresses_event", false],
],
});
nonEditableDiv.innerHTML = "<b>bold</b> <i>italic</i>";
getSelection().collapse(nonEditableDiv.querySelector("b").firstChild, 0);
synthesizeMouseAtCenter(nonEditableDiv.querySelector("i"), {shiftKey: true, button: 2});
is(
getRangeDescription(getSelection().getRangeAt(0)),
getRangeDescription({
startContainer: nonEditableDiv.querySelector("b").firstChild,
startOffset: 0,
endContainer: nonEditableDiv.querySelector("i").firstChild,
endOffset: getSelection().focusOffset,
}),
`Selection should be extended by a Shift + right click on non-editable node when it should open context menu`
);
editableDiv.innerHTML = "<b>bold</b> <i>italic</i>";
getSelection().collapse(editableDiv.querySelector("b").firstChild, 0);
synthesizeMouseAtCenter(editableDiv.querySelector("i"), {shiftKey: true, button: 2});
is(
getRangeDescription(getSelection().getRangeAt(0)),
getRangeDescription({
startContainer: editableDiv.querySelector("b").firstChild,
startOffset: 0,
endContainer: editableDiv.querySelector("i").firstChild,
endOffset: getSelection().focusOffset,
}),
`Selection should be extended by a Shift + right click on editable node when it should open context menu`
);
input.focus();
input.setSelectionRange(0, 0);
synthesizeMouseAtCenter(input, {shiftKey: true, button: 2});
isnot(
input.selectionStart,
input.selectionEnd,
`Selection in <input> should be extended by a Shift + right click when it should open context menu (got: ${
input.selectionStart
} - ${input.selectionEnd})`
);
input.blur();
SimpleTest.finish();
});
</script>
</body>
</html>