Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#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> 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<bool>& 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
// `<input type="file">` and `<input type="number">.
if (inputOrTextArea->IsInNativeAnonymousSubtree()) {
return NS_ERROR_FAILURE;
}
if (RefPtr<HTMLInputElement> 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<HTMLTextAreaElement> 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<nsresult> 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<nsresult> 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<nsresult> 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<nsresult> 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<nsCommandManager> 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> 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<nsresult> 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