tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

HTMLEditorDocumentCommands.cpp (18754B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "EditorCommands.h"
      7 
      8 #include "EditorBase.h"  // for EditorBase
      9 #include "ErrorList.h"
     10 #include "HTMLEditor.h"  // for HTMLEditor
     11 
     12 #include "mozilla/BasePrincipal.h"  // for nsIPrincipal::IsSystemPrincipal()
     13 #include "mozilla/dom/Element.h"    // for Element
     14 #include "mozilla/dom/Document.h"   // for Document
     15 #include "mozilla/dom/HTMLInputElement.h"     // for HTMLInputElement
     16 #include "mozilla/dom/HTMLTextAreaElement.h"  // for HTMLTextAreaElement
     17 
     18 #include "nsCommandParams.h"    // for nsCommandParams
     19 #include "nsIEditingSession.h"  // for nsIEditingSession, etc
     20 #include "nsIPrincipal.h"       // for nsIPrincipal
     21 #include "nsISupportsImpl.h"    // for nsPresContext::Release
     22 #include "nsISupportsUtils.h"   // for NS_IF_ADDREF
     23 #include "nsIURI.h"             // for nsIURI
     24 #include "nsPresContext.h"      // for nsPresContext
     25 
     26 // defines
     27 #define STATE_ENABLED "state_enabled"
     28 #define STATE_ALL "state_all"
     29 #define STATE_ATTRIBUTE "state_attribute"
     30 #define STATE_DATA "state_data"
     31 
     32 namespace mozilla {
     33 
     34 using namespace dom;
     35 
     36 /*****************************************************************************
     37 * mozilla::SetDocumentStateCommand
     38 *
     39 *  Commands for document state that may be changed via doCommandParams
     40 *  As of 11/11/02, this is just "cmd_setDocumentModified"
     41 *  Note that you can use the same command class, SetDocumentStateCommand,
     42 *    for more than one of this type of command
     43 *    We check the input command param for different behavior
     44 *****************************************************************************/
     45 
     46 StaticRefPtr<SetDocumentStateCommand> SetDocumentStateCommand::sInstance;
     47 
     48 bool SetDocumentStateCommand::IsCommandEnabled(Command aCommand,
     49                                               EditorBase* aEditorBase) const {
     50  switch (aCommand) {
     51    case Command::SetDocumentReadOnly:
     52      return !!aEditorBase;
     53    default:
     54      // The other commands are always enabled if given editor is an HTMLEditor.
     55      return aEditorBase && aEditorBase->IsHTMLEditor();
     56  }
     57 }
     58 
     59 nsresult SetDocumentStateCommand::DoCommand(Command aCommand,
     60                                            EditorBase& aEditorBase,
     61                                            nsIPrincipal* aPrincipal) const {
     62  return NS_ERROR_NOT_IMPLEMENTED;
     63 }
     64 
     65 nsresult SetDocumentStateCommand::DoCommandParam(
     66    Command aCommand, const Maybe<bool>& aBoolParam, EditorBase& aEditorBase,
     67    nsIPrincipal* aPrincipal) const {
     68  if (NS_WARN_IF(aBoolParam.isNothing())) {
     69    return NS_ERROR_INVALID_ARG;
     70  }
     71 
     72  if (aCommand != Command::SetDocumentReadOnly &&
     73      NS_WARN_IF(!aEditorBase.IsHTMLEditor())) {
     74    return NS_ERROR_FAILURE;
     75  }
     76 
     77  switch (aCommand) {
     78    case Command::SetDocumentModified: {
     79      if (aBoolParam.value()) {
     80        nsresult rv = aEditorBase.IncrementModificationCount(1);
     81        NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
     82                             "EditorBase::IncrementModificationCount() failed");
     83        return rv;
     84      }
     85      nsresult rv = aEditorBase.ResetModificationCount();
     86      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
     87                           "EditorBase::ResetModificationCount() failed");
     88      return rv;
     89    }
     90    case Command::SetDocumentReadOnly: {
     91      if (aEditorBase.IsTextEditor()) {
     92        Element* inputOrTextArea = aEditorBase.GetExposedRoot();
     93        if (NS_WARN_IF(!inputOrTextArea)) {
     94          return NS_ERROR_FAILURE;
     95        }
     96        // Perhaps, this legacy command shouldn't work with
     97        // `<input type="file">` and `<input type="number">.
     98        if (inputOrTextArea->IsInNativeAnonymousSubtree()) {
     99          return NS_ERROR_FAILURE;
    100        }
    101        if (RefPtr<HTMLInputElement> inputElement =
    102                HTMLInputElement::FromNode(inputOrTextArea)) {
    103          if (inputElement->ReadOnly() == aBoolParam.value()) {
    104            return NS_SUCCESS_DOM_NO_OPERATION;
    105          }
    106          ErrorResult error;
    107          inputElement->SetReadOnly(aBoolParam.value(), error);
    108          return error.StealNSResult();
    109        }
    110        if (RefPtr<HTMLTextAreaElement> textAreaElement =
    111                HTMLTextAreaElement::FromNode(inputOrTextArea)) {
    112          if (textAreaElement->ReadOnly() == aBoolParam.value()) {
    113            return NS_SUCCESS_DOM_NO_OPERATION;
    114          }
    115          ErrorResult error;
    116          textAreaElement->SetReadOnly(aBoolParam.value(), error);
    117          return error.StealNSResult();
    118        }
    119        NS_ASSERTION(
    120            false,
    121            "Unexpected exposed root element, fallthrough to directly make the "
    122            "editor readonly");
    123      }
    124      ErrorResult error;
    125      if (aBoolParam.value()) {
    126        nsresult rv = aEditorBase.AddFlags(nsIEditor::eEditorReadonlyMask);
    127        NS_WARNING_ASSERTION(
    128            NS_SUCCEEDED(rv),
    129            "EditorBase::AddFlags(nsIEditor::eEditorReadonlyMask) failed");
    130        return rv;
    131      }
    132      nsresult rv = aEditorBase.RemoveFlags(nsIEditor::eEditorReadonlyMask);
    133      NS_WARNING_ASSERTION(
    134          NS_SUCCEEDED(rv),
    135          "EditorBase::RemoveFlags(nsIEditor::eEditorReadonlyMask) failed");
    136      return rv;
    137    }
    138    case Command::SetDocumentUseCSS: {
    139      nsresult rv = MOZ_KnownLive(aEditorBase.AsHTMLEditor())
    140                        ->SetIsCSSEnabled(aBoolParam.value());
    141      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    142                           "HTMLEditor::SetIsCSSEnabled() failed");
    143      return rv;
    144    }
    145    case Command::SetDocumentInsertBROnEnterKeyPress: {
    146      nsresult rv =
    147          aEditorBase.AsHTMLEditor()->SetReturnInParagraphCreatesNewParagraph(
    148              !aBoolParam.value());
    149      NS_WARNING_ASSERTION(
    150          NS_SUCCEEDED(rv),
    151          "HTMLEditor::SetReturnInParagraphCreatesNewParagraph() failed");
    152      return rv;
    153    }
    154    case Command::ToggleObjectResizers: {
    155      MOZ_KnownLive(aEditorBase.AsHTMLEditor())
    156          ->EnableObjectResizer(aBoolParam.value());
    157      return NS_OK;
    158    }
    159    case Command::ToggleInlineTableEditor: {
    160      MOZ_KnownLive(aEditorBase.AsHTMLEditor())
    161          ->EnableInlineTableEditor(aBoolParam.value());
    162      return NS_OK;
    163    }
    164    case Command::ToggleAbsolutePositionEditor: {
    165      MOZ_KnownLive(aEditorBase.AsHTMLEditor())
    166          ->EnableAbsolutePositionEditor(aBoolParam.value());
    167      return NS_OK;
    168    }
    169    case Command::EnableCompatibleJoinSplitNodeDirection:
    170      // Now we don't support the legacy join/split node direction anymore, but
    171      // this result may be used for the feature detection whether Gecko
    172      // supports the new direction mode.  Therefore, even though we do nothing,
    173      // but we should return NS_OK to return `true` from
    174      // `Document.execCommand()`.
    175      return NS_OK;
    176    default:
    177      return NS_ERROR_NOT_IMPLEMENTED;
    178  }
    179 }
    180 
    181 nsresult SetDocumentStateCommand::DoCommandParam(
    182    Command aCommand, const nsACString& aCStringParam, EditorBase& aEditorBase,
    183    nsIPrincipal* aPrincipal) const {
    184  if (NS_WARN_IF(aCStringParam.IsVoid())) {
    185    return NS_ERROR_INVALID_ARG;
    186  }
    187 
    188  if (NS_WARN_IF(!aEditorBase.IsHTMLEditor())) {
    189    return NS_ERROR_FAILURE;
    190  }
    191 
    192  switch (aCommand) {
    193    case Command::SetDocumentDefaultParagraphSeparator: {
    194      if (aCStringParam.LowerCaseEqualsLiteral("div")) {
    195        aEditorBase.AsHTMLEditor()->SetDefaultParagraphSeparator(
    196            ParagraphSeparator::div);
    197        return NS_OK;
    198      }
    199      if (aCStringParam.LowerCaseEqualsLiteral("p")) {
    200        aEditorBase.AsHTMLEditor()->SetDefaultParagraphSeparator(
    201            ParagraphSeparator::p);
    202        return NS_OK;
    203      }
    204      if (aCStringParam.LowerCaseEqualsLiteral("br")) {
    205        // Mozilla extension for backwards compatibility
    206        aEditorBase.AsHTMLEditor()->SetDefaultParagraphSeparator(
    207            ParagraphSeparator::br);
    208        return NS_OK;
    209      }
    210 
    211      // This should not be reachable from nsHTMLDocument::ExecCommand
    212      // XXX Shouldn't return error in this case because Chrome does not throw
    213      //     exception in this case.
    214      NS_WARNING("Invalid default paragraph separator");
    215      return NS_ERROR_UNEXPECTED;
    216    }
    217    default:
    218      return NS_ERROR_NOT_IMPLEMENTED;
    219  }
    220 }
    221 
    222 nsresult SetDocumentStateCommand::GetCommandStateParams(
    223    Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase,
    224    nsIEditingSession* aEditingSession) const {
    225  // If the result is set to STATE_ATTRIBUTE as CString value,
    226  // queryCommandValue() returns the string value.
    227  // Otherwise, ignored.
    228 
    229  // The base editor owns most state info
    230  if (NS_WARN_IF(!aEditorBase)) {
    231    return NS_ERROR_INVALID_ARG;
    232  }
    233 
    234  if (NS_WARN_IF(!aEditorBase->IsHTMLEditor())) {
    235    return NS_ERROR_FAILURE;
    236  }
    237 
    238  // Always get the enabled state
    239  nsresult rv =
    240      aParams.SetBool(STATE_ENABLED, IsCommandEnabled(aCommand, aEditorBase));
    241  if (NS_WARN_IF(NS_FAILED(rv))) {
    242    return rv;
    243  }
    244 
    245  switch (aCommand) {
    246    case Command::SetDocumentModified: {
    247      bool modified;
    248      rv = aEditorBase->GetDocumentModified(&modified);
    249      if (NS_FAILED(rv)) {
    250        NS_WARNING("EditorBase::GetDocumentModified() failed");
    251        return rv;
    252      }
    253      // XXX Nobody refers this result due to wrong type.
    254      rv = aParams.SetBool(STATE_ATTRIBUTE, modified);
    255      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    256                           "nsCommandParams::SetBool(STATE_ATTRIBUTE) failed");
    257      return rv;
    258    }
    259    case Command::SetDocumentReadOnly: {
    260      // XXX Nobody refers this result due to wrong type.
    261      rv = aParams.SetBool(STATE_ATTRIBUTE, aEditorBase->IsReadonly());
    262      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    263                           "nsCommandParams::SetBool(STATE_ATTRIBUTE) failed");
    264      return rv;
    265    }
    266    case Command::SetDocumentUseCSS: {
    267      HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor();
    268      if (NS_WARN_IF(!htmlEditor)) {
    269        return NS_ERROR_INVALID_ARG;
    270      }
    271      rv = aParams.SetBool(STATE_ALL, htmlEditor->IsCSSEnabled());
    272      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    273                           "nsCommandParams::SetBool(STATE_ALL) failed");
    274      return rv;
    275    }
    276    case Command::SetDocumentInsertBROnEnterKeyPress: {
    277      HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor();
    278      if (NS_WARN_IF(!htmlEditor)) {
    279        return NS_ERROR_INVALID_ARG;
    280      }
    281      bool createPOnReturn;
    282      DebugOnly<nsresult> rvIgnored =
    283          htmlEditor->GetReturnInParagraphCreatesNewParagraph(&createPOnReturn);
    284      NS_WARNING_ASSERTION(
    285          NS_SUCCEEDED(rvIgnored),
    286          "HTMLEditor::GetReturnInParagraphCreatesNewParagraph() failed");
    287      // XXX Nobody refers this result due to wrong type.
    288      rv = aParams.SetBool(STATE_ATTRIBUTE, !createPOnReturn);
    289      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    290                           "nsCommandParams::SetBool(STATE_ATTRIBUTE) failed");
    291      return rv;
    292    }
    293    case Command::SetDocumentDefaultParagraphSeparator: {
    294      HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor();
    295      if (NS_WARN_IF(!htmlEditor)) {
    296        return NS_ERROR_INVALID_ARG;
    297      }
    298 
    299      switch (htmlEditor->GetDefaultParagraphSeparator()) {
    300        case ParagraphSeparator::div: {
    301          DebugOnly<nsresult> rv =
    302              aParams.SetCString(STATE_ATTRIBUTE, "div"_ns);
    303          NS_WARNING_ASSERTION(
    304              NS_SUCCEEDED(rv),
    305              "Failed to set command params to return \"div\"");
    306          return NS_OK;
    307        }
    308        case ParagraphSeparator::p: {
    309          DebugOnly<nsresult> rv = aParams.SetCString(STATE_ATTRIBUTE, "p"_ns);
    310          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    311                               "Failed to set command params to return \"p\"");
    312          return NS_OK;
    313        }
    314        case ParagraphSeparator::br: {
    315          DebugOnly<nsresult> rv = aParams.SetCString(STATE_ATTRIBUTE, "br"_ns);
    316          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    317                               "Failed to set command params to return \"br\"");
    318          return NS_OK;
    319        }
    320        default:
    321          MOZ_ASSERT_UNREACHABLE("Invalid paragraph separator value");
    322          return NS_ERROR_UNEXPECTED;
    323      }
    324    }
    325    case Command::ToggleObjectResizers: {
    326      HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor();
    327      if (NS_WARN_IF(!htmlEditor)) {
    328        return NS_ERROR_INVALID_ARG;
    329      }
    330      // We returned the result as STATE_ATTRIBUTE with bool value 60 or
    331      // earlier. So, the result was ignored by both
    332      // nsHTMLDocument::QueryCommandValue() and
    333      // nsHTMLDocument::QueryCommandState().
    334      rv = aParams.SetBool(STATE_ALL, htmlEditor->IsObjectResizerEnabled());
    335      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    336                           "nsCommandParams::SetBool(STATE_ALL) failed");
    337      return rv;
    338    }
    339    case Command::ToggleInlineTableEditor: {
    340      HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor();
    341      if (NS_WARN_IF(!htmlEditor)) {
    342        return NS_ERROR_INVALID_ARG;
    343      }
    344      // We returned the result as STATE_ATTRIBUTE with bool value 60 or
    345      // earlier. So, the result was ignored by both
    346      // nsHTMLDocument::QueryCommandValue() and
    347      // nsHTMLDocument::QueryCommandState().
    348      rv = aParams.SetBool(STATE_ALL, htmlEditor->IsInlineTableEditorEnabled());
    349      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    350                           "nsCommandParams::SetBool(STATE_ALL) failed");
    351      return rv;
    352    }
    353    case Command::ToggleAbsolutePositionEditor: {
    354      HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor();
    355      if (NS_WARN_IF(!htmlEditor)) {
    356        return NS_ERROR_INVALID_ARG;
    357      }
    358      return aParams.SetBool(STATE_ALL,
    359                             htmlEditor->IsAbsolutePositionEditorEnabled());
    360    }
    361    case Command::EnableCompatibleJoinSplitNodeDirection: {
    362      HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor();
    363      if (NS_WARN_IF(!htmlEditor)) {
    364        return NS_ERROR_INVALID_ARG;
    365      }
    366      // Now we don't support the legacy join/split node direction anymore, but
    367      // this result may be used for the feature detection whether Gecko
    368      // supports the new direction mode.  Therefore, we should return `true`
    369      // even though executing the command does nothing.
    370      return aParams.SetBool(STATE_ALL, true);
    371    }
    372    default:
    373      return NS_ERROR_NOT_IMPLEMENTED;
    374  }
    375 }
    376 
    377 /*****************************************************************************
    378 * mozilla::DocumentStateCommand
    379 *
    380 * Commands just for state notification
    381 *  As of 11/21/02, possible commands are:
    382 *    "obs_documentCreated"
    383 *    "obs_documentWillBeDestroyed"
    384 *    "obs_documentLocationChanged"
    385 *  Note that you can use the same command class, DocumentStateCommand
    386 *    for these or future observer commands.
    387 *    We check the input command param for different behavior
    388 *
    389 *  How to use:
    390 *  1. Get the nsCommandManager for the current editor
    391 *  2. Implement an nsIObserve object, e.g:
    392 *
    393 *    void Observe(
    394 *        in nsISupports aSubject, // The nsCommandManager calling this
    395 *                                 // Observer
    396 *        in string      aTopic,   // command name, e.g.:"obs_documentCreated"
    397 *                                 //    or "obs_documentWillBeDestroyed"
    398          in wstring     aData );  // ignored (set to "command_status_changed")
    399 *
    400 *  3. Add the observer by:
    401 *       commandManager.addObserver(observeobject, obs_documentCreated);
    402 *  4. In the appropriate location in editorSession, editor, or commands code,
    403 *     trigger the notification of this observer by something like:
    404 *
    405 *  RefPtr<nsCommandManager> commandManager = mDocShell->GetCommandManager();
    406 *  commandManager->CommandStatusChanged(obs_documentCreated);
    407 *
    408 *  5. Use GetCommandStateParams() to obtain state information
    409 *     e.g., any creation state codes when creating an editor are
    410 *     supplied for "obs_documentCreated" command in the
    411 *     "state_data" param's value
    412 *****************************************************************************/
    413 
    414 StaticRefPtr<DocumentStateCommand> DocumentStateCommand::sInstance;
    415 
    416 bool DocumentStateCommand::IsCommandEnabled(Command aCommand,
    417                                            EditorBase* aEditorBase) const {
    418  // Always return false to discourage callers from using DoCommand()
    419  return false;
    420 }
    421 
    422 nsresult DocumentStateCommand::DoCommand(Command aCommand,
    423                                         EditorBase& aEditorBase,
    424                                         nsIPrincipal* aPrincipal) const {
    425  return NS_ERROR_NOT_IMPLEMENTED;
    426 }
    427 
    428 nsresult DocumentStateCommand::GetCommandStateParams(
    429    Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase,
    430    nsIEditingSession* aEditingSession) const {
    431  switch (aCommand) {
    432    case Command::EditorObserverDocumentCreated: {
    433      uint32_t editorStatus = nsIEditingSession::eEditorErrorUnknown;
    434      if (aEditingSession) {
    435        // Current context is initially set to nsIEditingSession until editor is
    436        // successfully created and source doc is loaded.  Embedder gets error
    437        // status if this fails.  If called before startup is finished,
    438        // status will be eEditorCreationInProgress.
    439        nsresult rv = aEditingSession->GetEditorStatus(&editorStatus);
    440        if (NS_FAILED(rv)) {
    441          NS_WARNING("nsIEditingSession::GetEditorStatus() failed");
    442          return rv;
    443        }
    444      } else if (aEditorBase) {
    445        // If current context is an editor, then everything started up OK!
    446        editorStatus = nsIEditingSession::eEditorOK;
    447      }
    448 
    449      // Note that if refCon is not-null, but is neither
    450      // an nsIEditingSession or nsIEditor, we return "eEditorErrorUnknown"
    451      DebugOnly<nsresult> rvIgnored = aParams.SetInt(STATE_DATA, editorStatus);
    452      NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
    453                           "Failed to set editor status");
    454      return NS_OK;
    455    }
    456    case Command::EditorObserverDocumentLocationChanged: {
    457      if (!aEditorBase) {
    458        return NS_OK;
    459      }
    460      Document* document = aEditorBase->GetDocument();
    461      if (NS_WARN_IF(!document)) {
    462        return NS_ERROR_FAILURE;
    463      }
    464      nsIURI* uri = document->GetDocumentURI();
    465      if (NS_WARN_IF(!uri)) {
    466        return NS_ERROR_FAILURE;
    467      }
    468      nsresult rv = aParams.SetISupports(STATE_DATA, uri);
    469      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    470                           "nsCOmmandParms::SetISupports(STATE_DATA) failed");
    471      return rv;
    472    }
    473    default:
    474      return NS_ERROR_NOT_IMPLEMENTED;
    475  }
    476 }
    477 
    478 }  // namespace mozilla