/* -*- 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 "EditorBase.h" // for EditorBase #include "ErrorList.h" #include "HTMLEditor.h" // for HTMLEditor #include "mozilla/BasePrincipal.h" // for nsIPrincipal::IsSystemPrincipal() #include "mozilla/dom/Element.h" // for Element #include "mozilla/dom/Document.h" // for Document #include "mozilla/dom/HTMLInputElement.h" // for HTMLInputElement #include "mozilla/dom/HTMLTextAreaElement.h" // for HTMLTextAreaElement #include "nsCommandParams.h" // for nsCommandParams #include "nsIEditingSession.h" // for nsIEditingSession, etc #include "nsIPrincipal.h" // for nsIPrincipal #include "nsISupportsImpl.h" // for nsPresContext::Release #include "nsISupportsUtils.h" // for NS_IF_ADDREF #include "nsIURI.h" // for nsIURI #include "nsPresContext.h" // for nsPresContext // defines #define STATE_ENABLED "state_enabled" #define STATE_ALL "state_all" #define STATE_ATTRIBUTE "state_attribute" #define STATE_DATA "state_data" namespace mozilla { using namespace dom; /***************************************************************************** * mozilla::SetDocumentStateCommand * * Commands for document state that may be changed via doCommandParams * As of 11/11/02, this is just "cmd_setDocumentModified" * Note that you can use the same command class, SetDocumentStateCommand, * for more than one of this type of command * We check the input command param for different behavior *****************************************************************************/ StaticRefPtr SetDocumentStateCommand::sInstance; bool SetDocumentStateCommand::IsCommandEnabled(Command aCommand, EditorBase* aEditorBase) const { switch (aCommand) { case Command::SetDocumentReadOnly: return !!aEditorBase; default: // The other commands are always enabled if given editor is an HTMLEditor. return aEditorBase && aEditorBase->IsHTMLEditor(); } } nsresult SetDocumentStateCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, nsIPrincipal* aPrincipal) const { return NS_ERROR_NOT_IMPLEMENTED; } nsresult SetDocumentStateCommand::DoCommandParam( Command aCommand, const Maybe& aBoolParam, EditorBase& aEditorBase, nsIPrincipal* aPrincipal) const { if (NS_WARN_IF(aBoolParam.isNothing())) { return NS_ERROR_INVALID_ARG; } if (aCommand != Command::SetDocumentReadOnly && NS_WARN_IF(!aEditorBase.IsHTMLEditor())) { return NS_ERROR_FAILURE; } switch (aCommand) { case Command::SetDocumentModified: { if (aBoolParam.value()) { nsresult rv = aEditorBase.IncrementModificationCount(1); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::IncrementModificationCount() failed"); return rv; } nsresult rv = aEditorBase.ResetModificationCount(); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::ResetModificationCount() failed"); return rv; } case Command::SetDocumentReadOnly: { if (aEditorBase.IsTextEditor()) { Element* inputOrTextArea = aEditorBase.GetExposedRoot(); if (NS_WARN_IF(!inputOrTextArea)) { return NS_ERROR_FAILURE; } // Perhaps, this legacy command shouldn't work with // `` and `. if (inputOrTextArea->IsInNativeAnonymousSubtree()) { return NS_ERROR_FAILURE; } if (RefPtr inputElement = HTMLInputElement::FromNode(inputOrTextArea)) { if (inputElement->ReadOnly() == aBoolParam.value()) { return NS_SUCCESS_DOM_NO_OPERATION; } ErrorResult error; inputElement->SetReadOnly(aBoolParam.value(), error); return error.StealNSResult(); } if (RefPtr textAreaElement = HTMLTextAreaElement::FromNode(inputOrTextArea)) { if (textAreaElement->ReadOnly() == aBoolParam.value()) { return NS_SUCCESS_DOM_NO_OPERATION; } ErrorResult error; textAreaElement->SetReadOnly(aBoolParam.value(), error); return error.StealNSResult(); } NS_ASSERTION( false, "Unexpected exposed root element, fallthrough to directly make the " "editor readonly"); } ErrorResult error; if (aBoolParam.value()) { nsresult rv = aEditorBase.AddFlags(nsIEditor::eEditorReadonlyMask); NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), "EditorBase::AddFlags(nsIEditor::eEditorReadonlyMask) failed"); return rv; } nsresult rv = aEditorBase.RemoveFlags(nsIEditor::eEditorReadonlyMask); NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), "EditorBase::RemoveFlags(nsIEditor::eEditorReadonlyMask) failed"); return rv; } case Command::SetDocumentUseCSS: { nsresult rv = MOZ_KnownLive(aEditorBase.AsHTMLEditor()) ->SetIsCSSEnabled(aBoolParam.value()); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::SetIsCSSEnabled() failed"); return rv; } case Command::SetDocumentInsertBROnEnterKeyPress: { nsresult rv = aEditorBase.AsHTMLEditor()->SetReturnInParagraphCreatesNewParagraph( !aBoolParam.value()); NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), "HTMLEditor::SetReturnInParagraphCreatesNewParagraph() failed"); return rv; } case Command::ToggleObjectResizers: { MOZ_KnownLive(aEditorBase.AsHTMLEditor()) ->EnableObjectResizer(aBoolParam.value()); return NS_OK; } case Command::ToggleInlineTableEditor: { MOZ_KnownLive(aEditorBase.AsHTMLEditor()) ->EnableInlineTableEditor(aBoolParam.value()); return NS_OK; } case Command::ToggleAbsolutePositionEditor: { MOZ_KnownLive(aEditorBase.AsHTMLEditor()) ->EnableAbsolutePositionEditor(aBoolParam.value()); return NS_OK; } case Command::EnableCompatibleJoinSplitNodeDirection: // Now we don't support the legacy join/split node direction anymore, but // this result may be used for the feature detection whether Gecko // supports the new direction mode. Therefore, even though we do nothing, // but we should return NS_OK to return `true` from // `Document.execCommand()`. return NS_OK; default: return NS_ERROR_NOT_IMPLEMENTED; } } nsresult SetDocumentStateCommand::DoCommandParam( Command aCommand, const nsACString& aCStringParam, EditorBase& aEditorBase, nsIPrincipal* aPrincipal) const { if (NS_WARN_IF(aCStringParam.IsVoid())) { return NS_ERROR_INVALID_ARG; } if (NS_WARN_IF(!aEditorBase.IsHTMLEditor())) { return NS_ERROR_FAILURE; } switch (aCommand) { case Command::SetDocumentDefaultParagraphSeparator: { if (aCStringParam.LowerCaseEqualsLiteral("div")) { aEditorBase.AsHTMLEditor()->SetDefaultParagraphSeparator( ParagraphSeparator::div); return NS_OK; } if (aCStringParam.LowerCaseEqualsLiteral("p")) { aEditorBase.AsHTMLEditor()->SetDefaultParagraphSeparator( ParagraphSeparator::p); return NS_OK; } if (aCStringParam.LowerCaseEqualsLiteral("br")) { // Mozilla extension for backwards compatibility aEditorBase.AsHTMLEditor()->SetDefaultParagraphSeparator( ParagraphSeparator::br); return NS_OK; } // This should not be reachable from nsHTMLDocument::ExecCommand // XXX Shouldn't return error in this case because Chrome does not throw // exception in this case. NS_WARNING("Invalid default paragraph separator"); return NS_ERROR_UNEXPECTED; } default: return NS_ERROR_NOT_IMPLEMENTED; } } nsresult SetDocumentStateCommand::GetCommandStateParams( Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, nsIEditingSession* aEditingSession) const { // If the result is set to STATE_ATTRIBUTE as CString value, // queryCommandValue() returns the string value. // Otherwise, ignored. // The base editor owns most state info if (NS_WARN_IF(!aEditorBase)) { return NS_ERROR_INVALID_ARG; } if (NS_WARN_IF(!aEditorBase->IsHTMLEditor())) { return NS_ERROR_FAILURE; } // Always get the enabled state nsresult rv = aParams.SetBool(STATE_ENABLED, IsCommandEnabled(aCommand, aEditorBase)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } switch (aCommand) { case Command::SetDocumentModified: { bool modified; rv = aEditorBase->GetDocumentModified(&modified); if (NS_FAILED(rv)) { NS_WARNING("EditorBase::GetDocumentModified() failed"); return rv; } // XXX Nobody refers this result due to wrong type. rv = aParams.SetBool(STATE_ATTRIBUTE, modified); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsCommandParams::SetBool(STATE_ATTRIBUTE) failed"); return rv; } case Command::SetDocumentReadOnly: { // XXX Nobody refers this result due to wrong type. rv = aParams.SetBool(STATE_ATTRIBUTE, aEditorBase->IsReadonly()); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsCommandParams::SetBool(STATE_ATTRIBUTE) failed"); return rv; } case Command::SetDocumentUseCSS: { HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor(); if (NS_WARN_IF(!htmlEditor)) { return NS_ERROR_INVALID_ARG; } rv = aParams.SetBool(STATE_ALL, htmlEditor->IsCSSEnabled()); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsCommandParams::SetBool(STATE_ALL) failed"); return rv; } case Command::SetDocumentInsertBROnEnterKeyPress: { HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor(); if (NS_WARN_IF(!htmlEditor)) { return NS_ERROR_INVALID_ARG; } bool createPOnReturn; DebugOnly rvIgnored = htmlEditor->GetReturnInParagraphCreatesNewParagraph(&createPOnReturn); NS_WARNING_ASSERTION( NS_SUCCEEDED(rvIgnored), "HTMLEditor::GetReturnInParagraphCreatesNewParagraph() failed"); // XXX Nobody refers this result due to wrong type. rv = aParams.SetBool(STATE_ATTRIBUTE, !createPOnReturn); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsCommandParams::SetBool(STATE_ATTRIBUTE) failed"); return rv; } case Command::SetDocumentDefaultParagraphSeparator: { HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor(); if (NS_WARN_IF(!htmlEditor)) { return NS_ERROR_INVALID_ARG; } switch (htmlEditor->GetDefaultParagraphSeparator()) { case ParagraphSeparator::div: { DebugOnly rv = aParams.SetCString(STATE_ATTRIBUTE, "div"_ns); NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), "Failed to set command params to return \"div\""); return NS_OK; } case ParagraphSeparator::p: { DebugOnly rv = aParams.SetCString(STATE_ATTRIBUTE, "p"_ns); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to set command params to return \"p\""); return NS_OK; } case ParagraphSeparator::br: { DebugOnly rv = aParams.SetCString(STATE_ATTRIBUTE, "br"_ns); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to set command params to return \"br\""); return NS_OK; } default: MOZ_ASSERT_UNREACHABLE("Invalid paragraph separator value"); return NS_ERROR_UNEXPECTED; } } case Command::ToggleObjectResizers: { HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor(); if (NS_WARN_IF(!htmlEditor)) { return NS_ERROR_INVALID_ARG; } // We returned the result as STATE_ATTRIBUTE with bool value 60 or // earlier. So, the result was ignored by both // nsHTMLDocument::QueryCommandValue() and // nsHTMLDocument::QueryCommandState(). rv = aParams.SetBool(STATE_ALL, htmlEditor->IsObjectResizerEnabled()); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsCommandParams::SetBool(STATE_ALL) failed"); return rv; } case Command::ToggleInlineTableEditor: { HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor(); if (NS_WARN_IF(!htmlEditor)) { return NS_ERROR_INVALID_ARG; } // We returned the result as STATE_ATTRIBUTE with bool value 60 or // earlier. So, the result was ignored by both // nsHTMLDocument::QueryCommandValue() and // nsHTMLDocument::QueryCommandState(). rv = aParams.SetBool(STATE_ALL, htmlEditor->IsInlineTableEditorEnabled()); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsCommandParams::SetBool(STATE_ALL) failed"); return rv; } case Command::ToggleAbsolutePositionEditor: { HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor(); if (NS_WARN_IF(!htmlEditor)) { return NS_ERROR_INVALID_ARG; } return aParams.SetBool(STATE_ALL, htmlEditor->IsAbsolutePositionEditorEnabled()); } case Command::EnableCompatibleJoinSplitNodeDirection: { HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor(); if (NS_WARN_IF(!htmlEditor)) { return NS_ERROR_INVALID_ARG; } // Now we don't support the legacy join/split node direction anymore, but // this result may be used for the feature detection whether Gecko // supports the new direction mode. Therefore, we should return `true` // even though executing the command does nothing. return aParams.SetBool(STATE_ALL, true); } default: return NS_ERROR_NOT_IMPLEMENTED; } } /***************************************************************************** * mozilla::DocumentStateCommand * * Commands just for state notification * As of 11/21/02, possible commands are: * "obs_documentCreated" * "obs_documentWillBeDestroyed" * "obs_documentLocationChanged" * Note that you can use the same command class, DocumentStateCommand * for these or future observer commands. * We check the input command param for different behavior * * How to use: * 1. Get the nsCommandManager for the current editor * 2. Implement an nsIObserve object, e.g: * * void Observe( * in nsISupports aSubject, // The nsCommandManager calling this * // Observer * in string aTopic, // command name, e.g.:"obs_documentCreated" * // or "obs_documentWillBeDestroyed" in wstring aData ); // ignored (set to "command_status_changed") * * 3. Add the observer by: * commandManager.addObserver(observeobject, obs_documentCreated); * 4. In the appropriate location in editorSession, editor, or commands code, * trigger the notification of this observer by something like: * * RefPtr commandManager = mDocShell->GetCommandManager(); * commandManager->CommandStatusChanged(obs_documentCreated); * * 5. Use GetCommandStateParams() to obtain state information * e.g., any creation state codes when creating an editor are * supplied for "obs_documentCreated" command in the * "state_data" param's value *****************************************************************************/ StaticRefPtr DocumentStateCommand::sInstance; bool DocumentStateCommand::IsCommandEnabled(Command aCommand, EditorBase* aEditorBase) const { // Always return false to discourage callers from using DoCommand() return false; } nsresult DocumentStateCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, nsIPrincipal* aPrincipal) const { return NS_ERROR_NOT_IMPLEMENTED; } nsresult DocumentStateCommand::GetCommandStateParams( Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, nsIEditingSession* aEditingSession) const { switch (aCommand) { case Command::EditorObserverDocumentCreated: { uint32_t editorStatus = nsIEditingSession::eEditorErrorUnknown; if (aEditingSession) { // Current context is initially set to nsIEditingSession until editor is // successfully created and source doc is loaded. Embedder gets error // status if this fails. If called before startup is finished, // status will be eEditorCreationInProgress. nsresult rv = aEditingSession->GetEditorStatus(&editorStatus); if (NS_FAILED(rv)) { NS_WARNING("nsIEditingSession::GetEditorStatus() failed"); return rv; } } else if (aEditorBase) { // If current context is an editor, then everything started up OK! editorStatus = nsIEditingSession::eEditorOK; } // Note that if refCon is not-null, but is neither // an nsIEditingSession or nsIEditor, we return "eEditorErrorUnknown" DebugOnly rvIgnored = aParams.SetInt(STATE_DATA, editorStatus); NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "Failed to set editor status"); return NS_OK; } case Command::EditorObserverDocumentLocationChanged: { if (!aEditorBase) { return NS_OK; } Document* document = aEditorBase->GetDocument(); if (NS_WARN_IF(!document)) { return NS_ERROR_FAILURE; } nsIURI* uri = document->GetDocumentURI(); if (NS_WARN_IF(!uri)) { return NS_ERROR_FAILURE; } nsresult rv = aParams.SetISupports(STATE_DATA, uri); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsCOmmandParms::SetISupports(STATE_DATA) failed"); return rv; } default: return NS_ERROR_NOT_IMPLEMENTED; } } } // namespace mozilla