Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/editor/libeditor/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 532 kB image not shown  

Quelle  HTMLEditSubActionHandler.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=80: */
/* 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 "HTMLEditor.h"
#include "HTMLEditorInlines.h"
#include "HTMLEditorNestedClasses.h"

#include <utility>

#include "AutoClonedRangeArray.h"
#include "AutoSelectionRestorer.h"
#include "CSSEditUtils.h"
#include "EditAction.h"
#include "EditorDOMPoint.h"
#include "EditorLineBreak.h"
#include "EditorUtils.h"
#include "HTMLEditHelpers.h"
#include "HTMLEditUtils.h"
#include "PendingStyles.h"  // for SpecifiedStyle
#include "WhiteSpaceVisibilityKeeper.h"
#include "WSRunScanner.h"

#include "ErrorList.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/ContentIterator.h"
#include "mozilla/EditorForwards.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/InternalMutationEvent.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Maybe.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/PresShell.h"
#include "mozilla/TextComposition.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/AncestorIterator.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/HTMLBRElement.h"
#include "mozilla/dom/RangeBinding.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/StaticRange.h"
#include "nsAtom.h"
#include "nsCRT.h"
#include "nsCRTGlue.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsFrameSelection.h"
#include "nsGkAtoms.h"
#include "nsIContent.h"
#include "nsIFrame.h"
#include "nsINode.h"
#include "nsLiteralString.h"
#include "nsPrintfCString.h"
#include "nsRange.h"
#include "nsReadableUtils.h"
#include "nsString.h"
#include "nsStringFwd.h"
#include "nsStyledElement.h"
#include "nsTArray.h"
#include "nsTextNode.h"
#include "nsThreadUtils.h"

class nsISupports;

namespace mozilla {

using namespace dom;
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
using EmptyCheckOptions = HTMLEditUtils::EmptyCheckOptions;
using LeafNodeType = HTMLEditUtils::LeafNodeType;
using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
using WalkTextOption = HTMLEditUtils::WalkTextOption;
using WalkTreeDirection = HTMLEditUtils::WalkTreeDirection;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;

/********************************************************
 *  first some helpful functors we will use
 ********************************************************/


static bool IsPendingStyleCachePreservingSubAction(
    EditSubAction aEditSubAction) {
  switch (aEditSubAction) {
    case EditSubAction::eDeleteSelectedContent:
    case EditSubAction::eInsertLineBreak:
    case EditSubAction::eInsertParagraphSeparator:
    case EditSubAction::eCreateOrChangeList:
    case EditSubAction::eIndent:
    case EditSubAction::eOutdent:
    case EditSubAction::eSetOrClearAlignment:
    case EditSubAction::eCreateOrRemoveBlock:
    case EditSubAction::eFormatBlockForHTMLCommand:
    case EditSubAction::eMergeBlockContents:
    case EditSubAction::eRemoveList:
    case EditSubAction::eCreateOrChangeDefinitionListItem:
    case EditSubAction::eInsertElement:
    case EditSubAction::eInsertQuotation:
    case EditSubAction::eInsertQuotedText:
      return true;
    default:
      return false;
  }
}

template already_AddRefed<nsRange>
HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
    const EditorDOMRange& aRange);
template already_AddRefed<nsRange>
HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
    const EditorRawDOMRange& aRange);
template already_AddRefed<nsRange>
HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
    const EditorDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint);
template already_AddRefed<nsRange>
HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
    const EditorRawDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint);
template already_AddRefed<nsRange>
HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
    const EditorDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint);
template already_AddRefed<nsRange>
HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
    const EditorRawDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint);

nsresult HTMLEditor::InitEditorContentAndSelection() {
  MOZ_ASSERT(IsEditActionDataAvailable());

  // We should do nothing with the result of GetRoot() if only a part of the
  // document is editable.
  if (!EntireDocumentIsEditable()) {
    return NS_OK;
  }

  nsresult rv = MaybeCreatePaddingBRElementForEmptyEditor();
  if (NS_FAILED(rv)) {
    NS_WARNING(
        "HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() failed");
    return rv;
  }

  // If the selection hasn't been set up yet, set it up collapsed to the end of
  // our editable content.
  // XXX I think that this shouldn't do it in `HTMLEditor` because it maybe
  //     removed by the web app and if they call `Selection::AddRange()` without
  //     checking the range count, it may cause multiple selection ranges.
  if (!SelectionRef().RangeCount()) {
    nsresult rv = CollapseSelectionToEndOfLastLeafNodeOfDocument();
    if (NS_FAILED(rv)) {
      NS_WARNING(
          "HTMLEditor::CollapseSelectionToEndOfLastLeafNodeOfDocument() "
          "failed");
      return rv;
    }
  }

  if (IsPlaintextMailComposer()) {
    // XXX Should we do this in HTMLEditor?  It's odd to guarantee that last
    //     empty line is visible only when it's in the plain text mode.
    nsresult rv = EnsurePaddingBRElementInMultilineEditor();
    if (NS_FAILED(rv)) {
      NS_WARNING(
          "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed");
      return rv;
    }
  }

  Element* bodyOrDocumentElement = GetRoot();
  if (NS_WARN_IF(!bodyOrDocumentElement && !GetDocument())) {
    return NS_ERROR_FAILURE;
  }

  if (!bodyOrDocumentElement) {
    return NS_OK;
  }

  rv = InsertBRElementToEmptyListItemsAndTableCellsInRange(
      RawRangeBoundary(bodyOrDocumentElement, 0u),
      RawRangeBoundary(bodyOrDocumentElement,
                       bodyOrDocumentElement->GetChildCount()));
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    return NS_ERROR_EDITOR_DESTROYED;
  }
  NS_WARNING_ASSERTION(
      NS_SUCCEEDED(rv),
      "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange() "
      "failed, but ignored");
  return NS_OK;
}

void HTMLEditor::OnStartToHandleTopLevelEditSubAction(
    EditSubAction aTopLevelEditSubAction,
    nsIEditor::EDirection aDirectionOfTopLevelEditSubAction, ErrorResult& aRv) {
  MOZ_ASSERT(IsEditActionDataAvailable());
  MOZ_ASSERT(!aRv.Failed());

  EditorBase::OnStartToHandleTopLevelEditSubAction(
      aTopLevelEditSubAction, aDirectionOfTopLevelEditSubAction, aRv);

  MOZ_ASSERT(GetTopLevelEditSubAction() == aTopLevelEditSubAction);
  MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() ==
             aDirectionOfTopLevelEditSubAction);

  if (NS_WARN_IF(Destroyed())) {
    aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
    return;
  }

  if (!mInitSucceeded) {
    return;  // We should do nothing if we're being initialized.
  }

  NS_WARNING_ASSERTION(
      !aRv.Failed(),
      "EditorBase::OnStartToHandleTopLevelEditSubAction() failed");

  // Let's work with the latest layout information after (maybe) dispatching
  // `beforeinput` event.
  RefPtr<Document> document = GetDocument();
  if (NS_WARN_IF(!document)) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return;
  }
  document->FlushPendingNotifications(FlushType::Frames);
  if (NS_WARN_IF(Destroyed())) {
    aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
    return;
  }

  // Remember where our selection was before edit action took place:
  const auto atCompositionStart =
      GetFirstIMESelectionStartPoint<EditorRawDOMPoint>();
  if (atCompositionStart.IsSet()) {
    // If there is composition string, let's remember current composition
    // range.
    TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(
        atCompositionStart, GetLastIMESelectionEndPoint<EditorRawDOMPoint>());
  } else {
    // Get the selection location
    // XXX This may occur so that I think that we shouldn't throw exception
    //     in this case.
    if (NS_WARN_IF(!SelectionRef().RangeCount())) {
      aRv.Throw(NS_ERROR_UNEXPECTED);
      return;
    }
    if (const nsRange* range = SelectionRef().GetRangeAt(0)) {
      TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(*range);
    }
  }

  // Register with range updater to track this as we perturb the doc
  RangeUpdaterRef().RegisterRangeItem(
      *TopLevelEditSubActionDataRef().mSelectedRange);

  // Remember current inline styles for deletion and normal insertion ops
  const bool cacheInlineStyles = [&]() {
    switch (aTopLevelEditSubAction) {
      case EditSubAction::eInsertText:
      case EditSubAction::eInsertTextComingFromIME:
      case EditSubAction::eDeleteSelectedContent:
        return true;
      default:
        return IsPendingStyleCachePreservingSubAction(aTopLevelEditSubAction);
    }
  }();
  if (cacheInlineStyles) {
    const RefPtr<Element> editingHost =
        ComputeEditingHost(LimitInBodyElement::No);
    if (NS_WARN_IF(!editingHost)) {
      aRv.Throw(NS_ERROR_FAILURE);
      return;
    }

    nsIContent* const startContainer =
        HTMLEditUtils::GetContentToPreserveInlineStyles(
            TopLevelEditSubActionDataRef()
                .mSelectedRange->StartPoint<EditorRawDOMPoint>(),
            *editingHost);
    if (NS_WARN_IF(!startContainer)) {
      aRv.Throw(NS_ERROR_FAILURE);
      return;
    }
    if (const RefPtr<Element> startContainerElement =
            startContainer->GetAsElementOrParentElement()) {
      nsresult rv = CacheInlineStyles(*startContainerElement);
      if (NS_FAILED(rv)) {
        NS_WARNING("HTMLEditor::CacheInlineStyles() failed");
        aRv.Throw(rv);
        return;
      }
    }
  }

  // Stabilize the document against contenteditable count changes
  if (document->GetEditingState() == Document::EditingState::eContentEditable) {
    document->ChangeContentEditableCount(nullptr, +1);
    TopLevelEditSubActionDataRef().mRestoreContentEditableCount = true;
  }

  // Check that selection is in subtree defined by body node
  nsresult rv = EnsureSelectionInBodyOrDocumentElement();
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
    return;
  }
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
                       "failed, but ignored");
}

nsresult HTMLEditor::OnEndHandlingTopLevelEditSubAction() {
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());

  nsresult rv;
  while (true) {
    if (NS_WARN_IF(Destroyed())) {
      rv = NS_ERROR_EDITOR_DESTROYED;
      break;
    }

    if (!mInitSucceeded) {
      rv = NS_OK;  // We should do nothing if we're being initialized.
      break;
    }

    // Do all the tricky stuff
    rv = OnEndHandlingTopLevelEditSubActionInternal();
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rv),
        "HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() failied");
    // Perhaps, we need to do the following jobs even if the editor has been
    // destroyed since they adjust some states of HTML document but don't
    // modify the DOM tree nor Selection.

    // Free up selectionState range item
    if (TopLevelEditSubActionDataRef().mSelectedRange) {
      RangeUpdaterRef().DropRangeItem(
          *TopLevelEditSubActionDataRef().mSelectedRange);
    }

    // Reset the contenteditable count to its previous value
    if (TopLevelEditSubActionDataRef().mRestoreContentEditableCount) {
      Document* document = GetDocument();
      if (NS_WARN_IF(!document)) {
        rv = NS_ERROR_FAILURE;
        break;
      }
      if (document->GetEditingState() ==
          Document::EditingState::eContentEditable) {
        document->ChangeContentEditableCount(nullptr, -1);
      }
    }
    break;
  }
  DebugOnly<nsresult> rvIgnored =
      EditorBase::OnEndHandlingTopLevelEditSubAction();
  NS_WARNING_ASSERTION(
      NS_FAILED(rv) || NS_SUCCEEDED(rvIgnored),
      "EditorBase::OnEndHandlingTopLevelEditSubAction() failed, but ignored");
  MOZ_ASSERT(!GetTopLevelEditSubAction());
  MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() == eNone);
  return rv;
}

nsresult HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() {
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());

  // If we just maintained the DOM tree for consistent behavior even after
  // web apps modified the DOM, we should not touch the DOM in this
  // post-processor.
  if (GetTopLevelEditSubAction() ==
      EditSubAction::eMaintainWhiteSpaceVisibility) {
    return NS_OK;
  }

  nsresult rv = EnsureSelectionInBodyOrDocumentElement();
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    return NS_ERROR_EDITOR_DESTROYED;
  }
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
                       "failed, but ignored");

  switch (GetTopLevelEditSubAction()) {
    case EditSubAction::eReplaceHeadWithHTMLSource:
    case EditSubAction::eCreatePaddingBRElementForEmptyEditor:
      return NS_OK;
    default:
      break;
  }

  if (TopLevelEditSubActionDataRef().mChangedRange->IsPositioned() &&
      GetTopLevelEditSubAction() != EditSubAction::eUndo &&
      GetTopLevelEditSubAction() != EditSubAction::eRedo) {
    // don't let any txns in here move the selection around behind our back.
    // Note that this won't prevent explicit selection setting from working.
    AutoTransactionsConserveSelection dontChangeMySelection(*this);

    {
      EditorDOMRange changedRange(
          *TopLevelEditSubActionDataRef().mChangedRange);
      if (changedRange.IsPositioned() &&
          changedRange.EnsureNotInNativeAnonymousSubtree()) {
        bool isBlockLevelSubAction = false;
        switch (GetTopLevelEditSubAction()) {
          case EditSubAction::eInsertText:
          case EditSubAction::eInsertTextComingFromIME:
          case EditSubAction::eInsertLineBreak:
          case EditSubAction::eInsertParagraphSeparator:
          case EditSubAction::eDeleteText: {
            // XXX We should investigate whether this is really needed because
            //     it seems that the following code does not handle the
            //     white-spaces.
            RefPtr<nsRange> extendedChangedRange =
                CreateRangeIncludingAdjuscentWhiteSpaces(changedRange);
            if (extendedChangedRange) {
              MOZ_ASSERT(extendedChangedRange->IsPositioned());
              // Use extended range temporarily.
              TopLevelEditSubActionDataRef().mChangedRange =
                  std::move(extendedChangedRange);
            }
            break;
          }
          case EditSubAction::eCreateOrChangeList:
          case EditSubAction::eCreateOrChangeDefinitionListItem:
          case EditSubAction::eRemoveList:
          case EditSubAction::eFormatBlockForHTMLCommand:
          case EditSubAction::eCreateOrRemoveBlock:
          case EditSubAction::eIndent:
          case EditSubAction::eOutdent:
          case EditSubAction::eSetOrClearAlignment:
          case EditSubAction::eSetPositionToAbsolute:
          case EditSubAction::eSetPositionToStatic:
          case EditSubAction::eDecreaseZIndex:
          case EditSubAction::eIncreaseZIndex:
            isBlockLevelSubAction = true;
            [[fallthrough]];
          default: {
            Element* editingHost = ComputeEditingHost();
            if (MOZ_UNLIKELY(!editingHost)) {
              break;
            }
            RefPtr<nsRange> extendedChangedRange = AutoClonedRangeArray::
                CreateRangeWrappingStartAndEndLinesContainingBoundaries(
                    changedRange, GetTopLevelEditSubAction(),
                    isBlockLevelSubAction
                        ? BlockInlineCheck::UseHTMLDefaultStyle
                        : BlockInlineCheck::UseComputedDisplayOutsideStyle,
                    *editingHost);
            if (!extendedChangedRange) {
              break;
            }
            MOZ_ASSERT(extendedChangedRange->IsPositioned());
            // Use extended range temporarily.
            TopLevelEditSubActionDataRef().mChangedRange =
                std::move(extendedChangedRange);
            break;
          }
        }
      }
    }

    // if we did a ranged deletion or handling backspace key, make sure we have
    // a place to put caret.
    // Note we only want to do this if the overall operation was deletion,
    // not if deletion was done along the way for
    // EditSubAction::eInsertHTMLSource, EditSubAction::eInsertText, etc.
    // That's why this is here rather than DeleteSelectionAsSubAction().
    // However, we shouldn't insert <br> elements if we've already removed
    // empty block parents because users may want to disappear the line by
    // the deletion.
    // XXX We should make HandleDeleteSelection() store expected container
    //     for handling this here since we cannot trust current selection is
    //     collapsed at deleted point.
    if (GetTopLevelEditSubAction() == EditSubAction::eDeleteSelectedContent &&
        TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange &&
        !TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks) {
      const auto newCaretPosition =
          GetFirstSelectionStartPoint<EditorDOMPoint>();
      if (!newCaretPosition.IsSet()) {
        NS_WARNING("There was no selection range");
        return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
      }
      Result<CreateLineBreakResult, nsresult>
          insertPaddingBRElementResultOrError =
              InsertPaddingBRElementToMakeEmptyLineVisibleIfNeeded(
                  newCaretPosition);
      if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) {
        NS_WARNING(
            "HTMLEditor::"
            "InsertPaddingBRElementToMakeEmptyLineVisibleIfNeeded() failed");
        return insertPaddingBRElementResultOrError.unwrapErr();
      }
      nsresult rv =
          insertPaddingBRElementResultOrError.unwrap().SuggestCaretPointTo(
              *this, {SuggestCaret::OnlyIfHasSuggestion});
      if (NS_FAILED(rv)) {
        NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
        return rv;
      }
      NS_WARNING_ASSERTION(
          rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
          "CaretPoint::SuggestCaretPointTo() failed, but ignored");
    }

    // add in any needed <br>s, and remove any unneeded ones.
    nsresult rv = InsertBRElementToEmptyListItemsAndTableCellsInRange(
        TopLevelEditSubActionDataRef().mChangedRange->StartRef().AsRaw(),
        TopLevelEditSubActionDataRef().mChangedRange->EndRef().AsRaw());
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rv),
        "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange()"
        " failed, but ignored");

    // merge any adjacent text nodes
    switch (GetTopLevelEditSubAction()) {
      case EditSubAction::eInsertText:
      case EditSubAction::eInsertTextComingFromIME:
        break;
      default: {
        nsresult rv = CollapseAdjacentTextNodes(
            MOZ_KnownLive(*TopLevelEditSubActionDataRef().mChangedRange));
        if (NS_WARN_IF(Destroyed())) {
          return NS_ERROR_EDITOR_DESTROYED;
        }
        if (NS_FAILED(rv)) {
          NS_WARNING("HTMLEditor::CollapseAdjacentTextNodes() failed");
          return rv;
        }
        break;
      }
    }

    // Clean up any empty nodes in the changed range unless they are inserted
    // intentionally.
    if (TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements) {
      nsresult rv = RemoveEmptyNodesIn(
          EditorDOMRange(*TopLevelEditSubActionDataRef().mChangedRange));
      if (NS_FAILED(rv)) {
        NS_WARNING("HTMLEditor::RemoveEmptyNodesIn() failed");
        return rv;
      }
    }

    // attempt to transform any unneeded nbsp's into spaces after doing various
    // operations
    switch (GetTopLevelEditSubAction()) {
      case EditSubAction::eDeleteSelectedContent:
        if (TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces) {
          break;
        }
        [[fallthrough]];
      case EditSubAction::eInsertText:
      case EditSubAction::eInsertTextComingFromIME:
      case EditSubAction::eInsertLineBreak:
      case EditSubAction::eInsertParagraphSeparator:
      case EditSubAction::ePasteHTMLContent:
      case EditSubAction::eInsertHTMLSource: {
        // Due to the replacement of white-spaces in
        // WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(),
        // selection ranges may be changed since DOM ranges track the DOM
        // mutation by themselves.  However, we want to keep selection as-is.
        // Therefore, we should restore `Selection` after replacing
        // white-spaces.
        AutoSelectionRestorer restoreSelection(this);
        // TODO: Temporarily, WhiteSpaceVisibilityKeeper replaces ASCII
        //       white-spaces with NPSPs and then, we'll replace them with ASCII
        //       white-spaces here.  We should avoid this overwriting things as
        //       far as possible because replacing characters in text nodes
        //       causes running mutation event listeners which are really
        //       expensive.
        // Adjust end of composition string if there is composition string.
        auto pointToAdjust = GetLastIMESelectionEndPoint<EditorDOMPoint>();
        if (!pointToAdjust.IsInContentNode()) {
          // Otherwise, adjust current selection start point.
          pointToAdjust = GetFirstSelectionStartPoint<EditorDOMPoint>();
          if (NS_WARN_IF(!pointToAdjust.IsInContentNode())) {
            return NS_ERROR_FAILURE;
          }
        }
        const RefPtr<Element> editingHost =
            ComputeEditingHost(LimitInBodyElement::No);
        if (MOZ_UNLIKELY(!editingHost)) {
          break;
        }
        if (EditorUtils::IsEditableContent(
                *pointToAdjust.ContainerAs<nsIContent>(), EditorType::HTML)) {
          AutoTrackDOMPoint trackPointToAdjust(RangeUpdaterRef(),
                                               &pointToAdjust);
          nsresult rv =
              WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
                  *this, pointToAdjust, *editingHost);
          if (NS_FAILED(rv)) {
            NS_WARNING(
                "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
                "failed");
            return rv;
          }
        }

        // also do this for original selection endpoints.
        // XXX Hmm, if `NormalizeVisibleWhiteSpacesAt()` runs mutation event
        //     listener and that causes changing `mSelectedRange`, what we
        //     should do?
        if (NS_WARN_IF(!TopLevelEditSubActionDataRef()
                            .mSelectedRange->IsPositioned())) {
          return NS_ERROR_FAILURE;
        }

        EditorDOMPoint atStart =
            TopLevelEditSubActionDataRef().mSelectedRange->StartPoint();
        if (atStart != pointToAdjust && atStart.IsInContentNode() &&
            EditorUtils::IsEditableContent(*atStart.ContainerAs<nsIContent>(),
                                           EditorType::HTML)) {
          AutoTrackDOMPoint trackPointToAdjust(RangeUpdaterRef(),
                                               &pointToAdjust);
          AutoTrackDOMPoint trackStartPoint(RangeUpdaterRef(), &atStart);
          nsresult rv =
              WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
                  *this, atStart, *editingHost);
          if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          NS_WARNING_ASSERTION(
              NS_SUCCEEDED(rv),
              "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
              "failed, but ignored");
        }
        // we only need to handle old selection endpoint if it was different
        // from start
        EditorDOMPoint atEnd =
            TopLevelEditSubActionDataRef().mSelectedRange->EndPoint();
        if (!TopLevelEditSubActionDataRef().mSelectedRange->Collapsed() &&
            atEnd != pointToAdjust && atEnd != atStart &&
            atEnd.IsInContentNode() &&
            EditorUtils::IsEditableContent(*atEnd.ContainerAs<nsIContent>(),
                                           EditorType::HTML)) {
          nsresult rv =
              WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
                  *this, atEnd, *editingHost);
          if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
            return NS_ERROR_EDITOR_DESTROYED;
          }
          NS_WARNING_ASSERTION(
              NS_SUCCEEDED(rv),
              "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
              "failed, but ignored");
        }
        break;
      }
      default:
        break;
    }

    // Adjust selection for insert text, html paste, and delete actions if
    // we haven't removed new empty blocks.  Note that if empty block parents
    // are removed, Selection should've been adjusted by the method which
    // did it.
    if (!TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks &&
        SelectionRef().IsCollapsed()) {
      switch (GetTopLevelEditSubAction()) {
        case EditSubAction::eInsertText:
        case EditSubAction::eInsertTextComingFromIME:
        case EditSubAction::eInsertLineBreak:
        case EditSubAction::eInsertParagraphSeparator:
        case EditSubAction::ePasteHTMLContent:
        case EditSubAction::eInsertHTMLSource:
          // XXX AdjustCaretPositionAndEnsurePaddingBRElement() intentionally
          //     does not create padding `<br>` element for empty editor.
          //     Investigate which is better that whether this should does it
          //     or wait MaybeCreatePaddingBRElementForEmptyEditor().
          rv = AdjustCaretPositionAndEnsurePaddingBRElement(
              GetDirectionOfTopLevelEditSubAction());
          if (NS_FAILED(rv)) {
            NS_WARNING(
                "HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement() "
                "failed");
            return rv;
          }
          break;
        default:
          break;
      }
    }

    // check for any styles which were removed inappropriately
    bool reapplyCachedStyle;
    switch (GetTopLevelEditSubAction()) {
      case EditSubAction::eInsertText:
      case EditSubAction::eInsertTextComingFromIME:
      case EditSubAction::eDeleteSelectedContent:
        reapplyCachedStyle = true;
        break;
      default:
        reapplyCachedStyle =
            IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction());
        break;
    }

    // If the selection is in empty inline HTML elements, we should delete
    // them unless it's inserted intentionally.
    if (mPlaceholderBatch &&
        TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements &&
        SelectionRef().IsCollapsed() && SelectionRef().GetFocusNode()) {
      RefPtr<Element> mostDistantEmptyInlineAncestor = nullptr;
      for (Element* ancestor :
           SelectionRef().GetFocusNode()->InclusiveAncestorsOfType<Element>()) {
        if (!ancestor->IsHTMLElement() ||
            !HTMLEditUtils::IsRemovableFromParentNode(*ancestor) ||
            !HTMLEditUtils::IsEmptyInlineContainer(
                *ancestor, {EmptyCheckOption::TreatSingleBRElementAsVisible},
                BlockInlineCheck::UseComputedDisplayStyle)) {
          break;
        }
        mostDistantEmptyInlineAncestor = ancestor;
      }
      if (mostDistantEmptyInlineAncestor) {
        nsresult rv =
            DeleteNodeWithTransaction(*mostDistantEmptyInlineAncestor);
        if (NS_FAILED(rv)) {
          NS_WARNING(
              "EditorBase::DeleteNodeWithTransaction() failed at deleting "
              "empty inline ancestors");
          return rv;
        }
      }
    }

    // But the cached inline styles should be restored from type-in-state later.
    if (reapplyCachedStyle) {
      DebugOnly<nsresult> rvIgnored =
          mPendingStylesToApplyToNewContent->UpdateSelState(*this);
      NS_WARNING_ASSERTION(
          NS_SUCCEEDED(rvIgnored),
          "PendingStyles::UpdateSelState() failed, but ignored");
      rvIgnored = ReapplyCachedStyles();
      NS_WARNING_ASSERTION(
          NS_SUCCEEDED(rvIgnored),
          "HTMLEditor::ReapplyCachedStyles() failed, but ignored");
      TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
    }
  }

  rv = HandleInlineSpellCheck(
      TopLevelEditSubActionDataRef().mSelectedRange->StartPoint(),
      TopLevelEditSubActionDataRef().mChangedRange);
  if (NS_FAILED(rv)) {
    NS_WARNING("EditorBase::HandleInlineSpellCheck() failed");
    return rv;
  }

  // detect empty doc
  // XXX Need to investigate when the padding <br> element is removed because
  //     I don't see the <br> element with testing manually.  If it won't be
  //     used, we can get rid of this cost.
  rv = MaybeCreatePaddingBRElementForEmptyEditor();
  if (NS_FAILED(rv)) {
    NS_WARNING(
        "EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() failed");
    return rv;
  }

  // adjust selection HINT if needed
  if (!TopLevelEditSubActionDataRef().mDidExplicitlySetInterLine &&
      SelectionRef().IsCollapsed()) {
    SetSelectionInterlinePosition();
  }

  return NS_OK;
}

Result<EditActionResult, nsresult> HTMLEditor::CanHandleHTMLEditSubAction(
    CheckSelectionInReplacedElement aCheckSelectionInReplacedElement
    /* = CheckSelectionInReplacedElement::Yes */) const {
  MOZ_ASSERT(IsEditActionDataAvailable());

  if (NS_WARN_IF(Destroyed())) {
    return Err(NS_ERROR_EDITOR_DESTROYED);
  }

  // If there is not selection ranges, we should ignore the result.
  if (!SelectionRef().RangeCount()) {
    return EditActionResult::CanceledResult();
  }

  const nsRange* range = SelectionRef().GetRangeAt(0);
  nsINode* selStartNode = range->GetStartContainer();
  if (NS_WARN_IF(!selStartNode) || NS_WARN_IF(!selStartNode->IsContent())) {
    return Err(NS_ERROR_FAILURE);
  }

  if (!HTMLEditUtils::IsSimplyEditableNode(*selStartNode)) {
    return EditActionResult::CanceledResult();
  }

  nsINode* selEndNode = range->GetEndContainer();
  if (NS_WARN_IF(!selEndNode) || NS_WARN_IF(!selEndNode->IsContent())) {
    return Err(NS_ERROR_FAILURE);
  }

  if (selStartNode == selEndNode) {
    if (aCheckSelectionInReplacedElement ==
            CheckSelectionInReplacedElement::Yes &&
        HTMLEditUtils::IsNonEditableReplacedContent(
            *selStartNode->AsContent())) {
      return EditActionResult::CanceledResult();
    }
    return EditActionResult::IgnoredResult();
  }

  if (HTMLEditUtils::IsNonEditableReplacedContent(*selStartNode->AsContent()) ||
      HTMLEditUtils::IsNonEditableReplacedContent(*selEndNode->AsContent())) {
    return EditActionResult::CanceledResult();
  }

  if (!HTMLEditUtils::IsSimplyEditableNode(*selEndNode)) {
    return EditActionResult::CanceledResult();
  }

  // If anchor node is in an HTML element which has inert attribute, we should
  // do nothing.
  // XXX HTMLEditor typically uses first range instead of anchor/focus range.
  //     Therefore, referring first range here is more reasonable than
  //     anchor/focus range of Selection.
  nsIContent* const selAnchorContent = SelectionRef().GetDirection() == eDirNext
                                           ? nsIContent::FromNode(selStartNode)
                                           : nsIContent::FromNode(selEndNode);
  if (selAnchorContent &&
      HTMLEditUtils::ContentIsInert(*selAnchorContent->AsContent())) {
    return EditActionResult::CanceledResult();
  }

  // XXX What does it mean the common ancestor is editable?  I have no idea.
  //     It should be in same (active) editing host, and even if it's editable,
  //     there may be non-editable contents in the range.
  nsINode* commonAncestor = range->GetClosestCommonInclusiveAncestor();
  if (MOZ_UNLIKELY(!commonAncestor)) {
    NS_WARNING(
        "AbstractRange::GetClosestCommonInclusiveAncestor() returned nullptr");
    return Err(NS_ERROR_FAILURE);
  }
  return HTMLEditUtils::IsSimplyEditableNode(*commonAncestor)
             ? EditActionResult::IgnoredResult()
             : EditActionResult::CanceledResult();
}

MOZ_CAN_RUN_SCRIPT static nsStaticAtom& MarginPropertyAtomForIndent(
    nsIContent& aContent) {
  nsAutoString direction;
  DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedProperty(
      aContent, *nsGkAtoms::direction, direction);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                       "CSSEditUtils::GetComputedProperty(nsGkAtoms::direction)"
                       " failed, but ignored");
  return direction.EqualsLiteral("rtl") ? *nsGkAtoms::marginRight
                                        : *nsGkAtoms::marginLeft;
}

nsresult HTMLEditor::EnsureCaretNotAfterInvisibleBRElement(
    const Element& aEditingHost) {
  MOZ_ASSERT(IsEditActionDataAvailable());
  MOZ_ASSERT(SelectionRef().IsCollapsed());

  // If we are after a padding `<br>` element for empty last line in the same
  // block, then move selection to be before it
  const nsRange* firstRange = SelectionRef().GetRangeAt(0);
  if (NS_WARN_IF(!firstRange)) {
    return NS_ERROR_FAILURE;
  }

  EditorRawDOMPoint atSelectionStart(firstRange->StartRef());
  if (NS_WARN_IF(!atSelectionStart.IsSet())) {
    return NS_ERROR_FAILURE;
  }
  MOZ_ASSERT(atSelectionStart.IsSetAndValid());

  if (!atSelectionStart.IsInContentNode()) {
    return NS_OK;
  }

  nsIContent* previousBRElement = HTMLEditUtils::GetPreviousContent(
      atSelectionStart, {}, BlockInlineCheck::UseComputedDisplayStyle,
      &aEditingHost);
  if (!previousBRElement || !previousBRElement->IsHTMLElement(nsGkAtoms::br) ||
      !previousBRElement->GetParent() ||
      !EditorUtils::IsEditableContent(*previousBRElement->GetParent(),
                                      EditorType::HTML) ||
      !HTMLEditUtils::IsInvisibleBRElement(*previousBRElement)) {
    return NS_OK;
  }

  const RefPtr<const Element> blockElementAtSelectionStart =
      HTMLEditUtils::GetInclusiveAncestorElement(
          *atSelectionStart.ContainerAs<nsIContent>(),
          HTMLEditUtils::ClosestBlockElement,
          BlockInlineCheck::UseComputedDisplayStyle);
  const RefPtr<const Element> parentBlockElementOfBRElement =
      HTMLEditUtils::GetAncestorElement(
          *previousBRElement, HTMLEditUtils::ClosestBlockElement,
          BlockInlineCheck::UseComputedDisplayStyle);

  if (!blockElementAtSelectionStart ||
      blockElementAtSelectionStart != parentBlockElementOfBRElement) {
    return NS_OK;
  }

  // If we are here then the selection is right after a padding <br>
  // element for empty last line that is in the same block as the
  // selection.  We need to move the selection start to be before the
  // padding <br> element.
  EditorRawDOMPoint atInvisibleBRElement(previousBRElement);
  nsresult rv = CollapseSelectionTo(atInvisibleBRElement);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "EditorBase::CollapseSelectionTo() failed");
  return rv;
}

nsresult HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() {
  MOZ_ASSERT(IsEditActionDataAvailable());

  if (mPaddingBRElementForEmptyEditor) {
    return NS_OK;
  }

  // XXX I think that we should not insert a <br> element if we're for a web
  // content.  Probably, this is required only by chrome editors such as
  // the mail composer of Thunderbird and the composer of SeaMonkey.

  const RefPtr<Element> bodyOrDocumentElement = GetRoot();
  if (!bodyOrDocumentElement) {
    return NS_OK;
  }

  // Skip adding the padding <br> element for empty editor if body
  // is read-only.
  if (!HTMLEditUtils::IsSimplyEditableNode(*bodyOrDocumentElement)) {
    return NS_OK;
  }

  // Now we've got the body element. Iterate over the body element's children,
  // looking for editable content. If no editable content is found, insert the
  // padding <br> element.
  EditorType editorType = GetEditorType();
  bool isRootEditable =
      EditorUtils::IsEditableContent(*bodyOrDocumentElement, editorType);
  for (nsIContent* child = bodyOrDocumentElement->GetFirstChild(); child;
       child = child->GetNextSibling()) {
    if (EditorUtils::IsPaddingBRElementForEmptyEditor(*child) ||
        !isRootEditable || EditorUtils::IsEditableContent(*child, editorType) ||
        HTMLEditUtils::IsBlockElement(
            *child, BlockInlineCheck::UseComputedDisplayStyle)) {
      return NS_OK;
    }
  }

  IgnoredErrorResult ignoredError;
  AutoEditSubActionNotifier startToHandleEditSubAction(
      *this, EditSubAction::eCreatePaddingBRElementForEmptyEditor,
      nsIEditor::eNone, ignoredError);
  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
    return ignoredError.StealNSResult();
  }
  NS_WARNING_ASSERTION(
      !ignoredError.Failed(),
      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");

  Result<CreateElementResult, nsresult> insertPaddingBRElementResultOrError =
      InsertBRElement(WithTransaction::Yes,
                      BRElementType::PaddingForEmptyEditor,
                      EditorDOMPoint(bodyOrDocumentElement, 0u));
  if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) {
    NS_WARNING(
        "EditorBase::InsertBRElement(WithTransaction::Yes, "
        "BRElementType::PaddingForEmptyEditor) failed");
    return insertPaddingBRElementResultOrError.propagateErr();
  }
  CreateElementResult insertPaddingBRElementResult =
      insertPaddingBRElementResultOrError.unwrap();
  mPaddingBRElementForEmptyEditor =
      HTMLBRElement::FromNode(insertPaddingBRElementResult.GetNewNode());
  nsresult rv = insertPaddingBRElementResult.SuggestCaretPointTo(
      *this, {SuggestCaret::AndIgnoreTrivialError});
  if (NS_FAILED(rv)) {
    NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
    return rv;
  }
  NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
                       "CaretPoint::SuggestCaretPointTo() failed, but ignored");
  return NS_OK;
}

nsresult HTMLEditor::EnsureNoPaddingBRElementForEmptyEditor() {
  MOZ_ASSERT(IsEditActionDataAvailable());

  if (!mPaddingBRElementForEmptyEditor) {
    return NS_OK;
  }

  // If we're an HTML editor, a mutation event listener may recreate padding
  // <br> element for empty editor again during the call of
  // DeleteNodeWithTransaction().  So, move it first.
  RefPtr<HTMLBRElement> paddingBRElement(
      std::move(mPaddingBRElementForEmptyEditor));
  nsresult rv = DeleteNodeWithTransaction(*paddingBRElement);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "EditorBase::DeleteNodeWithTransaction() failed");
  return rv;
}

nsresult HTMLEditor::ReflectPaddingBRElementForEmptyEditor() {
  if (NS_WARN_IF(!mRootElement)) {
    NS_WARNING("Failed to handle padding BR element due to no root element");
    return NS_ERROR_FAILURE;
  }
  // The idea here is to see if the magic empty node has suddenly reappeared. If
  // it has, set our state so we remember it. There is a tradeoff between doing
  // here and at redo, or doing it everywhere else that might care.  Since undo
  // and redo are relatively rare, it makes sense to take the (small)
  // performance hit here.
  nsIContent* firstLeafChild = HTMLEditUtils::GetFirstLeafContent(
      *mRootElement, {LeafNodeType::OnlyLeafNode});
  if (firstLeafChild &&
      EditorUtils::IsPaddingBRElementForEmptyEditor(*firstLeafChild)) {
    mPaddingBRElementForEmptyEditor =
        static_cast<HTMLBRElement*>(firstLeafChild);
  } else {
    mPaddingBRElementForEmptyEditor = nullptr;
  }
  return NS_OK;
}

nsresult HTMLEditor::PrepareInlineStylesForCaret() {
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
  MOZ_ASSERT(SelectionRef().IsCollapsed());

  // XXX This method works with the top level edit sub-action, but this
  //     must be wrong if we are handling nested edit action.

  if (TopLevelEditSubActionDataRef().mDidDeleteSelection) {
    switch (GetTopLevelEditSubAction()) {
      case EditSubAction::eInsertText:
      case EditSubAction::eInsertTextComingFromIME:
      case EditSubAction::eDeleteSelectedContent: {
        nsresult rv = ReapplyCachedStyles();
        if (NS_FAILED(rv)) {
          NS_WARNING("HTMLEditor::ReapplyCachedStyles() failed");
          return rv;
        }
        break;
      }
      default:
        break;
    }
  }
  // For most actions we want to clear the cached styles, but there are
  // exceptions
  if (!IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
    TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
  }
  return NS_OK;
}

Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
    EditSubAction aEditSubAction, const nsAString& aInsertionString,
    SelectionHandling aSelectionHandling) {
  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
  MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText ||
             aEditSubAction == EditSubAction::eInsertTextComingFromIME);
  MOZ_ASSERT_IF(aSelectionHandling == SelectionHandling::Ignore,
                aEditSubAction == EditSubAction::eInsertTextComingFromIME);

  {
    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
    if (MOZ_UNLIKELY(result.isErr())) {
      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
      return result;
    }
    if (result.inspect().Canceled()) {
      return result;
    }
  }

  UndefineCaretBidiLevel();

  // If the selection isn't collapsed, delete it.  Don't delete existing inline
  // tags, because we're hopefully going to insert text (bug 787432).
  if (!SelectionRef().IsCollapsed() &&
      aSelectionHandling == SelectionHandling::Delete) {
    nsresult rv =
        DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
    if (NS_FAILED(rv)) {
      NS_WARNING(
          "EditorBase::DeleteSelectionAsSubAction(nsIEditor::eNone, "
          "nsIEditor::eNoStrip) failed");
      return Err(rv);
    }
  }

  const RefPtr<Element> editingHost =
      ComputeEditingHost(LimitInBodyElement::No);
  if (NS_WARN_IF(!editingHost)) {
    return Err(NS_ERROR_FAILURE);
  }

  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    return Err(NS_ERROR_EDITOR_DESTROYED);
  }
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
                       "failed, but ignored");

  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
    nsresult rv = EnsureCaretNotAfterInvisibleBRElement(*editingHost);
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
      return Err(NS_ERROR_EDITOR_DESTROYED);
    }
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
                         "failed, but ignored");
    if (NS_SUCCEEDED(rv)) {
      nsresult rv = PrepareInlineStylesForCaret();
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
        return Err(NS_ERROR_EDITOR_DESTROYED);
      }
      NS_WARNING_ASSERTION(
          NS_SUCCEEDED(rv),
          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
    }
  }

  RefPtr<Document> document = GetDocument();
  if (NS_WARN_IF(!document)) {
    return Err(NS_ERROR_FAILURE);
  }

  const bool isHandlingComposition =
      aEditSubAction == EditSubAction::eInsertTextComingFromIME;
  auto pointToInsert = isHandlingComposition
                           ? GetFirstIMESelectionStartPoint<EditorDOMPoint>()
                           : GetFirstSelectionStartPoint<EditorDOMPoint>();
  if (!pointToInsert.IsSet()) {
    if (MOZ_LIKELY(isHandlingComposition)) {
      pointToInsert = GetFirstSelectionStartPoint<EditorDOMPoint>();
    }
    if (NS_WARN_IF(!pointToInsert.IsSet())) {
      return Err(NS_ERROR_FAILURE);
    }
  }

  // for every property that is set, insert a new inline style node
  // XXX I think that if this is second or later composition update, we should
  // not change the style because we won't update composition with keeping
  // inline elements in composing range.
  Result<EditorDOMPoint, nsresult> setStyleResult =
      CreateStyleForInsertText(pointToInsert, *editingHost);
  if (MOZ_UNLIKELY(setStyleResult.isErr())) {
    NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed");
    return setStyleResult.propagateErr();
  }
  if (setStyleResult.inspect().IsSet()) {
    pointToInsert = setStyleResult.unwrap();
  }

  if (NS_WARN_IF(!pointToInsert.IsSetAndValid()) ||
      NS_WARN_IF(!pointToInsert.IsInContentNode())) {
    return Err(NS_ERROR_FAILURE);
  }
  MOZ_ASSERT(pointToInsert.IsSetAndValid());

  // If the point is not in an element which can contain text nodes, climb up
  // the DOM tree.
  if (!pointToInsert.IsInTextNode()) {
    while (!HTMLEditUtils::CanNodeContain(*pointToInsert.GetContainer(),
                                          *nsGkAtoms::textTagName)) {
      if (NS_WARN_IF(pointToInsert.GetContainer() == editingHost) ||
          NS_WARN_IF(!pointToInsert.GetContainerParentAs<nsIContent>())) {
        NS_WARNING("Selection start point couldn't have text nodes");
        return Err(NS_ERROR_FAILURE);
      }
      pointToInsert.Set(pointToInsert.ContainerAs<nsIContent>());
    }
  }

  if (isHandlingComposition) {
    if (aInsertionString.IsEmpty()) {
      // Right now the WhiteSpaceVisibilityKeeper code bails on empty strings,
      // but IME needs the InsertTextWithTransaction() call to still happen
      // since empty strings are meaningful there.
      Result<InsertTextResult, nsresult> insertEmptyTextResultOrError =
          InsertTextWithTransaction(*document, aInsertionString, pointToInsert,
                                    InsertTextTo::ExistingTextNodeIfAvailable);
      if (MOZ_UNLIKELY(insertEmptyTextResultOrError.isErr())) {
        NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
        return insertEmptyTextResultOrError.propagateErr();
      }
      const InsertTextResult insertEmptyTextResult =
          insertEmptyTextResultOrError.unwrap();
      nsresult rv = EnsureNoFollowingUnnecessaryLineBreak(
          insertEmptyTextResult.EndOfInsertedTextRef());
      if (NS_FAILED(rv)) {
        NS_WARNING(
            "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
        return Err(rv);
      }
      // If we replaced non-empty composition string with an empty string,
      // its preceding character may be a collapsible ASCII white-space.
      // Therefore, we may need to insert a padding <br> after the white-space.
      Result<CreateLineBreakResult, nsresult>
          insertPaddingBRElementResultOrError = InsertPaddingBRElementIfNeeded(
              insertEmptyTextResult.EndOfInsertedTextRef(), nsIEditor::eNoStrip,
              *editingHost);
      if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) {
        NS_WARNING(
            "HTMLEditor::InsertPaddingBRElementIfNeeded(eNoStrip) failed");
        insertEmptyTextResult.IgnoreCaretPointSuggestion();
        return insertPaddingBRElementResultOrError.propagateErr();
      }
      insertPaddingBRElementResultOrError.unwrap().IgnoreCaretPointSuggestion();
      rv = insertEmptyTextResult.SuggestCaretPointTo(
          *this, {SuggestCaret::OnlyIfHasSuggestion,
                  SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
                  SuggestCaret::AndIgnoreTrivialError});
      if (NS_FAILED(rv)) {
        NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
        return Err(rv);
      }
      NS_WARNING_ASSERTION(
          rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
          "CaretPoint::SuggestCaretPointTo() failed, but ignored");
      return EditActionResult::HandledResult();
    }

    auto compositionEndPoint = GetLastIMESelectionEndPoint<EditorDOMPoint>();
    if (!compositionEndPoint.IsSet()) {
      compositionEndPoint = pointToInsert;
    }
    Result<InsertTextResult, nsresult> replaceTextResult =
        WhiteSpaceVisibilityKeeper::ReplaceText(
            *this, aInsertionString,
            EditorDOMRange(pointToInsert, compositionEndPoint),
            InsertTextTo::ExistingTextNodeIfAvailable);
    if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
      NS_WARNING("WhiteSpaceVisibilityKeeper::ReplaceText() failed");
      return replaceTextResult.propagateErr();
    }
    InsertTextResult unwrappedReplacedTextResult = replaceTextResult.unwrap();
    nsresult rv = EnsureNoFollowingUnnecessaryLineBreak(
        unwrappedReplacedTextResult.EndOfInsertedTextRef());
    if (NS_FAILED(rv)) {
      NS_WARNING("HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
      return Err(rv);
    }
    // CompositionTransaction should've set selection so that we should ignore
    // caret suggestion.
    unwrappedReplacedTextResult.IgnoreCaretPointSuggestion();

    const auto newCompositionStartPoint =
        GetFirstIMESelectionStartPoint<EditorDOMPoint>();
    const auto newCompositionEndPoint =
        GetLastIMESelectionEndPoint<EditorDOMPoint>();
    if (NS_WARN_IF(!newCompositionStartPoint.IsSet()) ||
        NS_WARN_IF(!newCompositionEndPoint.IsSet())) {
      // Mutation event listener has changed the DOM tree...
      return EditActionResult::HandledResult();
    }
    rv = TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
        newCompositionStartPoint.ToRawRangeBoundary(),
        newCompositionEndPoint.ToRawRangeBoundary());
    if (NS_FAILED(rv)) {
      NS_WARNING("nsRange::SetStartAndEnd() failed");
      return Err(rv);
    }
    return EditActionResult::HandledResult();
  }

  MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText);

  // find where we are
  EditorDOMPoint currentPoint(pointToInsert);

  // is our text going to be PREformatted?
  // We remember this so that we know how to handle tabs.
  const bool isWhiteSpaceCollapsible = !EditorUtils::IsWhiteSpacePreformatted(
      *pointToInsert.ContainerAs<nsIContent>());
  const Maybe<LineBreakType> lineBreakType = GetPreferredLineBreakType(
      *pointToInsert.ContainerAs<nsIContent>(), *editingHost);
  if (NS_WARN_IF(lineBreakType.isNothing())) {
    return Err(NS_ERROR_FAILURE);
  }

  // turn off the edit listener: we know how to
  // build the "doc changed range" ourselves, and it's
  // must faster to do it once here than to track all
  // the changes one at a time.
  AutoRestore<bool> disableListener(
      EditSubActionDataRef().mAdjustChangedRangeFromListener);
  EditSubActionDataRef().mAdjustChangedRangeFromListener = false;

  // don't change my selection in subtransactions
  AutoTransactionsConserveSelection dontChangeMySelection(*this);
  {
    AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToInsert);

    const auto GetInsertTextTo = [](int32_t aInclusiveNextLinefeedOffset,
                                    uint32_t aLineStartOffset) {
      if (aInclusiveNextLinefeedOffset > 0) {
        return aLineStartOffset > 0
                   // If we'll insert a <br> and we're inserting 2nd or later
                   // line, we should always create new `Text` since it'll be
                   // between 2 <br> elements.
                   ? InsertTextTo::AlwaysCreateNewTextNode
                   // If we'll insert a <br> and we're inserting first line,
                   // we should append text to preceding text node, but
                   // we don't want to insert it to a a following text node
                   // because of avoiding to split the `Text`.
                   : InsertTextTo::ExistingTextNodeIfAvailableAndNotStart;
      }
      // If we're inserting the last line, the text should be inserted to
      // start of the following `Text` if there is or middle of the `Text`
      // at insertion position if we're inserting only the line.
      return InsertTextTo::ExistingTextNodeIfAvailable;
    };

    // for efficiency, break out the pre case separately.  This is because
    // its a lot cheaper to search the input string for only newlines than
    // it is to search for both tabs and newlines.
    if (!isWhiteSpaceCollapsible || IsPlaintextMailComposer()) {
      if (*lineBreakType == LineBreakType::Linefeed) {
        // Both Chrome and us inserts a preformatted linefeed with its own
        // `Text` node in various cases.  However, when inserting multiline
        // text, we should insert a `Text` because Chrome does so and the
        // comment field in https://discussions.apple.com/ handles the new
        // `Text` to split each line into a paragraph.  At that time, it's
        // not assumed that inserted text is split at every linefeed.
        MOZ_ASSERT(*lineBreakType == LineBreakType::Linefeed);
        Result<InsertTextResult, nsresult> insertTextResult =
            InsertTextWithTransaction(
                *document, aInsertionString, currentPoint,
                InsertTextTo::ExistingTextNodeIfAvailable);
        if (MOZ_UNLIKELY(insertTextResult.isErr())) {
          NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
          return insertTextResult.propagateErr();
        }
        // Ignore the caret suggestion because of `dontChangeMySelection`
        // above.
        insertTextResult.inspect().IgnoreCaretPointSuggestion();
        if (insertTextResult.inspect().Handled()) {
          pointToInsert = currentPoint = insertTextResult.unwrap()
                                             .EndOfInsertedTextRef()
                                             .To<EditorDOMPoint>();
        } else {
          pointToInsert = currentPoint;
        }
      } else {
        MOZ_ASSERT(*lineBreakType == LineBreakType::BRElement);
        uint32_t nextOffset = 0;
        while (nextOffset < aInsertionString.Length()) {
          const uint32_t lineStartOffset = nextOffset;
          const int32_t inclusiveNextLinefeedOffset =
              aInsertionString.FindChar(nsCRT::LF, lineStartOffset);
          const uint32_t lineLength =
              inclusiveNextLinefeedOffset != -1
                  ? static_cast<uint32_t>(inclusiveNextLinefeedOffset) -
                        lineStartOffset
                  : aInsertionString.Length() - lineStartOffset;
          if (lineLength) {
            // lineText does not include the preformatted line break.
            const nsDependentSubstring lineText(aInsertionString,
                                                lineStartOffset, lineLength);
            Result<InsertTextResult, nsresult> insertTextResult =
                InsertTextWithTransaction(
                    *document, lineText, currentPoint,
                    GetInsertTextTo(inclusiveNextLinefeedOffset,
                                    lineStartOffset));
            if (MOZ_UNLIKELY(insertTextResult.isErr())) {
              NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
              return insertTextResult.propagateErr();
            }
            // Ignore the caret suggestion because of `dontChangeMySelection`
            // above.
            insertTextResult.inspect().IgnoreCaretPointSuggestion();
            if (insertTextResult.inspect().Handled()) {
              pointToInsert = currentPoint = insertTextResult.unwrap()
                                                 .EndOfInsertedTextRef()
                                                 .To<EditorDOMPoint>();
            } else {
              pointToInsert = currentPoint;
            }
            if (inclusiveNextLinefeedOffset < 0) {
              break;  // We reached the last line
            }
          }
          MOZ_ASSERT(inclusiveNextLinefeedOffset >= 0);
          Result<CreateLineBreakResult, nsresult> insertLineBreakResultOrError =
              InsertLineBreak(WithTransaction::Yes, *lineBreakType,
                              currentPoint);
          if (MOZ_UNLIKELY(insertLineBreakResultOrError.isErr())) {
            NS_WARNING(nsPrintfCString("HTMLEditor::InsertLineBreak("
                                       "WithTransaction::Yes, %s) failed",
                                       ToString(*lineBreakType).c_str())
                           .get());
            return insertLineBreakResultOrError.propagateErr();
          }
          CreateLineBreakResult insertLineBreakResult =
              insertLineBreakResultOrError.unwrap();
          // We don't want to update selection here because we've blocked
          // InsertNodeTransaction updating selection with
          // dontChangeMySelection.
          insertLineBreakResult.IgnoreCaretPointSuggestion();
          MOZ_ASSERT(!AllowsTransactionsToChangeSelection());

          nextOffset = inclusiveNextLinefeedOffset + 1;
          pointToInsert =
              insertLineBreakResult.AfterLineBreak<EditorDOMPoint>();
          currentPoint.SetAfter(&insertLineBreakResult.LineBreakContentRef());
          if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc()) ||
              NS_WARN_IF(!currentPoint.IsSetAndValidInComposedDoc())) {
            return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
          }
        }
      }
    } else {
      uint32_t nextOffset = 0;
      while (nextOffset < aInsertionString.Length()) {
        const uint32_t lineStartOffset = nextOffset;
        const int32_t inclusiveNextLinefeedOffset =
            aInsertionString.FindChar(nsCRT::LF, lineStartOffset);
        const uint32_t lineLength =
            inclusiveNextLinefeedOffset != -1
                ? static_cast<uint32_t>(inclusiveNextLinefeedOffset) -
                      lineStartOffset
                : aInsertionString.Length() - lineStartOffset;

        if (lineLength) {
          auto insertTextResult =
              [&]() MOZ_CAN_RUN_SCRIPT -> Result<InsertTextResult, nsresult> {
            // lineText does not include the preformatted line break.
            const nsDependentSubstring lineText(aInsertionString,
                                                lineStartOffset, lineLength);
            if (!lineText.Contains(u'\t')) {
              return WhiteSpaceVisibilityKeeper::InsertText(
                  *this, lineText, currentPoint,
                  GetInsertTextTo(inclusiveNextLinefeedOffset,
                                  lineStartOffset));
            }
            nsAutoString formattedLineText(lineText);
            formattedLineText.ReplaceSubstring(u"\t"_ns, u" "_ns);
            return WhiteSpaceVisibilityKeeper::InsertText(
                *this, formattedLineText, currentPoint,
                GetInsertTextTo(inclusiveNextLinefeedOffset, lineStartOffset));
          }();
          if (MOZ_UNLIKELY(insertTextResult.isErr())) {
            NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
            return insertTextResult.propagateErr();
          }
          // Ignore the caret suggestion because of `dontChangeMySelection`
          // above.
          insertTextResult.inspect().IgnoreCaretPointSuggestion();
          if (insertTextResult.inspect().Handled()) {
            pointToInsert = currentPoint =
                insertTextResult.unwrap().EndOfInsertedTextRef();
          } else {
            pointToInsert = currentPoint;
          }
          if (inclusiveNextLinefeedOffset < 0) {
            break;  // We reached the last line
          }
        }

        Result<CreateLineBreakResult, nsresult> insertLineBreakResultOrError =
            WhiteSpaceVisibilityKeeper::InsertLineBreak(*lineBreakType, *this,
                                                        currentPoint);
        if (MOZ_UNLIKELY(insertLineBreakResultOrError.isErr())) {
          NS_WARNING(
              nsPrintfCString(
                  "WhiteSpaceVisibilityKeeper::InsertLineBreak(%s) failed",
                  ToString(*lineBreakType).c_str())
                  .get());
          return insertLineBreakResultOrError.propagateErr();
        }
        CreateLineBreakResult insertLineBreakResult =
            insertLineBreakResultOrError.unwrap();
        // TODO: Some methods called for handling non-preformatted text use
        //       ComputeEditingHost().  Therefore, they depend on the latest
        //       selection.  So we cannot skip updating selection here.
        nsresult rv = insertLineBreakResult.SuggestCaretPointTo(
            *this, {SuggestCaret::OnlyIfHasSuggestion,
                    SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
                    SuggestCaret::AndIgnoreTrivialError});
        if (NS_FAILED(rv)) {
          NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
          return Err(rv);
        }
        NS_WARNING_ASSERTION(
            rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
            "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
        nextOffset = inclusiveNextLinefeedOffset + 1;
        pointToInsert = insertLineBreakResult.AfterLineBreak<EditorDOMPoint>();
        currentPoint.SetAfter(&insertLineBreakResult.LineBreakContentRef());
        if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc()) ||
            NS_WARN_IF(!currentPoint.IsSetAndValidInComposedDoc())) {
          return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
        }
      }
    }

    // After this block, pointToInsert is updated by AutoTrackDOMPoint.
  }

  if (currentPoint.IsSet()) {
    // If we appended a collapsible white-space to the end of the text node,
    // its following content may be removed by the web app.  Then, we need to
    // keep it visible even if it becomes immediately before a block boundary.
    // For referring the node from our mutation observer, we need to store the
    // text node temporarily.
    if (currentPoint.IsInTextNode() &&
        MOZ_LIKELY(!currentPoint.IsStartOfContainer()) &&
        currentPoint.IsEndOfContainer() &&
        currentPoint.IsPreviousCharCollapsibleASCIISpace()) {
      mLastCollapsibleWhiteSpaceAppendedTextNode =
          currentPoint.ContainerAs<Text>();
    }
    nsresult rv = EnsureNoFollowingUnnecessaryLineBreak(currentPoint);
    if (NS_FAILED(rv)) {
      NS_WARNING("HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
      return Err(rv);
    }
    currentPoint.SetInterlinePosition(InterlinePosition::EndOfLine);
    rv = CollapseSelectionTo(currentPoint);
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
      return Err(NS_ERROR_EDITOR_DESTROYED);
    }
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                         "Selection::Collapse() failed, but ignored");

    // manually update the doc changed range so that AfterEdit will clean up
    // the correct portion of the document.
    rv = TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
        pointToInsert.ToRawRangeBoundary(), currentPoint.ToRawRangeBoundary());
    if (NS_FAILED(rv)) {
      NS_WARNING("nsRange::SetStartAndEnd() failed");
      return Err(rv);
    }
    return EditActionResult::HandledResult();
  }

  DebugOnly<nsresult> rvIgnored =
      SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                       "Selection::SetInterlinePosition(InterlinePosition::"
                       "EndOfLine) failed, but ignored");
  rv = TopLevelEditSubActionDataRef().mChangedRange->CollapseTo(pointToInsert);
  if (NS_FAILED(rv)) {
    NS_WARNING("nsRange::CollapseTo() failed");
    return Err(rv);
  }
  return EditActionResult::HandledResult();
}

nsresult HTMLEditor::InsertLineBreakAsSubAction() {
  MOZ_ASSERT(IsEditActionDataAvailable());
  MOZ_ASSERT(!IsSelectionRangeContainerNotContent());

  if (NS_WARN_IF(!mInitSucceeded)) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  {
    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
    if (MOZ_UNLIKELY(result.isErr())) {
      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
      return result.unwrapErr();
    }
    if (result.inspect().Canceled()) {
      return NS_OK;
    }
  }

  // XXX This may be called by execCommand() with "insertLineBreak".
  //     In such case, naming the transaction "TypingTxnName" is odd.
  AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
                                             ScrollSelectionIntoView::Yes,
                                             __FUNCTION__);

  // calling it text insertion to trigger moz br treatment by rules
  // XXX Why do we use EditSubAction::eInsertText here?  Looks like
  //     EditSubAction::eInsertLineBreak or EditSubAction::eInsertNode
  //     is better.
  IgnoredErrorResult ignoredError;
  AutoEditSubActionNotifier startToHandleEditSubAction(
      *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError);
  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
--> --------------------

--> maximum size reached

--> --------------------

98%


¤ Dauer der Verarbeitung: 0.26 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.