mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 12:51:06 +00:00
Bug 1883367 - Disable editor commands for read-only editors. r=masayuki
The purpose of this patch is to prevent users to make changes to read-only editors. This patch forces `IsCommandEnabled()` to return false for read-only editors for "standard" editor commands that could have an associated icon or menu entry and are therefore user-accessible. This patch does not modify/test any paste commands or any of the advanced internal editor commands. Differential Revision: https://phabricator.services.mozilla.com/D203754
This commit is contained in:
parent
417ec57c47
commit
89637d1080
@ -269,7 +269,8 @@ bool UndoCommand::IsCommandEnabled(Command aCommand,
|
||||
if (!aEditorBase) {
|
||||
return false;
|
||||
}
|
||||
return aEditorBase->IsSelectionEditable() && aEditorBase->CanUndo();
|
||||
return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable() &&
|
||||
aEditorBase->CanUndo();
|
||||
}
|
||||
|
||||
nsresult UndoCommand::DoCommand(Command aCommand, EditorBase& aEditorBase,
|
||||
@ -297,7 +298,8 @@ bool RedoCommand::IsCommandEnabled(Command aCommand,
|
||||
if (!aEditorBase) {
|
||||
return false;
|
||||
}
|
||||
return aEditorBase->IsSelectionEditable() && aEditorBase->CanRedo();
|
||||
return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable() &&
|
||||
aEditorBase->CanRedo();
|
||||
}
|
||||
|
||||
nsresult RedoCommand::DoCommand(Command aCommand, EditorBase& aEditorBase,
|
||||
@ -548,7 +550,7 @@ bool SwitchTextDirectionCommand::IsCommandEnabled(
|
||||
if (!aEditorBase) {
|
||||
return false;
|
||||
}
|
||||
return aEditorBase->IsSelectionEditable();
|
||||
return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable();
|
||||
}
|
||||
|
||||
nsresult SwitchTextDirectionCommand::DoCommand(Command aCommand,
|
||||
@ -581,7 +583,8 @@ bool DeleteCommand::IsCommandEnabled(Command aCommand,
|
||||
// We can generally delete whenever the selection is editable. However,
|
||||
// cmd_delete doesn't make sense if the selection is collapsed because it's
|
||||
// directionless.
|
||||
bool isEnabled = aEditorBase->IsSelectionEditable();
|
||||
bool isEnabled =
|
||||
aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable();
|
||||
|
||||
if (aCommand == Command::Delete && isEnabled) {
|
||||
return aEditorBase->CanDeleteSelection();
|
||||
@ -820,7 +823,7 @@ bool InsertPlaintextCommand::IsCommandEnabled(Command aCommand,
|
||||
if (!aEditorBase) {
|
||||
return false;
|
||||
}
|
||||
return aEditorBase->IsSelectionEditable();
|
||||
return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable();
|
||||
}
|
||||
|
||||
nsresult InsertPlaintextCommand::DoCommand(Command aCommand,
|
||||
@ -877,7 +880,7 @@ bool InsertParagraphCommand::IsCommandEnabled(Command aCommand,
|
||||
if (!aEditorBase || aEditorBase->IsSingleLineEditor()) {
|
||||
return false;
|
||||
}
|
||||
return aEditorBase->IsSelectionEditable();
|
||||
return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable();
|
||||
}
|
||||
|
||||
nsresult InsertParagraphCommand::DoCommand(Command aCommand,
|
||||
@ -918,7 +921,7 @@ bool InsertLineBreakCommand::IsCommandEnabled(Command aCommand,
|
||||
if (!aEditorBase || aEditorBase->IsSingleLineEditor()) {
|
||||
return false;
|
||||
}
|
||||
return aEditorBase->IsSelectionEditable();
|
||||
return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable();
|
||||
}
|
||||
|
||||
nsresult InsertLineBreakCommand::DoCommand(Command aCommand,
|
||||
|
@ -52,7 +52,7 @@ bool StateUpdatingCommandBase::IsCommandEnabled(Command aCommand,
|
||||
if (!htmlEditor) {
|
||||
return false;
|
||||
}
|
||||
if (!htmlEditor->IsSelectionEditable()) {
|
||||
if (!htmlEditor->IsModifiable() || !htmlEditor->IsSelectionEditable()) {
|
||||
return false;
|
||||
}
|
||||
if (aCommand == Command::FormatAbsolutePosition) {
|
||||
@ -355,8 +355,7 @@ bool RemoveListCommand::IsCommandEnabled(Command aCommand,
|
||||
if (!htmlEditor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!htmlEditor->IsSelectionEditable()) {
|
||||
if (!htmlEditor->IsModifiable() || !htmlEditor->IsSelectionEditable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -401,7 +400,7 @@ bool IndentCommand::IsCommandEnabled(Command aCommand,
|
||||
if (!htmlEditor) {
|
||||
return false;
|
||||
}
|
||||
return htmlEditor->IsSelectionEditable();
|
||||
return htmlEditor->IsModifiable() && htmlEditor->IsSelectionEditable();
|
||||
}
|
||||
|
||||
nsresult IndentCommand::DoCommand(Command aCommand, EditorBase& aEditorBase,
|
||||
@ -434,7 +433,7 @@ bool OutdentCommand::IsCommandEnabled(Command aCommand,
|
||||
if (!htmlEditor) {
|
||||
return false;
|
||||
}
|
||||
return htmlEditor->IsSelectionEditable();
|
||||
return htmlEditor->IsModifiable() && htmlEditor->IsSelectionEditable();
|
||||
}
|
||||
|
||||
nsresult OutdentCommand::DoCommand(Command aCommand, EditorBase& aEditorBase,
|
||||
@ -467,7 +466,7 @@ bool MultiStateCommandBase::IsCommandEnabled(Command aCommand,
|
||||
return false;
|
||||
}
|
||||
// should be disabled sometimes, like if the current selection is an image
|
||||
return htmlEditor->IsSelectionEditable();
|
||||
return htmlEditor->IsModifiable() && htmlEditor->IsSelectionEditable();
|
||||
}
|
||||
|
||||
nsresult MultiStateCommandBase::DoCommand(Command aCommand,
|
||||
@ -1047,7 +1046,7 @@ bool RemoveStylesCommand::IsCommandEnabled(Command aCommand,
|
||||
return false;
|
||||
}
|
||||
// test if we have any styles?
|
||||
return htmlEditor->IsSelectionEditable();
|
||||
return htmlEditor->IsModifiable() && htmlEditor->IsSelectionEditable();
|
||||
}
|
||||
|
||||
nsresult RemoveStylesCommand::DoCommand(Command aCommand,
|
||||
@ -1085,7 +1084,7 @@ bool IncreaseFontSizeCommand::IsCommandEnabled(Command aCommand,
|
||||
return false;
|
||||
}
|
||||
// test if we are at max size?
|
||||
return htmlEditor->IsSelectionEditable();
|
||||
return htmlEditor->IsModifiable() && htmlEditor->IsSelectionEditable();
|
||||
}
|
||||
|
||||
nsresult IncreaseFontSizeCommand::DoCommand(Command aCommand,
|
||||
@ -1121,7 +1120,7 @@ bool DecreaseFontSizeCommand::IsCommandEnabled(Command aCommand,
|
||||
return false;
|
||||
}
|
||||
// test if we are at min size?
|
||||
return htmlEditor->IsSelectionEditable();
|
||||
return htmlEditor->IsModifiable() && htmlEditor->IsSelectionEditable();
|
||||
}
|
||||
|
||||
nsresult DecreaseFontSizeCommand::DoCommand(Command aCommand,
|
||||
@ -1156,7 +1155,7 @@ bool InsertHTMLCommand::IsCommandEnabled(Command aCommand,
|
||||
if (!htmlEditor) {
|
||||
return false;
|
||||
}
|
||||
return htmlEditor->IsSelectionEditable();
|
||||
return htmlEditor->IsModifiable() && htmlEditor->IsSelectionEditable();
|
||||
}
|
||||
|
||||
nsresult InsertHTMLCommand::DoCommand(Command aCommand, EditorBase& aEditorBase,
|
||||
@ -1213,7 +1212,7 @@ bool InsertTagCommand::IsCommandEnabled(Command aCommand,
|
||||
if (!htmlEditor) {
|
||||
return false;
|
||||
}
|
||||
return htmlEditor->IsSelectionEditable();
|
||||
return htmlEditor->IsModifiable() && htmlEditor->IsSelectionEditable();
|
||||
}
|
||||
|
||||
// corresponding STATE_ATTRIBUTE is: src (img) and href (a)
|
||||
|
@ -418,6 +418,8 @@ skip-if = ["os == 'android'"] #Bug 1575739
|
||||
|
||||
["test_cmd_paragraphState.html"]
|
||||
|
||||
["test_command_state_when_readonly.html"]
|
||||
|
||||
["test_composition_event_created_in_chrome.html"]
|
||||
|
||||
["test_composition_with_highlight_in_texteditor.html"]
|
||||
|
149
editor/libeditor/tests/test_command_state_when_readonly.html
Normal file
149
editor/libeditor/tests/test_command_state_when_readonly.html
Normal file
@ -0,0 +1,149 @@
|
||||
<!doctype html>
|
||||
<title>Test for nsIEditor.isCommandEnabled for normal and read-only editors</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
<div contenteditable></div>
|
||||
<script>
|
||||
let node = document.querySelector("div");
|
||||
node.focus();
|
||||
let htmlEditor =
|
||||
SpecialPowers.wrap(window).docShell.editingSession.getEditorForWindow(window);
|
||||
|
||||
// Supported environments for each command. Supported values for each
|
||||
// environment property:
|
||||
// content: "empty", "non-empty", "cleared"
|
||||
// selected: true, false
|
||||
// readonly: true, false
|
||||
//
|
||||
// If an environment definition does not state a certain property, the command
|
||||
// supports all possible values for that property. The following definition:
|
||||
// "cmd_copy": [{content: "non-empty", selected: true}],
|
||||
// is equivalent to:
|
||||
// "cmd_copy": [
|
||||
// {content: "non-empty", selected: true, readonly: true},
|
||||
// {content: "non-empty", selected: true, readonly: false},
|
||||
// ],
|
||||
const TEST_COMMANDS = {
|
||||
"cmd_selectAll": [{content: "non-empty"}],
|
||||
|
||||
"cmd_copy": [{content: "non-empty", selected: true}],
|
||||
|
||||
"cmd_cut": [{content: "non-empty", selected: true, readonly: false}],
|
||||
"cmd_delete": [{content: "non-empty", selected: true, readonly: false}],
|
||||
"cmd_removeList":[{content: "non-empty", selected: true, readonly: false}],
|
||||
|
||||
"cmd_undo": [{content: "cleared", readonly: false}],
|
||||
"cmd_redo": [{content: "cleared", readonly: false}],
|
||||
|
||||
"cmd_switchTextDirection": [{readonly: false}],
|
||||
"cmd_bold": [{readonly: false}],
|
||||
"cmd_italic": [{readonly: false}],
|
||||
"cmd_underline": [{readonly: false}],
|
||||
"cmd_em": [{readonly: false}],
|
||||
"cmd_strong": [{readonly: false}],
|
||||
"cmd_strikethrough": [{readonly: false}],
|
||||
"cmd_superscript": [{readonly: false}],
|
||||
"cmd_subscript": [{readonly: false}],
|
||||
"cmd_indent": [{readonly: false}],
|
||||
"cmd_outdent": [{readonly: false}],
|
||||
"cmd_formatBlock": [{readonly: false}],
|
||||
"cmd_paragraphState": [{readonly: false}],
|
||||
"cmd_fontFace": [{readonly: false}],
|
||||
"cmd_fontSize": [{readonly: false}],
|
||||
"cmd_fontColor": [{readonly: false}],
|
||||
"cmd_backgroundColor": [{readonly: false}],
|
||||
"cmd_highlight": [{readonly: false}],
|
||||
"cmd_align": [{readonly: false}],
|
||||
"cmd_removeStyles": [{readonly: false}],
|
||||
"cmd_increaseFont": [{readonly: false}],
|
||||
"cmd_decreaseFont": [{readonly: false}],
|
||||
"cmd_insertHR": [{readonly: false}],
|
||||
"cmd_insertHTML": [{readonly: false}],
|
||||
"cmd_insertText": [{readonly: false}],
|
||||
"cmd_insertParagraph": [{readonly: false}],
|
||||
"cmd_insertLineBreak": [{readonly: false}],
|
||||
"cmd_tt":[{readonly: false}],
|
||||
"cmd_nobreak":[{readonly: false}],
|
||||
"cmd_cite":[{readonly: false}],
|
||||
"cmd_abbr":[{readonly: false}],
|
||||
"cmd_acronym":[{readonly: false}],
|
||||
"cmd_code":[{readonly: false}],
|
||||
"cmd_samp":[{readonly: false}],
|
||||
"cmd_var":[{readonly: false}],
|
||||
"cmd_removeLinks":[{readonly: false}],
|
||||
"cmd_ol":[{readonly: false}],
|
||||
"cmd_ul":[{readonly: false}],
|
||||
"cmd_dt":[{readonly: false}],
|
||||
"cmd_dd":[{readonly: false}],
|
||||
|
||||
// InsertTagCommand
|
||||
"cmd_insertImageNoUI": [{readonly: false}],
|
||||
"cmd_insertLinkNoUI": [{readonly: false}],
|
||||
};
|
||||
|
||||
function testCommands(content) {
|
||||
for (let readonly of [true, false]){
|
||||
if (readonly) {
|
||||
htmlEditor.flags |= SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
} else {
|
||||
htmlEditor.flags &= ~SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
}
|
||||
|
||||
for (let selected of [true, false]) {
|
||||
let selection = window.getSelection();
|
||||
selection.collapse(node);
|
||||
|
||||
if (selected) {
|
||||
if (content == "non-empty") {
|
||||
// The command cmd_removeList needs selected text inside a list. It
|
||||
// does not matter for all other commands, so lets just select that.
|
||||
let range = document.createRange();
|
||||
let li = document.querySelector("li");
|
||||
range.selectNodeContents(li);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
} else {
|
||||
document.execCommand("selectAll");
|
||||
}
|
||||
}
|
||||
|
||||
for (let [cmd, supports] of Object.entries(TEST_COMMANDS)) {
|
||||
// Check if the command should support this environment.
|
||||
let expected = supports.some(supported =>
|
||||
content == (supported?.content ?? content) &&
|
||||
readonly == (supported?.readonly ?? readonly) &&
|
||||
selected == (supported?.selected ?? selected)
|
||||
)
|
||||
is(
|
||||
SpecialPowers.isCommandEnabled(window, cmd),
|
||||
expected,
|
||||
`Enabled state of command ${cmd} should be ${
|
||||
expected ? "TRUE" : "FALSE"
|
||||
} for ${JSON.stringify({content, selected, readonly})}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testCommands("empty");
|
||||
|
||||
// The cmd_removeList command needs a list.
|
||||
node.innerHTML = "<ul><li><span>abcd</span></li></ul>";
|
||||
testCommands("non-empty");
|
||||
|
||||
// Make some content modifications to enable undo and redo.
|
||||
node.innerText = "ABC";
|
||||
is(node.innerText.trim(), "ABC", "phase 1");
|
||||
document.execCommand("selectAll");
|
||||
synthesizeKey("KEY_Backspace");
|
||||
is(node.innerText.trim(), "", "phase 2");
|
||||
synthesizeKey("3");
|
||||
is(node.innerText.trim(), "3", "phase 3");
|
||||
SpecialPowers.doCommand(window, "cmd_undo");
|
||||
is(node.innerText.trim(), "", "phase 4");
|
||||
|
||||
node.innerHTML = "";
|
||||
testCommands("cleared");
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user