diff options
Diffstat (limited to 'editor/libeditor/EditorCommands.cpp')
-rw-r--r-- | editor/libeditor/EditorCommands.cpp | 975 |
1 files changed, 975 insertions, 0 deletions
diff --git a/editor/libeditor/EditorCommands.cpp b/editor/libeditor/EditorCommands.cpp new file mode 100644 index 0000000000..0b46c867d3 --- /dev/null +++ b/editor/libeditor/EditorCommands.cpp @@ -0,0 +1,975 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "EditorCommands.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/EditorBase.h" +#include "mozilla/FlushType.h" +#include "mozilla/HTMLEditor.h" +#include "mozilla/Maybe.h" +#include "mozilla/MozPromise.h" // for mozilla::detail::Any +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Selection.h" +#include "nsCommandParams.h" +#include "nsIClipboard.h" +#include "nsIEditingSession.h" +#include "nsIPrincipal.h" +#include "nsISelectionController.h" +#include "nsITransferable.h" +#include "nsString.h" +#include "nsAString.h" + +class nsISupports; + +#define STATE_ENABLED "state_enabled" +#define STATE_ATTRIBUTE "state_attribute" +#define STATE_DATA "state_data" + +namespace mozilla { + +using detail::Any; + +/****************************************************************************** + * mozilla::EditorCommand + ******************************************************************************/ + +NS_IMPL_ISUPPORTS(EditorCommand, nsIControllerCommand) + +NS_IMETHODIMP EditorCommand::IsCommandEnabled(const char* aCommandName, + nsISupports* aCommandRefCon, + bool* aIsEnabled) { + if (NS_WARN_IF(!aCommandName) || NS_WARN_IF(!aIsEnabled)) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon); + EditorBase* editorBase = editor ? editor->AsEditorBase() : nullptr; + *aIsEnabled = IsCommandEnabled(GetInternalCommand(aCommandName), + MOZ_KnownLive(editorBase)); + return NS_OK; +} + +NS_IMETHODIMP EditorCommand::DoCommand(const char* aCommandName, + nsISupports* aCommandRefCon) { + if (NS_WARN_IF(!aCommandName) || NS_WARN_IF(!aCommandRefCon)) { + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon); + if (NS_WARN_IF(!editor)) { + return NS_ERROR_INVALID_ARG; + } + nsresult rv = DoCommand(GetInternalCommand(aCommandName), + MOZ_KnownLive(*editor->AsEditorBase()), nullptr); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "Failed to do command from nsIControllerCommand::DoCommand()"); + return rv; +} + +NS_IMETHODIMP EditorCommand::DoCommandParams(const char* aCommandName, + nsICommandParams* aParams, + nsISupports* aCommandRefCon) { + if (NS_WARN_IF(!aCommandName) || NS_WARN_IF(!aCommandRefCon)) { + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon); + if (NS_WARN_IF(!editor)) { + return NS_ERROR_INVALID_ARG; + } + nsCommandParams* params = aParams ? aParams->AsCommandParams() : nullptr; + Command command = GetInternalCommand(aCommandName, params); + EditorCommandParamType paramType = EditorCommand::GetParamType(command); + if (paramType == EditorCommandParamType::None) { + nsresult rv = DoCommandParam( + command, MOZ_KnownLive(*editor->AsEditorBase()), nullptr); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "Failed to do command from nsIControllerCommand::DoCommandParams()"); + return rv; + } + + if (Any(paramType & EditorCommandParamType::Bool)) { + if (Any(paramType & EditorCommandParamType::StateAttribute)) { + Maybe<bool> boolParam = Nothing(); + if (params) { + ErrorResult error; + boolParam = Some(params->GetBool(STATE_ATTRIBUTE, error)); + if (NS_WARN_IF(error.Failed())) { + return error.StealNSResult(); + } + } + nsresult rv = DoCommandParam( + command, boolParam, MOZ_KnownLive(*editor->AsEditorBase()), nullptr); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "Failed to do command from nsIControllerCommand::DoCommandParams()"); + return rv; + } + MOZ_ASSERT_UNREACHABLE("Unexpected state for bool"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + // Special case for MultiStateCommandBase. It allows both CString and String + // in STATE_ATTRIBUTE and CString is preferred. + if (Any(paramType & EditorCommandParamType::CString) && + Any(paramType & EditorCommandParamType::String)) { + if (!params) { + nsresult rv = + DoCommandParam(command, VoidString(), + MOZ_KnownLive(*editor->AsEditorBase()), nullptr); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Failed to do command from " + "nsIControllerCommand::DoCommandParams()"); + return rv; + } + if (Any(paramType & EditorCommandParamType::StateAttribute)) { + nsCString cStringParam; + nsresult rv = params->GetCString(STATE_ATTRIBUTE, cStringParam); + if (NS_SUCCEEDED(rv)) { + NS_ConvertUTF8toUTF16 stringParam(cStringParam); + nsresult rv = + DoCommandParam(command, stringParam, + MOZ_KnownLive(*editor->AsEditorBase()), nullptr); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Failed to do command from " + "nsIControllerCommand::DoCommandParams()"); + return rv; + } + nsString stringParam; + DebugOnly<nsresult> rvIgnored = + params->GetString(STATE_ATTRIBUTE, stringParam); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "Failed to get string from STATE_ATTRIBUTE"); + rv = DoCommandParam(command, stringParam, + MOZ_KnownLive(*editor->AsEditorBase()), nullptr); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Failed to do command from " + "nsIControllerCommand::DoCommandParams()"); + return rv; + } + MOZ_ASSERT_UNREACHABLE("Unexpected state for CString/String"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + if (Any(paramType & EditorCommandParamType::CString)) { + if (!params) { + nsresult rv = + DoCommandParam(command, VoidCString(), + MOZ_KnownLive(*editor->AsEditorBase()), nullptr); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "Failed to do command from nsIControllerCommand::DoCommandParams()"); + return rv; + } + if (Any(paramType & EditorCommandParamType::StateAttribute)) { + nsCString cStringParam; + nsresult rv = params->GetCString(STATE_ATTRIBUTE, cStringParam); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = DoCommandParam(command, cStringParam, + MOZ_KnownLive(*editor->AsEditorBase()), nullptr); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "Failed to do command from nsIControllerCommand::DoCommandParams()"); + return rv; + } + MOZ_ASSERT_UNREACHABLE("Unexpected state for CString"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + if (Any(paramType & EditorCommandParamType::String)) { + if (!params) { + nsresult rv = + DoCommandParam(command, VoidString(), + MOZ_KnownLive(*editor->AsEditorBase()), nullptr); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "Failed to do command from nsIControllerCommand::DoCommandParams()"); + return rv; + } + nsString stringParam; + if (Any(paramType & EditorCommandParamType::StateAttribute)) { + nsresult rv = params->GetString(STATE_ATTRIBUTE, stringParam); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else if (Any(paramType & EditorCommandParamType::StateData)) { + nsresult rv = params->GetString(STATE_DATA, stringParam); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + MOZ_ASSERT_UNREACHABLE("Unexpected state for String"); + return NS_ERROR_NOT_IMPLEMENTED; + } + nsresult rv = DoCommandParam( + command, stringParam, MOZ_KnownLive(*editor->AsEditorBase()), nullptr); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "Failed to do command from nsIControllerCommand::DoCommandParams()"); + return rv; + } + + if (Any(paramType & EditorCommandParamType::Transferable)) { + nsCOMPtr<nsITransferable> transferable; + if (params) { + nsCOMPtr<nsISupports> supports = params->GetISupports("transferable"); + transferable = do_QueryInterface(supports); + } + nsresult rv = DoCommandParam( + command, transferable, MOZ_KnownLive(*editor->AsEditorBase()), nullptr); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "Failed to do command from nsIControllerCommand::DoCommandParams()"); + return rv; + } + + MOZ_ASSERT_UNREACHABLE("Unexpected param type"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP EditorCommand::GetCommandStateParams( + const char* aCommandName, nsICommandParams* aParams, + nsISupports* aCommandRefCon) { + if (NS_WARN_IF(!aCommandName) || NS_WARN_IF(!aParams)) { + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon); + if (editor) { + return GetCommandStateParams(GetInternalCommand(aCommandName), + MOZ_KnownLive(*aParams->AsCommandParams()), + MOZ_KnownLive(editor->AsEditorBase()), + nullptr); + } + nsCOMPtr<nsIEditingSession> editingSession = + do_QueryInterface(aCommandRefCon); + if (editingSession) { + return GetCommandStateParams(GetInternalCommand(aCommandName), + MOZ_KnownLive(*aParams->AsCommandParams()), + nullptr, editingSession); + } + return GetCommandStateParams(GetInternalCommand(aCommandName), + MOZ_KnownLive(*aParams->AsCommandParams()), + nullptr, nullptr); +} + +/****************************************************************************** + * mozilla::UndoCommand + ******************************************************************************/ + +StaticRefPtr<UndoCommand> UndoCommand::sInstance; + +bool UndoCommand::IsCommandEnabled(Command aCommand, + EditorBase* aEditorBase) const { + if (!aEditorBase) { + return false; + } + return aEditorBase->IsSelectionEditable() && aEditorBase->CanUndo(); +} + +nsresult UndoCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + nsresult rv = aEditorBase.UndoAsAction(1, aPrincipal); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::UndoAsAction() failed"); + return rv; +} + +nsresult UndoCommand::GetCommandStateParams( + Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, + nsIEditingSession* aEditingSession) const { + return aParams.SetBool(STATE_ENABLED, + IsCommandEnabled(aCommand, aEditorBase)); +} + +/****************************************************************************** + * mozilla::RedoCommand + ******************************************************************************/ + +StaticRefPtr<RedoCommand> RedoCommand::sInstance; + +bool RedoCommand::IsCommandEnabled(Command aCommand, + EditorBase* aEditorBase) const { + if (!aEditorBase) { + return false; + } + return aEditorBase->IsSelectionEditable() && aEditorBase->CanRedo(); +} + +nsresult RedoCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + nsresult rv = aEditorBase.RedoAsAction(1, aPrincipal); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::RedoAsAction() failed"); + return rv; +} + +nsresult RedoCommand::GetCommandStateParams( + Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, + nsIEditingSession* aEditingSession) const { + return aParams.SetBool(STATE_ENABLED, + IsCommandEnabled(aCommand, aEditorBase)); +} + +/****************************************************************************** + * mozilla::CutCommand + ******************************************************************************/ + +StaticRefPtr<CutCommand> CutCommand::sInstance; + +bool CutCommand::IsCommandEnabled(Command aCommand, + EditorBase* aEditorBase) const { + if (!aEditorBase) { + return false; + } + return aEditorBase->IsSelectionEditable() && + aEditorBase->IsCutCommandEnabled(); +} + +nsresult CutCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + nsresult rv = aEditorBase.CutAsAction(aPrincipal); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CutAsAction() failed"); + return rv; +} + +nsresult CutCommand::GetCommandStateParams( + Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, + nsIEditingSession* aEditingSession) const { + return aParams.SetBool(STATE_ENABLED, + IsCommandEnabled(aCommand, aEditorBase)); +} + +/****************************************************************************** + * mozilla::CutOrDeleteCommand + ******************************************************************************/ + +StaticRefPtr<CutOrDeleteCommand> CutOrDeleteCommand::sInstance; + +bool CutOrDeleteCommand::IsCommandEnabled(Command aCommand, + EditorBase* aEditorBase) const { + if (!aEditorBase) { + return false; + } + return aEditorBase->IsSelectionEditable(); +} + +nsresult CutOrDeleteCommand::DoCommand(Command aCommand, + EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + dom::Selection* selection = aEditorBase.GetSelection(); + if (selection && selection->IsCollapsed()) { + nsresult rv = aEditorBase.DeleteSelectionAsAction( + nsIEditor::eNext, nsIEditor::eStrip, aPrincipal); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::DeleteSelectionAsAction() failed"); + return rv; + } + nsresult rv = aEditorBase.CutAsAction(aPrincipal); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CutAsAction() failed"); + return rv; +} + +nsresult CutOrDeleteCommand::GetCommandStateParams( + Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, + nsIEditingSession* aEditingSession) const { + return aParams.SetBool(STATE_ENABLED, + IsCommandEnabled(aCommand, aEditorBase)); +} + +/****************************************************************************** + * mozilla::CopyCommand + ******************************************************************************/ + +StaticRefPtr<CopyCommand> CopyCommand::sInstance; + +bool CopyCommand::IsCommandEnabled(Command aCommand, + EditorBase* aEditorBase) const { + if (!aEditorBase) { + return false; + } + return aEditorBase->IsCopyCommandEnabled(); +} + +nsresult CopyCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + // Shouldn't cause "beforeinput" event so that we don't need to specify + // the given principal. + return aEditorBase.Copy(); +} + +nsresult CopyCommand::GetCommandStateParams( + Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, + nsIEditingSession* aEditingSession) const { + return aParams.SetBool(STATE_ENABLED, + IsCommandEnabled(aCommand, aEditorBase)); +} + +/****************************************************************************** + * mozilla::CopyOrDeleteCommand + ******************************************************************************/ + +StaticRefPtr<CopyOrDeleteCommand> CopyOrDeleteCommand::sInstance; + +bool CopyOrDeleteCommand::IsCommandEnabled(Command aCommand, + EditorBase* aEditorBase) const { + if (!aEditorBase) { + return false; + } + return aEditorBase->IsSelectionEditable(); +} + +nsresult CopyOrDeleteCommand::DoCommand(Command aCommand, + EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + dom::Selection* selection = aEditorBase.GetSelection(); + if (selection && selection->IsCollapsed()) { + nsresult rv = aEditorBase.DeleteSelectionAsAction( + nsIEditor::eNextWord, nsIEditor::eStrip, aPrincipal); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::DeleteSelectionAsAction() failed"); + return rv; + } + // Shouldn't cause "beforeinput" event so that we don't need to specify + // the given principal. + nsresult rv = aEditorBase.Copy(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::Copy() failed"); + return rv; +} + +nsresult CopyOrDeleteCommand::GetCommandStateParams( + Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, + nsIEditingSession* aEditingSession) const { + return aParams.SetBool(STATE_ENABLED, + IsCommandEnabled(aCommand, aEditorBase)); +} + +/****************************************************************************** + * mozilla::PasteCommand + ******************************************************************************/ + +StaticRefPtr<PasteCommand> PasteCommand::sInstance; + +bool PasteCommand::IsCommandEnabled(Command aCommand, + EditorBase* aEditorBase) const { + if (!aEditorBase) { + return false; + } + return aEditorBase->IsSelectionEditable() && + aEditorBase->CanPaste(nsIClipboard::kGlobalClipboard); +} + +nsresult PasteCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + nsresult rv = aEditorBase.PasteAsAction(nsIClipboard::kGlobalClipboard, true, + aPrincipal); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::PasteAsAction() failed"); + return rv; +} + +nsresult PasteCommand::GetCommandStateParams( + Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, + nsIEditingSession* aEditingSession) const { + return aParams.SetBool(STATE_ENABLED, + IsCommandEnabled(aCommand, aEditorBase)); +} + +/****************************************************************************** + * mozilla::PasteTransferableCommand + ******************************************************************************/ + +StaticRefPtr<PasteTransferableCommand> PasteTransferableCommand::sInstance; + +bool PasteTransferableCommand::IsCommandEnabled(Command aCommand, + EditorBase* aEditorBase) const { + if (!aEditorBase) { + return false; + } + return aEditorBase->IsSelectionEditable() && + aEditorBase->CanPasteTransferable(nullptr); +} + +nsresult PasteTransferableCommand::DoCommand(Command aCommand, + EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + return NS_ERROR_FAILURE; +} + +nsresult PasteTransferableCommand::DoCommandParam( + Command aCommand, nsITransferable* aTransferableParam, + EditorBase& aEditorBase, nsIPrincipal* aPrincipal) const { + if (NS_WARN_IF(!aTransferableParam)) { + return NS_ERROR_INVALID_ARG; + } + nsresult rv = + aEditorBase.PasteTransferableAsAction(aTransferableParam, aPrincipal); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::PasteTransferableAsAction() failed"); + return rv; +} + +nsresult PasteTransferableCommand::GetCommandStateParams( + Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, + nsIEditingSession* aEditingSession) const { + if (NS_WARN_IF(!aEditorBase)) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsISupports> supports = aParams.GetISupports("transferable"); + if (NS_WARN_IF(!supports)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsITransferable> trans; + trans = do_QueryInterface(supports); + if (NS_WARN_IF(!trans)) { + return NS_ERROR_FAILURE; + } + + return aParams.SetBool(STATE_ENABLED, + aEditorBase->CanPasteTransferable(trans)); +} + +/****************************************************************************** + * mozilla::SwitchTextDirectionCommand + ******************************************************************************/ + +StaticRefPtr<SwitchTextDirectionCommand> SwitchTextDirectionCommand::sInstance; + +bool SwitchTextDirectionCommand::IsCommandEnabled( + Command aCommand, EditorBase* aEditorBase) const { + if (!aEditorBase) { + return false; + } + return aEditorBase->IsSelectionEditable(); +} + +nsresult SwitchTextDirectionCommand::DoCommand(Command aCommand, + EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + nsresult rv = aEditorBase.ToggleTextDirectionAsAction(aPrincipal); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::ToggleTextDirectionAsAction() failed"); + return rv; +} + +nsresult SwitchTextDirectionCommand::GetCommandStateParams( + Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, + nsIEditingSession* aEditingSession) const { + return aParams.SetBool(STATE_ENABLED, + IsCommandEnabled(aCommand, aEditorBase)); +} + +/****************************************************************************** + * mozilla::DeleteCommand + ******************************************************************************/ + +StaticRefPtr<DeleteCommand> DeleteCommand::sInstance; + +bool DeleteCommand::IsCommandEnabled(Command aCommand, + EditorBase* aEditorBase) const { + if (!aEditorBase) { + return false; + } + // 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(); + + if (aCommand == Command::Delete && isEnabled) { + return aEditorBase->CanDeleteSelection(); + } + return isEnabled; +} + +nsresult DeleteCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + nsIEditor::EDirection deleteDir = nsIEditor::eNone; + switch (aCommand) { + case Command::Delete: + // Really this should probably be eNone, but it only makes a difference + // if the selection is collapsed, and then this command is disabled. So + // let's keep it as it always was to avoid breaking things. + deleteDir = nsIEditor::ePrevious; + break; + case Command::DeleteCharForward: + deleteDir = nsIEditor::eNext; + break; + case Command::DeleteCharBackward: + deleteDir = nsIEditor::ePrevious; + break; + case Command::DeleteWordBackward: + deleteDir = nsIEditor::ePreviousWord; + break; + case Command::DeleteWordForward: + deleteDir = nsIEditor::eNextWord; + break; + case Command::DeleteToBeginningOfLine: + deleteDir = nsIEditor::eToBeginningOfLine; + break; + case Command::DeleteToEndOfLine: + deleteDir = nsIEditor::eToEndOfLine; + break; + default: + MOZ_CRASH("Unrecognized nsDeleteCommand"); + } + nsresult rv = aEditorBase.DeleteSelectionAsAction( + deleteDir, nsIEditor::eStrip, aPrincipal); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::DeleteSelectionAsAction() failed"); + return rv; +} + +nsresult DeleteCommand::GetCommandStateParams( + Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, + nsIEditingSession* aEditingSession) const { + return aParams.SetBool(STATE_ENABLED, + IsCommandEnabled(aCommand, aEditorBase)); +} + +/****************************************************************************** + * mozilla::SelectAllCommand + ******************************************************************************/ + +StaticRefPtr<SelectAllCommand> SelectAllCommand::sInstance; + +bool SelectAllCommand::IsCommandEnabled(Command aCommand, + EditorBase* aEditorBase) const { + // You can always select all, unless the selection is editable, + // and the editable region is empty! + if (!aEditorBase) { + return true; + } + + // You can select all if there is an editor which is non-empty + return !aEditorBase->IsEmpty(); +} + +nsresult SelectAllCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + // Shouldn't cause "beforeinput" event so that we don't need to specify + // aPrincipal. + nsresult rv = aEditorBase.SelectAll(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::SelectAll() failed"); + return rv; +} + +nsresult SelectAllCommand::GetCommandStateParams( + Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, + nsIEditingSession* aEditingSession) const { + return aParams.SetBool(STATE_ENABLED, + IsCommandEnabled(aCommand, aEditorBase)); +} + +/****************************************************************************** + * mozilla::SelectionMoveCommands + ******************************************************************************/ + +StaticRefPtr<SelectionMoveCommands> SelectionMoveCommands::sInstance; + +bool SelectionMoveCommands::IsCommandEnabled(Command aCommand, + EditorBase* aEditorBase) const { + if (!aEditorBase) { + return false; + } + return aEditorBase->IsSelectionEditable(); +} + +static const struct ScrollCommand { + Command mReverseScroll; + Command mForwardScroll; + nsresult (NS_STDCALL nsISelectionController::*scroll)(bool); +} scrollCommands[] = {{Command::ScrollTop, Command::ScrollBottom, + &nsISelectionController::CompleteScroll}, + {Command::ScrollPageUp, Command::ScrollPageDown, + &nsISelectionController::ScrollPage}, + {Command::ScrollLineUp, Command::ScrollLineDown, + &nsISelectionController::ScrollLine}}; + +static const struct MoveCommand { + Command mReverseMove; + Command mForwardMove; + Command mReverseSelect; + Command mForwardSelect; + nsresult (NS_STDCALL nsISelectionController::*move)(bool, bool); +} moveCommands[] = { + {Command::CharPrevious, Command::CharNext, Command::SelectCharPrevious, + Command::SelectCharNext, &nsISelectionController::CharacterMove}, + {Command::LinePrevious, Command::LineNext, Command::SelectLinePrevious, + Command::SelectLineNext, &nsISelectionController::LineMove}, + {Command::WordPrevious, Command::WordNext, Command::SelectWordPrevious, + Command::SelectWordNext, &nsISelectionController::WordMove}, + {Command::BeginLine, Command::EndLine, Command::SelectBeginLine, + Command::SelectEndLine, &nsISelectionController::IntraLineMove}, + {Command::MovePageUp, Command::MovePageDown, Command::SelectPageUp, + Command::SelectPageDown, &nsISelectionController::PageMove}, + {Command::MoveTop, Command::MoveBottom, Command::SelectTop, + Command::SelectBottom, &nsISelectionController::CompleteMove}}; + +static const struct PhysicalCommand { + Command mMove; + Command mSelect; + int16_t direction; + int16_t amount; +} physicalCommands[] = { + {Command::MoveLeft, Command::SelectLeft, nsISelectionController::MOVE_LEFT, + 0}, + {Command::MoveRight, Command::SelectRight, + nsISelectionController::MOVE_RIGHT, 0}, + {Command::MoveUp, Command::SelectUp, nsISelectionController::MOVE_UP, 0}, + {Command::MoveDown, Command::SelectDown, nsISelectionController::MOVE_DOWN, + 0}, + {Command::MoveLeft2, Command::SelectLeft2, + nsISelectionController::MOVE_LEFT, 1}, + {Command::MoveRight2, Command::SelectRight2, + nsISelectionController::MOVE_RIGHT, 1}, + {Command::MoveUp2, Command::SelectUp2, nsISelectionController::MOVE_UP, 1}, + {Command::MoveDown2, Command::SelectDown2, + nsISelectionController::MOVE_DOWN, 1}}; + +nsresult SelectionMoveCommands::DoCommand(Command aCommand, + EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + RefPtr<dom::Document> document = aEditorBase.GetDocument(); + if (document) { + // Most of the commands below (possibly all of them) need layout to + // be up to date. + document->FlushPendingNotifications(FlushType::Layout); + } + + nsCOMPtr<nsISelectionController> selectionController = + aEditorBase.GetSelectionController(); + if (NS_WARN_IF(!selectionController)) { + return NS_ERROR_FAILURE; + } + + // scroll commands + for (size_t i = 0; i < ArrayLength(scrollCommands); i++) { + const ScrollCommand& cmd = scrollCommands[i]; + if (aCommand == cmd.mReverseScroll) { + return (selectionController->*(cmd.scroll))(false); + } + if (aCommand == cmd.mForwardScroll) { + return (selectionController->*(cmd.scroll))(true); + } + } + + // caret movement/selection commands + for (size_t i = 0; i < ArrayLength(moveCommands); i++) { + const MoveCommand& cmd = moveCommands[i]; + if (aCommand == cmd.mReverseMove) { + return (selectionController->*(cmd.move))(false, false); + } + if (aCommand == cmd.mForwardMove) { + return (selectionController->*(cmd.move))(true, false); + } + if (aCommand == cmd.mReverseSelect) { + return (selectionController->*(cmd.move))(false, true); + } + if (aCommand == cmd.mForwardSelect) { + return (selectionController->*(cmd.move))(true, true); + } + } + + // physical-direction movement/selection + for (size_t i = 0; i < ArrayLength(physicalCommands); i++) { + const PhysicalCommand& cmd = physicalCommands[i]; + if (aCommand == cmd.mMove) { + nsresult rv = + selectionController->PhysicalMove(cmd.direction, cmd.amount, false); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "nsISelectionController::PhysicalMove() failed to move caret"); + return rv; + } + if (aCommand == cmd.mSelect) { + nsresult rv = + selectionController->PhysicalMove(cmd.direction, cmd.amount, true); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "nsISelectionController::PhysicalMove() failed to select"); + return rv; + } + } + + return NS_ERROR_FAILURE; +} + +nsresult SelectionMoveCommands::GetCommandStateParams( + Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, + nsIEditingSession* aEditingSession) const { + return aParams.SetBool(STATE_ENABLED, + IsCommandEnabled(aCommand, aEditorBase)); +} + +/****************************************************************************** + * mozilla::InsertPlaintextCommand + ******************************************************************************/ + +StaticRefPtr<InsertPlaintextCommand> InsertPlaintextCommand::sInstance; + +bool InsertPlaintextCommand::IsCommandEnabled(Command aCommand, + EditorBase* aEditorBase) const { + if (!aEditorBase) { + return false; + } + return aEditorBase->IsSelectionEditable(); +} + +nsresult InsertPlaintextCommand::DoCommand(Command aCommand, + EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + // XXX InsertTextAsAction() is not same as OnInputText(). However, other + // commands to insert line break or paragraph separator use OnInput*(). + // According to the semantics of those methods, using *AsAction() is + // better, however, this may not cause two or more placeholder + // transactions to the top transaction since its name may not be + // nsGkAtoms::TypingTxnName. + DebugOnly<nsresult> rvIgnored = + aEditorBase.InsertTextAsAction(u""_ns, aPrincipal); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "EditorBase::InsertTextAsAction() failed, but ignored"); + return NS_OK; +} + +nsresult InsertPlaintextCommand::DoCommandParam( + Command aCommand, const nsAString& aStringParam, EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + if (NS_WARN_IF(aStringParam.IsVoid())) { + return NS_ERROR_INVALID_ARG; + } + + // XXX InsertTextAsAction() is not same as OnInputText(). However, other + // commands to insert line break or paragraph separator use OnInput*(). + // According to the semantics of those methods, using *AsAction() is + // better, however, this may not cause two or more placeholder + // transactions to the top transaction since its name may not be + // nsGkAtoms::TypingTxnName. + DebugOnly<nsresult> rvIgnored = + aEditorBase.InsertTextAsAction(aStringParam, aPrincipal); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "EditorBase::InsertTextAsAction() failed, but ignored"); + return NS_OK; +} + +nsresult InsertPlaintextCommand::GetCommandStateParams( + Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, + nsIEditingSession* aEditingSession) const { + return aParams.SetBool(STATE_ENABLED, + IsCommandEnabled(aCommand, aEditorBase)); +} + +/****************************************************************************** + * mozilla::InsertParagraphCommand + ******************************************************************************/ + +StaticRefPtr<InsertParagraphCommand> InsertParagraphCommand::sInstance; + +bool InsertParagraphCommand::IsCommandEnabled(Command aCommand, + EditorBase* aEditorBase) const { + if (!aEditorBase || aEditorBase->IsSingleLineEditor()) { + return false; + } + return aEditorBase->IsSelectionEditable(); +} + +nsresult InsertParagraphCommand::DoCommand(Command aCommand, + EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + if (aEditorBase.IsSingleLineEditor()) { + return NS_ERROR_FAILURE; + } + if (aEditorBase.IsHTMLEditor()) { + nsresult rv = MOZ_KnownLive(aEditorBase.AsHTMLEditor()) + ->InsertParagraphSeparatorAsAction(aPrincipal); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "HTMLEditor::InsertParagraphSeparatorAsAction() failed"); + return rv; + } + nsresult rv = aEditorBase.InsertLineBreakAsAction(aPrincipal); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::InsertLineBreakAsAction() failed"); + return rv; +} + +nsresult InsertParagraphCommand::GetCommandStateParams( + Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, + nsIEditingSession* aEditingSession) const { + return aParams.SetBool(STATE_ENABLED, + IsCommandEnabled(aCommand, aEditorBase)); +} + +/****************************************************************************** + * mozilla::InsertLineBreakCommand + ******************************************************************************/ + +StaticRefPtr<InsertLineBreakCommand> InsertLineBreakCommand::sInstance; + +bool InsertLineBreakCommand::IsCommandEnabled(Command aCommand, + EditorBase* aEditorBase) const { + if (!aEditorBase || aEditorBase->IsSingleLineEditor()) { + return false; + } + return aEditorBase->IsSelectionEditable(); +} + +nsresult InsertLineBreakCommand::DoCommand(Command aCommand, + EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + if (aEditorBase.IsSingleLineEditor()) { + return NS_ERROR_FAILURE; + } + nsresult rv = aEditorBase.InsertLineBreakAsAction(aPrincipal); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::InsertLineBreakAsAction() failed"); + return rv; +} + +nsresult InsertLineBreakCommand::GetCommandStateParams( + Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, + nsIEditingSession* aEditingSession) const { + return aParams.SetBool(STATE_ENABLED, + IsCommandEnabled(aCommand, aEditorBase)); +} + +/****************************************************************************** + * mozilla::PasteQuotationCommand + ******************************************************************************/ + +StaticRefPtr<PasteQuotationCommand> PasteQuotationCommand::sInstance; + +bool PasteQuotationCommand::IsCommandEnabled(Command aCommand, + EditorBase* aEditorBase) const { + if (!aEditorBase) { + return false; + } + return !aEditorBase->IsSingleLineEditor() && + aEditorBase->CanPaste(nsIClipboard::kGlobalClipboard); +} + +nsresult PasteQuotationCommand::DoCommand(Command aCommand, + EditorBase& aEditorBase, + nsIPrincipal* aPrincipal) const { + nsresult rv = aEditorBase.PasteAsQuotationAsAction( + nsIClipboard::kGlobalClipboard, true, aPrincipal); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::PasteAsQuotationAsAction() failed"); + return rv; +} + +nsresult PasteQuotationCommand::GetCommandStateParams( + Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, + nsIEditingSession* aEditingSession) const { + if (!aEditorBase) { + return NS_OK; + } + aParams.SetBool(STATE_ENABLED, + aEditorBase->CanPaste(nsIClipboard::kGlobalClipboard)); + return NS_OK; +} + +} // namespace mozilla |