Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  HTMLEditorDataTransfer.cpp

  Sprache: C
 

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=78: */
/* 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 <string.h>

#include "AutoSelectionRestorer.h"
#include "EditAction.h"
#include "EditorDOMPoint.h"
#include "EditorUtils.h"
#include "HTMLEditHelpers.h"
#include "HTMLEditUtils.h"
#include "InternetCiter.h"
#include "PendingStyles.h"
#include "SelectionState.h"
#include "WhiteSpaceVisibilityKeeper.h"
#include "WSRunScanner.h"

#include "ErrorList.h"
#include "mozilla/dom/Comment.h"
#include "mozilla/dom/DataTransfer.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentFragment.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/DOMStringList.h"
#include "mozilla/dom/DOMStringList.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/FileBlobImpl.h"
#include "mozilla/dom/FileReader.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/StaticRange.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/Base64.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Maybe.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/Preferences.h"
#include "mozilla/Result.h"
#include "mozilla/TextComposition.h"
#include "nsAString.h"
#include "nsCOMPtr.h"
#include "nsCRTGlue.h"  // for CRLF
#include "nsComponentManagerUtils.h"
#include "nsIScriptError.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsDependentSubstring.h"
#include "nsError.h"
#include "nsFocusManager.h"
#include "nsGkAtoms.h"
#include "nsIClipboard.h"
#include "nsIContent.h"
#include "nsIDocumentEncoder.h"
#include "nsIFile.h"
#include "nsIInputStream.h"
#include "nsIMIMEService.h"
#include "nsINode.h"
#include "nsIParserUtils.h"
#include "nsIPrincipal.h"
#include "nsISupportsImpl.h"
#include "nsISupportsPrimitives.h"
#include "nsISupportsUtils.h"
#include "nsITransferable.h"
#include "nsIVariant.h"
#include "nsLinebreakConverter.h"
#include "nsLiteralString.h"
#include "nsNameSpaceManager.h"
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
#include "nsRange.h"
#include "nsReadableUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsStreamUtils.h"
#include "nsString.h"
#include "nsStringFwd.h"
#include "nsStringIterator.h"
#include "nsTreeSanitizer.h"
#include "nsXPCOM.h"
#include "nscore.h"
#include "nsContentUtils.h"
#include "nsQueryObject.h"

class nsAtom;
class nsILoadContext;

namespace mozilla {

using namespace dom;
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
using LeafNodeType = HTMLEditUtils::LeafNodeType;

#define kInsertCookie "_moz_Insert Here_moz_"

// some little helpers
static bool FindIntegerAfterString(const char* aLeadingString,
                                   const nsCString& aCStr,
                                   int32_t& foundNumber);
static void RemoveFragComments(nsCString& aStr);

nsresult HTMLEditor::InsertDroppedDataTransferAsAction(
    AutoEditActionDataSetter& aEditActionData, DataTransfer& aDataTransfer,
    const EditorDOMPoint& aDroppedAt, nsIPrincipal* aSourcePrincipal) {
  MOZ_ASSERT(aEditActionData.GetEditAction() == EditAction::eDrop);
  MOZ_ASSERT(GetEditAction() == EditAction::eDrop);
  MOZ_ASSERT(aDroppedAt.IsSet());
  MOZ_ASSERT(aDataTransfer.MozItemCount() > 0);

  if (IsReadonly()) {
    return NS_OK;
  }

  aEditActionData.InitializeDataTransfer(&aDataTransfer);
  RefPtr<StaticRange> targetRange = StaticRange::Create(
      aDroppedAt.GetContainer(), aDroppedAt.Offset(), aDroppedAt.GetContainer(),
      aDroppedAt.Offset(), IgnoreErrors());
  NS_WARNING_ASSERTION(targetRange && targetRange->IsPositioned(),
                       "Why did we fail to create collapsed static range at "
                       "dropped position?");
  if (targetRange && targetRange->IsPositioned()) {
    aEditActionData.AppendTargetRange(*targetRange);
  }
  nsresult rv = aEditActionData.MaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "MaybeDispatchBeforeInputEvent() failed");
    return rv;
  }

  if (MOZ_UNLIKELY(!aDroppedAt.IsInContentNode())) {
    NS_WARNING("Dropped into non-content node");
    return NS_OK;
  }

  const RefPtr<Element> editingHost = ComputeEditingHost(
      *aDroppedAt.ContainerAs<nsIContent>(), LimitInBodyElement::No);
  if (MOZ_UNLIKELY(!editingHost)) {
    NS_WARNING("Dropped onto non-editable node");
    return NS_OK;
  }

  uint32_t numItems = aDataTransfer.MozItemCount();
  for (uint32_t i = 0; i < numItems; ++i) {
    DebugOnly<nsresult> rvIgnored =
        InsertFromDataTransfer(&aDataTransfer, i, aSourcePrincipal, aDroppedAt,
                               DeleteSelectedContent::No, *editingHost);
    if (NS_WARN_IF(Destroyed())) {
      return NS_OK;
    }
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                         "HTMLEditor::InsertFromDataTransfer("
                         "DeleteSelectedContent::No) failed, but ignored");
  }
  return NS_OK;
}

nsresult HTMLEditor::LoadHTML(const nsAString& aInputString) {
  MOZ_ASSERT(IsEditActionDataAvailable());

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

  // force IME commit; set up rules sniffing and batching
  DebugOnly<nsresult> rvIgnored = CommitComposition();
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                       "EditorBase::CommitComposition() failed, but ignored");
  if (NS_WARN_IF(Destroyed())) {
    return NS_ERROR_EDITOR_DESTROYED;
  }

  AutoPlaceholderBatch treatAsOneTransaction(
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
  IgnoredErrorResult ignoredError;
  AutoEditSubActionNotifier startToHandleEditSubAction(
      *this, EditSubAction::eInsertHTMLSource, nsIEditor::eNext, ignoredError);
  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
    return ignoredError.StealNSResult();
  }
  NS_WARNING_ASSERTION(
      !ignoredError.Failed(),
      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");

  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
  if (NS_FAILED(rv)) {
    NS_WARNING("EditorBase::EnsureNoPaddingBRElementForEmptyEditor() failed");
    return rv;
  }

  // Delete Selection, but only if it isn't collapsed, see bug #106269
  if (!SelectionRef().IsCollapsed()) {
    nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip);
    if (NS_FAILED(rv)) {
      NS_WARNING(
          "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
      return rv;
    }
  }

  // Get the first range in the selection, for context:
  RefPtr<const nsRange> range = SelectionRef().GetRangeAt(0);
  if (NS_WARN_IF(!range)) {
    return NS_ERROR_FAILURE;
  }

  // Create fragment for pasted HTML.
  ErrorResult error;
  RefPtr<DocumentFragment> documentFragment =
      range->CreateContextualFragment(aInputString, error);
  if (error.Failed()) {
    NS_WARNING("nsRange::CreateContextualFragment() failed");
    return error.StealNSResult();
  }

  // Put the fragment into the document at start of selection.
  EditorDOMPoint pointToInsert(range->StartRef());
  // XXX We need to make pointToInsert store offset for keeping traditional
  //     behavior since using only child node to pointing insertion point
  //     changes the behavior when inserted child is moved by mutation
  //     observer.  We need to investigate what we should do here.
  Unused << pointToInsert.Offset();
  EditorDOMPoint pointToPutCaret;
  for (nsCOMPtr<nsIContent> contentToInsert = documentFragment->GetFirstChild();
       contentToInsert; contentToInsert = documentFragment->GetFirstChild()) {
    Result<CreateContentResult, nsresult> insertChildContentNodeResult =
        InsertNodeWithTransaction(*contentToInsert, pointToInsert);
    if (MOZ_UNLIKELY(insertChildContentNodeResult.isErr())) {
      NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
      return insertChildContentNodeResult.unwrapErr();
    }
    CreateContentResult unwrappedInsertChildContentNodeResult =
        insertChildContentNodeResult.unwrap();
    unwrappedInsertChildContentNodeResult.MoveCaretPointTo(
        pointToPutCaret, *this,
        {SuggestCaret::OnlyIfHasSuggestion,
         SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
    // XXX If the inserted node has been moved by mutation observer,
    //     incrementing offset will cause odd result.  Next new node
    //     will be inserted after existing node and the offset will be
    //     overflown from the container node.
    pointToInsert.Set(pointToInsert.GetContainer(), pointToInsert.Offset() + 1);
    if (NS_WARN_IF(!pointToInsert.Offset())) {
      // Append the remaining children to the container if offset is
      // overflown.
      pointToInsert.SetToEndOf(pointToInsert.GetContainer());
    }
  }

  if (pointToPutCaret.IsSet()) {
    nsresult rv = CollapseSelectionTo(pointToPutCaret);
    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
      NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored");
      return NS_ERROR_EDITOR_DESTROYED;
    }
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rv),
        "EditorBase::CollapseSelectionTo() failed, but ignored");
  }

  return NS_OK;
}

NS_IMETHODIMP HTMLEditor::InsertHTML(const nsAString& aInString) {
  nsresult rv = InsertHTMLAsAction(aInString);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "HTMLEditor::InsertHTMLAsAction() failed");
  return rv;
}

nsresult HTMLEditor::InsertHTMLAsAction(const nsAString& aInString,
                                        nsIPrincipal* aPrincipal) {
  // FIXME: This should keep handling inserting HTML if the caller is
  // nsIHTMLEditor::InsertHTML.
  if (IsReadonly()) {
    return NS_OK;
  }

  AutoEditActionDataSetter editActionData(*this, EditAction::eInsertHTML,
                                          aPrincipal);
  nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
    return EditorBase::ToGenericNSResult(rv);
  }

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

  if (editingHost->IsContentEditablePlainTextOnly()) {
    nsAutoString plaintextString;
    nsresult rv = nsContentUtils::ConvertToPlainText(
        aInString, plaintextString, nsIDocumentEncoder::OutputLFLineBreak,
        0u /* never wrap lines*/);
    if (NS_FAILED(rv)) {
      NS_WARNING("nsContentUtils::ConvertToPlainText() failed");
      return EditorBase::ToGenericNSResult(rv);
    }
    Maybe<AutoPlaceholderBatch> treatAsOneTransaction;
    const auto EnsureAutoPlaceholderBatch = [&]() {
      if (treatAsOneTransaction.isNothing()) {
        treatAsOneTransaction.emplace(*this, ScrollSelectionIntoView::Yes,
                                      __FUNCTION__);
      }
    };
    if (mComposition &&
        mComposition->CanRequsetIMEToCommitOrCancelComposition()) {
      EnsureAutoPlaceholderBatch();
      CommitComposition();
      if (NS_WARN_IF(Destroyed())) {
        return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
      }
      if (NS_WARN_IF(editingHost !=
                     ComputeEditingHost(LimitInBodyElement::No))) {
        return EditorBase::ToGenericNSResult(
            NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
      }
    }
    if (MOZ_LIKELY(!plaintextString.IsEmpty())) {
      EnsureAutoPlaceholderBatch();
      rv = InsertTextAsSubAction(plaintextString, SelectionHandling::Delete);
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                           "EditorBase::InsertTextAsSubAction() failed");
    } else if (!SelectionRef().IsCollapsed()) {
      EnsureAutoPlaceholderBatch();
      rv = DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                           "EditorBase::DeleteSelectionAsSubAction() failed");
    }
    return EditorBase::ToGenericNSResult(rv);
  }
  AutoPlaceholderBatch treatAsOneTransaction(
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
  rv = InsertHTMLWithContextAsSubAction(
      aInString, u""_ns, u""_ns, u""_ns, SafeToInsertData::Yes,
      EditorDOMPoint(), DeleteSelectedContent::Yes,
      InlineStylesAtInsertionPoint::Clear, *editingHost);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "HTMLEditor::InsertHTMLWithContextAsSubAction("
                       "SafeToInsertData::Yes, DeleteSelectedContent::Yes, "
                       "InlineStylesAtInsertionPoint::Clear) failed");
  return EditorBase::ToGenericNSResult(rv);
}

class MOZ_STACK_CLASS HTMLEditor::HTMLWithContextInserter final {
 public:
  MOZ_CAN_RUN_SCRIPT HTMLWithContextInserter(HTMLEditor& aHTMLEditor,
                                             const Element& aEditingHost)
      : mHTMLEditor(aHTMLEditor), mEditingHost(aEditingHost) {}

  HTMLWithContextInserter() = delete;
  HTMLWithContextInserter(const HTMLWithContextInserter&) = delete;
  HTMLWithContextInserter(HTMLWithContextInserter&&) = delete;

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run(
      const nsAString& aInputString, const nsAString& aContextStr,
      const nsAString& aInfoStr, SafeToInsertData aSafeToInsertData,
      InlineStylesAtInsertionPoint aInlineStylesAtInsertionPoint);

 private:
  class FragmentFromPasteCreator;
  class FragmentParser;
  /**
   * CollectTopMostChildContentsCompletelyInRange() collects topmost child
   * contents which are completely in the given range.
   * For example, if the range points a node with its container node, the
   * result is only the node (meaning does not include its descendants).
   * If the range starts start of a node and ends end of it, and if the node
   * does not have children, returns no nodes, otherwise, if the node has
   * some children, the result includes its all children (not including their
   * descendants).
   *
   * @param aStartPoint         Start point of the range.
   * @param aEndPoint           End point of the range.
   * @param aOutArrayOfContents [Out] Topmost children which are completely in
   *                            the range.
   */

  static void CollectTopMostChildContentsCompletelyInRange(
      const EditorRawDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint,
      nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents);

  /**
   * @return nullptr, if there's no invisible `<br>`.
   */

  HTMLBRElement* GetInvisibleBRElementAtPoint(
      const EditorDOMPoint& aPointToInsert) const;

  EditorDOMPoint GetNewCaretPointAfterInsertingHTML(
      const EditorDOMPoint& aLastInsertedPoint) const;

  /**
   * @return error result or the last inserted point. The latter is only set, if
   *         content was inserted.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
  InsertContents(
      const EditorDOMPoint& aPointToInsert,
      nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents,
      const nsINode* aFragmentAsNode);

  /**
   * @param aContextStr as indicated by nsITransferable's kHTMLContext.
   * @param aInfoStr as indicated by nsITransferable's kHTMLInfo.
   */

  nsresult CreateDOMFragmentFromPaste(
      const nsAString& aInputString, const nsAString& aContextStr,
      const nsAString& aInfoStr, nsCOMPtr<nsINode>* aOutFragNode,
      nsCOMPtr<nsINode>* aOutStartNode, nsCOMPtr<nsINode>* aOutEndNode,
      uint32_t* aOutStartOffset, uint32_t* aOutEndOffset,
      SafeToInsertData aSafeToInsertData) const;

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult MoveCaretOutsideOfLink(
      Element& aLinkElement, const EditorDOMPoint& aPointToPutCaret);

  // MOZ_KNOWN_LIVE because this is set only by the constructor which is
  // marked as MOZ_CAN_RUN_SCRIPT and this is allocated only in the stack.
  MOZ_KNOWN_LIVE HTMLEditor& mHTMLEditor;
  MOZ_KNOWN_LIVE const Element& mEditingHost;
};

class MOZ_STACK_CLASS
HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator final {
 public:
  nsresult Run(const Document& aDocument, const nsAString& aInputString,
               const nsAString& aContextStr, const nsAString& aInfoStr,
               nsCOMPtr<nsINode>* aOutFragNode,
               nsCOMPtr<nsINode>* aOutStartNode, nsCOMPtr<nsINode>* aOutEndNode,
               SafeToInsertData aSafeToInsertData) const;

 private:
  nsresult CreateDocumentFragmentAndGetParentOfPastedHTMLInContext(
      const Document& aDocument, const nsAString& aInputString,
      const nsAString& aContextStr, SafeToInsertData aSafeToInsertData,
      nsCOMPtr<nsINode>& aParentNodeOfPastedHTMLInContext,
      RefPtr<DocumentFragment>& aDocumentFragmentToInsert) const;

  static nsAtom* DetermineContextLocalNameForParsingPastedHTML(
      const nsIContent* aParentContentOfPastedHTMLInContext);

  static bool FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie(
      nsINode& aStart, nsCOMPtr<nsINode>& aResult);

  static bool IsInsertionCookie(const nsIContent& aContent);

  /**
   * @param aDocumentFragmentForContext contains the merged result.
   */

  static nsresult MergeAndPostProcessFragmentsForPastedHTMLAndContext(
      DocumentFragment& aDocumentFragmentForPastedHTML,
      DocumentFragment& aDocumentFragmentForContext,
      nsIContent& aTargetContentOfContextForPastedHTML);

  /**
   * @param aInfoStr as indicated by nsITransferable's kHTMLInfo.
   */

  [[nodiscard]] static nsresult MoveStartAndEndAccordingToHTMLInfo(
      const nsAString& aInfoStr, nsCOMPtr<nsINode>* aOutStartNode,
      nsCOMPtr<nsINode>* aOutEndNode);

  static nsresult PostProcessFragmentForPastedHTMLWithoutContext(
      DocumentFragment& aDocumentFragmentForPastedHTML);

  static nsresult PreProcessContextDocumentFragmentForMerging(
      DocumentFragment& aDocumentFragmentForContext);

  static void RemoveHeadChildAndStealBodyChildsChildren(nsINode& aNode);

  /**
   * This is designed for a helper class to remove disturbing nodes at inserting
   * the HTML fragment into the DOM tree.  This walks the children and if some
   * elements do not have enough children, e.g., list elements not having
   * another visible list elements nor list item elements,
   * will be removed.
   *
   * @param aNode       Should not be a node whose mutation may be observed by
   *                    JS.
   */

  static void RemoveIncompleteDescendantsFromInsertingFragment(nsINode& aNode);

  enum class NodesToRemove {
    eAll,
    eOnlyListItems /*!< List items are always block-level elements, hence such
                     whitespace-only nodes are always invisible. */

  };
  static nsresult
  RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
      nsIContent& aNode, NodesToRemove aNodesToRemove);
};

HTMLBRElement*
HTMLEditor::HTMLWithContextInserter::GetInvisibleBRElementAtPoint(
    const EditorDOMPoint& aPointToInsert) const {
  const WSRunScanner wsRunScannerAtInsertionPoint(
      WSRunScanner::Scan::EditableNodes, aPointToInsert,
      BlockInlineCheck::UseComputedDisplayStyle);
  if (wsRunScannerAtInsertionPoint.EndsByInvisibleBRElement()) {
    return wsRunScannerAtInsertionPoint.EndReasonBRElementPtr();
  }
  return nullptr;
}

EditorDOMPoint
HTMLEditor::HTMLWithContextInserter::GetNewCaretPointAfterInsertingHTML(
    const EditorDOMPoint& aLastInsertedPoint) const {
  EditorDOMPoint pointToPutCaret;

  // but don't cross tables
  nsIContent* containerContent = nullptr;
  if (!HTMLEditUtils::IsTable(aLastInsertedPoint.GetChild())) {
    containerContent = HTMLEditUtils::GetLastLeafContent(
        *aLastInsertedPoint.GetChild(), {LeafNodeType::OnlyEditableLeafNode},
        BlockInlineCheck::Unused,
        aLastInsertedPoint.GetChild()->GetAsElementOrParentElement());
    if (containerContent) {
      Element* mostDistantInclusiveAncestorTableElement = nullptr;
      for (Element* maybeTableElement =
               containerContent->GetAsElementOrParentElement();
           maybeTableElement &&
           maybeTableElement != aLastInsertedPoint.GetChild();
           maybeTableElement = maybeTableElement->GetParentElement()) {
        if (HTMLEditUtils::IsTable(maybeTableElement)) {
          mostDistantInclusiveAncestorTableElement = maybeTableElement;
        }
      }
      // If we're in table elements, we should put caret into the most ancestor
      // table element.
      if (mostDistantInclusiveAncestorTableElement) {
        containerContent = mostDistantInclusiveAncestorTableElement;
      }
    }
  }
  // If we are not in table elements, we should put caret in the last inserted
  // node.
  if (!containerContent) {
    containerContent = aLastInsertedPoint.GetChild();
  }

  // If the container is a text node or a container element except `<table>`
  // element, put caret a end of it.
  if (containerContent->IsText() ||
      (HTMLEditUtils::IsContainerNode(*containerContent) &&
       !HTMLEditUtils::IsTable(containerContent))) {
    pointToPutCaret.SetToEndOf(containerContent);
  }
  // Otherwise, i.e., it's an atomic element, `<table>` element or data node,
  // put caret after it.
  else {
    pointToPutCaret.Set(containerContent);
    DebugOnly<bool> advanced = pointToPutCaret.AdvanceOffset();
    NS_WARNING_ASSERTION(advanced, "Failed to advance offset from found node");
  }

  // Make sure we don't end up with selection collapsed after an invisible
  // `<br>` element.
  const WSRunScanner wsRunScannerAtCaret(
      WSRunScanner::Scan::EditableNodes, pointToPutCaret,
      BlockInlineCheck::UseComputedDisplayStyle);
  if (wsRunScannerAtCaret
          .ScanPreviousVisibleNodeOrBlockBoundaryFrom(pointToPutCaret)
          .ReachedInvisibleBRElement()) {
    const WSRunScanner wsRunScannerAtStartReason(
        WSRunScanner::Scan::EditableNodes,
        EditorDOMPoint(wsRunScannerAtCaret.GetStartReasonContent()),
        BlockInlineCheck::UseComputedDisplayStyle);
    const WSScanResult backwardScanFromPointToCaretResult =
        wsRunScannerAtStartReason.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
            pointToPutCaret);
    if (backwardScanFromPointToCaretResult.InVisibleOrCollapsibleCharacters()) {
      pointToPutCaret = backwardScanFromPointToCaretResult
                            .PointAfterReachedContent<EditorDOMPoint>();
    } else if (backwardScanFromPointToCaretResult.ReachedSpecialContent()) {
      // XXX In my understanding, this is odd.  The end reason may not be
      //     same as the reached special content because the equality is
      //     guaranteed only when ReachedCurrentBlockBoundary() returns true.
      //     However, looks like that this code assumes that
      //     GetStartReasonContent() returns the content.
      NS_ASSERTION(wsRunScannerAtStartReason.GetStartReasonContent() ==
                       backwardScanFromPointToCaretResult.GetContent(),
                   "Start reason is not the reached special content");
      pointToPutCaret.SetAfter(
          wsRunScannerAtStartReason.GetStartReasonContent());
    }
  }

  return pointToPutCaret;
}

nsresult HTMLEditor::InsertHTMLWithContextAsSubAction(
    const nsAString& aInputString, const nsAString& aContextStr,
    const nsAString& aInfoStr, const nsAString& aFlavor,
    SafeToInsertData aSafeToInsertData, const EditorDOMPoint& aPointToInsert,
    DeleteSelectedContent aDeleteSelectedContent,
    InlineStylesAtInsertionPoint aInlineStylesAtInsertionPoint,
    const Element& aEditingHost) {
  MOZ_ASSERT(IsEditActionDataAvailable());

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

  CommitComposition();

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

  {
    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;
    }
  }

  // If we have a destination / target node, we want to insert there rather than
  // in place of the selection.  Ignore aDeleteSelectedContent here if
  // aPointToInsert is not set since deletion will also occur later in
  // HTMLWithContextInserter and will be collapsed around there; this block
  // is intended to cover the various scenarios where we are dropping in an
  // editor (and may want to delete the selection before collapsing the
  // selection in the new destination)
  if (aPointToInsert.IsSet()) {
    nsresult rv =
        PrepareToInsertContent(aPointToInsert, aDeleteSelectedContent);
    if (NS_FAILED(rv)) {
      NS_WARNING("EditorBase::PrepareToInsertContent() failed");
      return rv;
    }
    aDeleteSelectedContent = DeleteSelectedContent::No;
  }

  HTMLWithContextInserter htmlWithContextInserter(*this, aEditingHost);

  Result<EditActionResult, nsresult> result = htmlWithContextInserter.Run(
      aInputString, aContextStr, aInfoStr, aSafeToInsertData,
      aInlineStylesAtInsertionPoint);
  if (MOZ_UNLIKELY(result.isErr())) {
    return result.unwrapErr();
  }

  // If nothing is inserted and delete selection is required, we need to
  // delete selection right now.
  if (result.inspect().Ignored() &&
      aDeleteSelectedContent == DeleteSelectedContent::Yes) {
    nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip);
    if (NS_FAILED(rv)) {
      NS_WARNING(
          "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
      return rv;
    }
  }
  return NS_OK;
}

Result<EditActionResult, nsresult> HTMLEditor::HTMLWithContextInserter::Run(
    const nsAString& aInputString, const nsAString& aContextStr,
    const nsAString& aInfoStr, SafeToInsertData aSafeToInsertData,
    InlineStylesAtInsertionPoint aInlineStylesAtInsertionPoint) {
  MOZ_ASSERT(mHTMLEditor.IsEditActionDataAvailable());

  // create a dom document fragment that represents the structure to paste
  nsCOMPtr<nsINode> fragmentAsNode, streamStartParent, streamEndParent;
  uint32_t streamStartOffset = 0, streamEndOffset = 0;

  nsresult rv = CreateDOMFragmentFromPaste(
      aInputString, aContextStr, aInfoStr, address_of(fragmentAsNode),
      address_of(streamStartParent), address_of(streamEndParent),
      &streamStartOffset, &streamEndOffset, aSafeToInsertData);
  if (NS_FAILED(rv)) {
    NS_WARNING(
        "HTMLEditor::HTMLWithContextInserter::CreateDOMFragmentFromPaste() "
        "failed");
    return Err(rv);
  }

  // we need to recalculate various things based on potentially new offsets
  // this is work to be completed at a later date (probably by jfrancis)

  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfTopMostChildContents;
  // If we have stream start point information, lets use it and end point.
  // Otherwise, we should make a range all over the document fragment.
  EditorRawDOMPoint streamStartPoint =
      streamStartParent
          ? EditorRawDOMPoint(streamStartParent,
                              AssertedCast<uint32_t>(streamStartOffset))
          : EditorRawDOMPoint(fragmentAsNode, 0);
  EditorRawDOMPoint streamEndPoint =
      streamStartParent ? EditorRawDOMPoint(streamEndParent, streamEndOffset)
                        : EditorRawDOMPoint::AtEndOf(fragmentAsNode);

  Unused << streamStartPoint;
  Unused << streamEndPoint;

  HTMLWithContextInserter::CollectTopMostChildContentsCompletelyInRange(
      EditorRawDOMPoint(streamStartParent,
                        AssertedCast<uint32_t>(streamStartOffset)),
      EditorRawDOMPoint(streamEndParent,
                        AssertedCast<uint32_t>(streamEndOffset)),
      arrayOfTopMostChildContents);

  if (arrayOfTopMostChildContents.IsEmpty()) {
    return EditActionResult::IgnoredResult();  // Nothing to insert.
  }

  // Are there any table elements in the list?
  // check for table cell selection mode
  bool cellSelectionMode =
      HTMLEditUtils::IsInTableCellSelectionMode(mHTMLEditor.SelectionRef());

  if (cellSelectionMode) {
    // do we have table content to paste?  If so, we want to delete
    // the selected table cells and replace with new table elements;
    // but if not we want to delete _contents_ of cells and replace
    // with non-table elements.  Use cellSelectionMode bool to
    // indicate results.
    if (!HTMLEditUtils::IsAnyTableElement(arrayOfTopMostChildContents[0])) {
      cellSelectionMode = false;
    }
  }

  if (!cellSelectionMode) {
    rv = mHTMLEditor.DeleteSelectionAndPrepareToCreateNode();
    if (NS_FAILED(rv)) {
      NS_WARNING("HTMLEditor::DeleteSelectionAndPrepareToCreateNode() failed");
      return Err(rv);
    }

    if (aInlineStylesAtInsertionPoint == InlineStylesAtInsertionPoint::Clear) {
      // pasting does not inherit local inline styles
      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
          mHTMLEditor.ClearStyleAt(
              EditorDOMPoint(mHTMLEditor.SelectionRef().AnchorRef()),
              EditorInlineStyle::RemoveAllStyles(), SpecifiedStyle::Preserve,
              mEditingHost);
      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
        NS_WARNING("HTMLEditor::ClearStyleAt() failed");
        return pointToPutCaretOrError.propagateErr();
      }
      if (pointToPutCaretOrError.inspect().IsSetAndValid()) {
        nsresult rv =
            mHTMLEditor.CollapseSelectionTo(pointToPutCaretOrError.unwrap());
        if (NS_FAILED(rv)) {
          NS_WARNING("EditorBase::CollapseSelectionTo() failed");
          return Err(rv);
        }
      }
    }
  } else {
    // Delete whole cells: we will replace with new table content.

    // Braces for artificial block to scope AutoSelectionRestorer.
    // Save current selection since DeleteTableCellWithTransaction() perturbs
    // it.
    {
      AutoSelectionRestorer restoreSelectionLater(&mHTMLEditor);
      rv = mHTMLEditor.DeleteTableCellWithTransaction(1);
      if (NS_FAILED(rv)) {
        NS_WARNING("HTMLEditor::DeleteTableCellWithTransaction(1) failed");
        return Err(rv);
      }
    }
    // collapse selection to beginning of deleted table content
    IgnoredErrorResult ignoredError;
    mHTMLEditor.SelectionRef().CollapseToStart(ignoredError);
    NS_WARNING_ASSERTION(!ignoredError.Failed(),
                         "Selection::Collapse() failed, but ignored");
  }

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

  mHTMLEditor.UndefineCaretBidiLevel();

  rv = mHTMLEditor.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) && mHTMLEditor.SelectionRef().IsCollapsed()) {
    nsresult rv =
        mHTMLEditor.EnsureCaretNotAfterInvisibleBRElement(mEditingHost);
    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 = mHTMLEditor.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");
    }
  }

  // Adjust position based on the first node we are going to insert.
  const auto candidatePointToInsert =
      mHTMLEditor.GetFirstSelectionStartPoint<EditorRawDOMPoint>();
  if (NS_WARN_IF(!candidatePointToInsert.IsSet()) ||
      NS_WARN_IF(
          !candidatePointToInsert.GetContainer()->IsInclusiveDescendantOf(
              &mEditingHost))) {
    return Err(NS_ERROR_FAILURE);
  }
  EditorDOMPoint pointToInsert =
      HTMLEditUtils::GetBetterInsertionPointFor<EditorDOMPoint>(
          arrayOfTopMostChildContents[0],
          mHTMLEditor.GetFirstSelectionStartPoint<EditorRawDOMPoint>());
  if (!pointToInsert.IsSet()) {
    NS_WARNING("HTMLEditor::GetBetterInsertionPointFor() failed");
    return Err(NS_ERROR_FAILURE);
  }

  const bool insertionPointWasInLink =
      !!HTMLEditor::GetLinkElement(pointToInsert.GetContainer());

  if (pointToInsert.IsInTextNode()) {
    Result<SplitNodeResult, nsresult> splitNodeResult =
        mHTMLEditor.SplitNodeDeepWithTransaction(
            MOZ_KnownLive(*pointToInsert.ContainerAs<nsIContent>()),
            pointToInsert, SplitAtEdges::eAllowToCreateEmptyContainer);
    if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
      NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
      return splitNodeResult.propagateErr();
    }
    nsresult rv = splitNodeResult.inspect().SuggestCaretPointTo(
        mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
                      SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
    if (NS_FAILED(rv)) {
      NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
      return Err(rv);
    }
    pointToInsert = splitNodeResult.inspect().AtSplitPoint<EditorDOMPoint>();
    if (MOZ_UNLIKELY(!pointToInsert.IsSet())) {
      NS_WARNING(
          "HTMLEditor::SplitNodeDeepWithTransaction() didn't return split "
          "point");
      return Err(NS_ERROR_FAILURE);
    }
  }

  {  // Block only for AutoHTMLFragmentBoundariesFixer to hide it from the
     // following code. Note that it may modify arrayOfTopMostChildContents.
    AutoHTMLFragmentBoundariesFixer fixPiecesOfTablesAndLists(
        arrayOfTopMostChildContents);
  }

  MOZ_ASSERT(pointToInsert.GetContainer()->GetChildAt_Deprecated(
                 pointToInsert.Offset()) == pointToInsert.GetChild());

  Result<EditorDOMPoint, nsresult> lastInsertedPoint = InsertContents(
      pointToInsert, arrayOfTopMostChildContents, fragmentAsNode);
  if (lastInsertedPoint.isErr()) {
    NS_WARNING("HTMLWithContextInserter::InsertContents() failed.");
    return lastInsertedPoint.propagateErr();
  }

  mHTMLEditor.TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements =
      false;

  if (MOZ_UNLIKELY(!lastInsertedPoint.inspect().IsInComposedDoc())) {
    return EditActionResult::HandledResult();
  }

  if (MOZ_LIKELY(lastInsertedPoint.inspect().IsInContentNode())) {
    const auto afterLastInsertedContent =
        lastInsertedPoint.inspect().NextPointOrAfterContainer();
    if (MOZ_LIKELY(afterLastInsertedContent.IsInContentNode())) {
      nsresult rv = mHTMLEditor.EnsureNoFollowingUnnecessaryLineBreak(
          afterLastInsertedContent);
      if (NS_FAILED(rv)) {
        NS_WARNING(
            "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
        return Err(rv);
      }
    }
  }

  const EditorDOMPoint pointToPutCaret =
      GetNewCaretPointAfterInsertingHTML(lastInsertedPoint.inspect());
  // Now collapse the selection to the end of what we just inserted.
  rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret);
  if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
    NS_WARNING(
        "EditorBase::CollapseSelectionTo() caused destroying the editor");
    return Err(NS_ERROR_EDITOR_DESTROYED);
  }
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "EditorBase::CollapseSelectionTo() failed, but ignored");

  // If we didn't start from an `<a href>` element, we should not keep
  // caret in the link to make users type something outside the link.
  if (insertionPointWasInLink) {
    return EditActionResult::HandledResult();
  }
  RefPtr<Element> linkElement = GetLinkElement(pointToPutCaret.GetContainer());

  if (!linkElement) {
    return EditActionResult::HandledResult();
  }

  rv = MoveCaretOutsideOfLink(*linkElement, pointToPutCaret);
  if (NS_FAILED(rv)) {
    NS_WARNING(
        "HTMLEditor::HTMLWithContextInserter::MoveCaretOutsideOfLink "
        "failed.");
    return Err(rv);
  }

  return EditActionResult::HandledResult();
}

Result<EditorDOMPoint, nsresult>
HTMLEditor::HTMLWithContextInserter::InsertContents(
    const EditorDOMPoint& aPointToInsert,
    nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents,
    const nsINode* aFragmentAsNode) {
  MOZ_ASSERT(aPointToInsert.IsSetAndValidInComposedDoc());

  EditorDOMPoint pointToInsert{aPointToInsert};

  // Loop over the node list and paste the nodes:
  const RefPtr<const Element> maybeNonEditableBlockElement =
      pointToInsert.IsInContentNode()
          ? HTMLEditUtils::GetInclusiveAncestorElement(
                *pointToInsert.ContainerAs<nsIContent>(),
                HTMLEditUtils::ClosestBlockElement,
                BlockInlineCheck::UseComputedDisplayOutsideStyle)
          : nullptr;

  EditorDOMPoint lastInsertedPoint;
  nsCOMPtr<nsIContent> insertedContextParentContent;
  for (OwningNonNull<nsIContent>& content : aArrayOfTopMostChildContents) {
    if (NS_WARN_IF(content == aFragmentAsNode) ||
        NS_WARN_IF(content->IsHTMLElement(nsGkAtoms::body))) {
      return Err(NS_ERROR_FAILURE);
    }

    if (insertedContextParentContent) {
      // If we had to insert something higher up in the paste hierarchy,
      // we want to skip any further paste nodes that descend from that.
      // Else we will paste twice.
      // XXX This check may be really expensive.  Cannot we check whether
      //     the node's `ownerDocument` is the `aFragmentAsNode` or not?
      // XXX If content was moved to outside of insertedContextParentContent
      //     by mutation event listeners, we will anyway duplicate it.
      if (EditorUtils::IsDescendantOf(*content,
                                      *insertedContextParentContent)) {
        continue;
      }
    }

    // If a `<table>` or `<tr>` element on the clipboard, and pasting it into
    // a `<table>` or `<tr>` element, insert only the appropriate children
    // instead.
    bool inserted = false;
    if (HTMLEditUtils::IsTableRow(content) &&
        HTMLEditUtils::IsTableRow(pointToInsert.GetContainer()) &&
        (HTMLEditUtils::IsTable(content) ||
         HTMLEditUtils::IsTable(pointToInsert.GetContainer()))) {
      // Move children of current node to the insertion point.
      AutoTArray<OwningNonNull<nsIContent>, 24> children;
      HTMLEditUtils::CollectAllChildren(*content, children);
      EditorDOMPoint pointToPutCaret;
      for (const OwningNonNull<nsIContent>& child : children) {
        // MOZ_KnownLive(child) because of bug 1622253
        Result<CreateContentResult, nsresult> moveChildResult =
            mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction<nsIContent>(
                MOZ_KnownLive(child), pointToInsert,
                SplitAtEdges::eDoNotCreateEmptyContainer);
        if (MOZ_UNLIKELY(moveChildResult.isErr())) {
          // If moving node is moved to different place, we should ignore
          // this result and keep trying to insert next content node to same
          // position.
          if (moveChildResult.inspectErr() ==
              NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) {
            inserted = true;
            continue;  // the inner `for` loop
          }
          NS_WARNING(
              "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
              "SplitAtEdges::eDoNotCreateEmptyContainer) failed, maybe "
              "ignored");
          break;  // from the inner `for` loop
        }
        if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) {
          continue;
        }
        inserted = true;
        lastInsertedPoint.Set(child);
        pointToInsert = lastInsertedPoint.NextPoint();
        MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
        CreateContentResult unwrappedMoveChildResult = moveChildResult.unwrap();
        unwrappedMoveChildResult.MoveCaretPointTo(
            pointToPutCaret, mHTMLEditor,
            {SuggestCaret::OnlyIfHasSuggestion,
             SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
      }  // end of the inner `for` loop

      if (pointToPutCaret.IsSet()) {
        nsresult rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret);
        if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
          NS_WARNING(
              "EditorBase::CollapseSelectionTo() caused destroying the editor");
          return Err(NS_ERROR_EDITOR_DESTROYED);
        }
        NS_WARNING_ASSERTION(
            NS_SUCCEEDED(rv),
            "EditorBase::CollapseSelectionTo() failed, but ignored");
      }
    }
    // If a list element on the clipboard, and pasting it into a list or
    // list item element, insert the appropriate children instead.  I.e.,
    // merge the list elements instead of pasting as a sublist.
    else if (HTMLEditUtils::IsAnyListElement(content) &&
             (HTMLEditUtils::IsAnyListElement(pointToInsert.GetContainer()) ||
              HTMLEditUtils::IsListItem(pointToInsert.GetContainer()))) {
      AutoTArray<OwningNonNull<nsIContent>, 24> children;
      HTMLEditUtils::CollectAllChildren(*content, children);
      EditorDOMPoint pointToPutCaret;
      for (const OwningNonNull<nsIContent>& child : children) {
        if (HTMLEditUtils::IsListItem(child) ||
            HTMLEditUtils::IsAnyListElement(child)) {
          // If we're pasting into empty list item, we should remove it
          // and past current node into the parent list directly.
          // XXX This creates invalid structure if current list item element
          //     is not proper child of the parent element, or current node
          //     is a list element.
          if (HTMLEditUtils::IsListItem(pointToInsert.GetContainer()) &&
              HTMLEditUtils::IsEmptyNode(
                  *pointToInsert.GetContainer(),
                  {EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
            NS_WARNING_ASSERTION(pointToInsert.GetContainerParent(),
                                 "Insertion point is out of the DOM tree");
            if (pointToInsert.GetContainerParent()) {
              pointToInsert.Set(pointToInsert.GetContainer());
              MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
              AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert);
              nsresult rv = mHTMLEditor.DeleteNodeWithTransaction(
                  MOZ_KnownLive(*pointToInsert.GetChild()));
              if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
                NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
                return Err(NS_ERROR_EDITOR_DESTROYED);
              }
              NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                                   "EditorBase::DeleteNodeWithTransaction() "
                                   "failed, but ignored");
            }
          }
          // MOZ_KnownLive(child) because of bug 1622253
          Result<CreateContentResult, nsresult> moveChildResult =
              mHTMLEditor
                  .InsertNodeIntoProperAncestorWithTransaction<nsIContent>(
                      MOZ_KnownLive(child), pointToInsert,
                      SplitAtEdges::eDoNotCreateEmptyContainer);
          if (MOZ_UNLIKELY(moveChildResult.isErr())) {
            // If moving node is moved to different place, we should ignore
            // this result and keep trying to insert next content node to
            // same position.
            if (moveChildResult.inspectErr() ==
                NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) {
              inserted = true;
              continue;  // the inner `for` loop
            }
            if (NS_WARN_IF(moveChildResult.inspectErr() ==
                           NS_ERROR_EDITOR_DESTROYED)) {
              return Err(NS_ERROR_EDITOR_DESTROYED);
            }
            NS_WARNING(
                "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
                "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but maybe "
                "ignored");
            break;  // from the inner `for` loop
          }
          if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) {
            continue;
          }
          inserted = true;
          lastInsertedPoint.Set(child);
          pointToInsert = lastInsertedPoint.NextPoint();
          MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
          CreateContentResult unwrappedMoveChildResult =
              moveChildResult.unwrap();
          unwrappedMoveChildResult.MoveCaretPointTo(
              pointToPutCaret, mHTMLEditor,
              {SuggestCaret::OnlyIfHasSuggestion,
               SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
        }
        // If the child of current node is not list item nor list element,
        // we should remove it from the DOM tree.
        else if (HTMLEditUtils::IsRemovableNode(child)) {
          AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert);
          IgnoredErrorResult ignoredError;
          content->RemoveChild(child, ignoredError);
          if (MOZ_UNLIKELY(mHTMLEditor.Destroyed())) {
            NS_WARNING(
                "nsIContent::RemoveChild() caused destroying the editor");
            return Err(NS_ERROR_EDITOR_DESTROYED);
          }
          NS_WARNING_ASSERTION(!ignoredError.Failed(),
                               "nsINode::RemoveChild() failed, but ignored");
        } else {
          NS_WARNING(
              "Failed to delete the first child of a list element because the "
              "list element non-editable");
          break;  // from the inner `for` loop
        }
      }  // end of the inner `for` loop

      if (MOZ_UNLIKELY(mHTMLEditor.Destroyed())) {
        NS_WARNING("The editor has been destroyed");
        return Err(NS_ERROR_EDITOR_DESTROYED);
      }
      if (pointToPutCaret.IsSet()) {
        nsresult rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret);
        if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
          NS_WARNING(
              "EditorBase::CollapseSelectionTo() caused destroying the editor");
          return Err(NS_ERROR_EDITOR_DESTROYED);
        }
        NS_WARNING_ASSERTION(
            NS_SUCCEEDED(rv),
            "EditorBase::CollapseSelectionTo() failed, but ignored");
      }
    }
    // If pasting into a `<pre>` element and current node is a `<pre>` element,
    // move only its children.
    else if (HTMLEditUtils::IsPre(maybeNonEditableBlockElement) &&
             HTMLEditUtils::IsPre(content)) {
      // Check for pre's going into pre's.
      AutoTArray<OwningNonNull<nsIContent>, 24> children;
      HTMLEditUtils::CollectAllChildren(*content, children);
      EditorDOMPoint pointToPutCaret;
      for (const OwningNonNull<nsIContent>& child : children) {
        // MOZ_KnownLive(child) because of bug 1622253
        Result<CreateContentResult, nsresult> moveChildResult =
            mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction<nsIContent>(
                MOZ_KnownLive(child), pointToInsert,
                SplitAtEdges::eDoNotCreateEmptyContainer);
        if (MOZ_UNLIKELY(moveChildResult.isErr())) {
          // If moving node is moved to different place, we should ignore
          // this result and keep trying to insert next content node there.
          if (moveChildResult.inspectErr() ==
              NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) {
            inserted = true;
            continue;  // the inner `for` loop
          }
          if (NS_WARN_IF(moveChildResult.inspectErr() ==
                         NS_ERROR_EDITOR_DESTROYED)) {
            return moveChildResult.propagateErr();
          }
          NS_WARNING(
              "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
              "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but maybe "
              "ignored");
          break;  // from the inner `for` loop
        }
        if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) {
          continue;
        }
        CreateContentResult unwrappedMoveChildResult = moveChildResult.unwrap();
        inserted = true;
        lastInsertedPoint.Set(child);
        pointToInsert = lastInsertedPoint.NextPoint();
        MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
        unwrappedMoveChildResult.MoveCaretPointTo(
            pointToPutCaret, mHTMLEditor,
            {SuggestCaret::OnlyIfHasSuggestion,
             SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
      }  // end of the inner `for` loop

      if (pointToPutCaret.IsSet()) {
        nsresult rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret);
        if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
          NS_WARNING(
              "EditorBase::CollapseSelectionTo() caused destroying the editor");
          return Err(NS_ERROR_EDITOR_DESTROYED);
        }
        NS_WARNING_ASSERTION(
            NS_SUCCEEDED(rv),
            "EditorBase::CollapseSelectionTo() failed, but ignored");
      }
    }

    // TODO: For making the above code clearer, we should move this fallback
    //       path into a lambda and call it in each if/else-if block.
    // If we haven't inserted current node nor its children, move current node
    // to the insertion point.
    if (!inserted) {
      // MOZ_KnownLive(content) because 'aArrayOfTopMostChildContents' is
      // guaranteed to keep it alive.
      Result<CreateContentResult, nsresult> moveContentResult =
          mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction<nsIContent>(
              MOZ_KnownLive(content), pointToInsert,
              SplitAtEdges::eDoNotCreateEmptyContainer);
      if (MOZ_LIKELY(moveContentResult.isOk())) {
        if (MOZ_UNLIKELY(!moveContentResult.inspect().Handled())) {
          continue;
        }
        lastInsertedPoint.Set(content);
        pointToInsert = lastInsertedPoint;
        MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
        nsresult rv = moveContentResult.inspect().SuggestCaretPointTo(
            mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
                          SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
                          SuggestCaret::AndIgnoreTrivialError});
        if (NS_FAILED(rv)) {
          NS_WARNING("CreateContentResult::SuggestCaretPointTo() failed");
          return Err(rv);
        }
        NS_WARNING_ASSERTION(
            rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
            "CreateContentResult::SuggestCaretPointTo() failed, but ignored");
      } else if (moveContentResult.inspectErr() ==
                 NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) {
        // Moving node is moved to different place, we should keep trying to
        // insert the next content to same position.
      } else {
        NS_WARNING(
            "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
            "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but ignored");
        // Assume failure means no legal parent in the document hierarchy,
        // try again with the parent of content in the paste hierarchy.
        // FYI: We cannot use `InclusiveAncestorOfType` here because of
        //      calling `InsertNodeIntoProperAncestorWithTransaction()`.
        for (nsCOMPtr<nsIContent> childContent = content; childContent;
             childContent = childContent->GetParent()) {
          if (NS_WARN_IF(!childContent->GetParent()) ||
              NS_WARN_IF(
                  childContent->GetParent()->IsHTMLElement(nsGkAtoms::body))) {
            break;  // for the inner `for` loop
          }
          const OwningNonNull<nsIContent> oldParentContent =
              *childContent->GetParent();
          Result<CreateContentResult, nsresult> moveParentResult =
              mHTMLEditor
                  .InsertNodeIntoProperAncestorWithTransaction<nsIContent>(
                      oldParentContent, pointToInsert,
                      SplitAtEdges::eDoNotCreateEmptyContainer);
          if (MOZ_UNLIKELY(moveParentResult.isErr())) {
            // Moving node is moved to different place, we should keep trying to
            // insert the next content to same position.
            if (moveParentResult.inspectErr() ==
                NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) {
              break;  // from the inner `for` loop
            }
            if (NS_WARN_IF(moveParentResult.inspectErr() ==
                           NS_ERROR_EDITOR_DESTROYED)) {
              return Err(NS_ERROR_EDITOR_DESTROYED);
            }
            NS_WARNING(
                "HTMLEditor::InsertNodeInToProperAncestorWithTransaction("
                "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but "
                "ignored");
            continue;  // the inner `for` loop
          }
          if (MOZ_UNLIKELY(!moveParentResult.inspect().Handled())) {
            continue;
          }
          insertedContextParentContent = oldParentContent;
          pointToInsert.Set(oldParentContent);
          MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
          nsresult rv = moveParentResult.inspect().SuggestCaretPointTo(
              mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
                            SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
                            SuggestCaret::AndIgnoreTrivialError});
          if (NS_FAILED(rv)) {
            NS_WARNING("CreateContentResult::SuggestCaretPointTo() failed");
            return Err(rv);
          }
          NS_WARNING_ASSERTION(
              rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
              "CreateContentResult::SuggestCaretPointTo() failed, but ignored");
          break;  // from the inner `for` loop
        }  // end of the inner `for` loop
      }
    }
    if (lastInsertedPoint.IsSet()) {
      if (MOZ_UNLIKELY(lastInsertedPoint.GetContainer() !=
                       lastInsertedPoint.GetChild()->GetParentNode())) {
        NS_WARNING(
            "HTMLEditor::InsertHTMLWithContextAsSubAction() got lost insertion "
            "point");
        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
      }
      pointToInsert = lastInsertedPoint.NextPoint();
      MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
    }
  }  // end of the `for` loop

  return lastInsertedPoint;
}

nsresult HTMLEditor::HTMLWithContextInserter::MoveCaretOutsideOfLink(
    Element& aLinkElement, const EditorDOMPoint& aPointToPutCaret) {
  MOZ_ASSERT(HTMLEditUtils::IsLink(&aLinkElement));

  // The reason why do that instead of just moving caret after it is, the
  // link might have ended in an invisible `<br>` element.  If so, the code
  // above just placed selection inside that.  So we need to split it instead.
  // XXX Sounds like that it's not really expensive comparing with the reason
  //     to use SplitNodeDeepWithTransaction() here.
  Result<SplitNodeResult, nsresult> splitLinkResult =
      mHTMLEditor.SplitNodeDeepWithTransaction(
          aLinkElement, aPointToPutCaret,
          SplitAtEdges::eDoNotCreateEmptyContainer);
  if (MOZ_UNLIKELY(splitLinkResult.isErr())) {
    if (splitLinkResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
      NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
      return NS_ERROR_EDITOR_DESTROYED;
    }
    NS_WARNING(
        "HTMLEditor::SplitNodeDeepWithTransaction() failed, but ignored");
  }

  if (nsIContent* previousContentOfSplitPoint =
          splitLinkResult.inspect().GetPreviousContent()) {
    splitLinkResult.inspect().IgnoreCaretPointSuggestion();
    nsresult rv = mHTMLEditor.CollapseSelectionTo(
        EditorRawDOMPoint::After(*previousContentOfSplitPoint));
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rv),
        "EditorBase::CollapseSelectionTo() failed, but ignored");
    return NS_OK;
  }

  nsresult rv = splitLinkResult.inspect().SuggestCaretPointTo(
      mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
                    SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "SplitNodeResult::SuggestCaretPointTo() failed");
  return rv;
}

// static
Element* HTMLEditor::GetLinkElement(nsINode* aNode) {
  if (NS_WARN_IF(!aNode)) {
    return nullptr;
  }
  nsINode* node = aNode;
  while (node) {
    if (HTMLEditUtils::IsLink(node)) {
      return node->AsElement();
    }
    node = node->GetParentNode();
  }
  return nullptr;
}

// static
nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
    RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
        nsIContent& aNode, NodesToRemove aNodesToRemove) {
  if (aNode.TextIsOnlyWhitespace()) {
    nsCOMPtr<nsINode> parent = aNode.GetParentNode();
    // TODO: presumably, if the parent is a `<pre>` element, the node
    // shouldn't be removed.
    if (parent) {
      if (aNodesToRemove == NodesToRemove::eAll ||
          HTMLEditUtils::IsAnyListElement(parent)) {
        ErrorResult error;
        parent->RemoveChild(aNode, error);
        NS_WARNING_ASSERTION(!error.Failed(), "nsINode::RemoveChild() failed");
        return error.StealNSResult();
      }
      return NS_OK;
    }
  }

  if (!aNode.IsHTMLElement(nsGkAtoms::pre)) {
    nsCOMPtr<nsIContent> child = aNode.GetLastChild();
    while (child) {
      nsCOMPtr<nsIContent> previous = child->GetPreviousSibling();
      nsresult rv = FragmentFromPasteCreator::
          RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
              *child, aNodesToRemove);
      if (NS_FAILED(rv)) {
        NS_WARNING(
            "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
            "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces"
            "() "
            "failed");
        return rv;
      }
      child = std::move(previous);
    }
  }
  return NS_OK;
}

class MOZ_STACK_CLASS HTMLEditor::HTMLTransferablePreparer {
 public:
  HTMLTransferablePreparer(const HTMLEditor& aHTMLEditor,
                           nsITransferable** aTransferable,
                           const Element* aEditingHost);

  nsresult Run();

 private:
  void AddDataFlavorsInBestOrder(nsITransferable& aTransferable) const;

  const HTMLEditor& mHTMLEditor;
  const Element* const mEditingHost;
  nsITransferable** mTransferable;
};

HTMLEditor::HTMLTransferablePreparer::HTMLTransferablePreparer(
    const HTMLEditor& aHTMLEditor, nsITransferable** aTransferable,
    const Element* aEditingHost)
    : mHTMLEditor{aHTMLEditor},
      mEditingHost(aEditingHost),
      mTransferable{aTransferable} {
  MOZ_ASSERT(mTransferable);
  MOZ_ASSERT(!*mTransferable);
}

nsresult HTMLEditor::PrepareHTMLTransferable(
    nsITransferable** aTransferable, const Element* aEditingHost) const {
  HTMLTransferablePreparer htmlTransferablePreparer{*this, aTransferable,
                                                    aEditingHost};
  return htmlTransferablePreparer.Run();
}

nsresult HTMLEditor::HTMLTransferablePreparer::Run() {
  // Create generic Transferable for getting the data
  nsresult rv;
  RefPtr<nsITransferable> transferable =
      do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
  if (NS_FAILED(rv)) {
    NS_WARNING("do_CreateInstance() failed to create nsITransferable instance");
    return rv;
  }

  if (!transferable) {
    NS_WARNING("do_CreateInstance() returned nullptr, but ignored");
    return NS_OK;
  }

  // Get the nsITransferable interface for getting the data from the clipboard
  RefPtr<Document> destdoc = mHTMLEditor.GetDocument();
  nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
  DebugOnly<nsresult> rvIgnored = transferable->Init(loadContext);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                       "nsITransferable::Init() failed, but ignored");

  // See `HTMLEditor::InsertFromTransferableAtSelection`.
  AddDataFlavorsInBestOrder(*transferable);

  transferable.forget(mTransferable);

  return NS_OK;
}

void HTMLEditor::HTMLTransferablePreparer::AddDataFlavorsInBestOrder(
    nsITransferable& aTransferable) const {
  // Create the desired DataFlavor for the type of data
  // we want to get out of the transferable
  // This should only happen in html editors, not plaintext
  // Note that if you add more flavors here you will need to add them
  // to DataTransfer::GetExternalClipboardFormats as well.
  if (!mHTMLEditor.IsPlaintextMailComposer() &&
      !(mEditingHost && mEditingHost->IsContentEditablePlainTextOnly())) {
    DebugOnly<nsresult> rvIgnored =
        aTransferable.AddDataFlavor(kNativeHTMLMime);
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rvIgnored),
        "nsITransferable::AddDataFlavor(kNativeHTMLMime) failed, but ignored");
    rvIgnored = aTransferable.AddDataFlavor(kHTMLMime);
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rvIgnored),
        "nsITransferable::AddDataFlavor(kHTMLMime) failed, but ignored");
    rvIgnored = aTransferable.AddDataFlavor(kFileMime);
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rvIgnored),
        "nsITransferable::AddDataFlavor(kFileMime) failed, but ignored");

    switch (Preferences::GetInt("clipboard.paste_image_type", 1)) {
      case 0:  // prefer JPEG over PNG over GIF encoding
        rvIgnored = aTransferable.AddDataFlavor(kJPEGImageMime);
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                             "nsITransferable::AddDataFlavor(kJPEGImageMime) "
                             "failed, but ignored");
        rvIgnored = aTransferable.AddDataFlavor(kJPGImageMime);
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                             "nsITransferable::AddDataFlavor(kJPGImageMime) "
                             "failed, but ignored");
        rvIgnored = aTransferable.AddDataFlavor(kPNGImageMime);
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                             "nsITransferable::AddDataFlavor(kPNGImageMime) "
                             "failed, but ignored");
        rvIgnored = aTransferable.AddDataFlavor(kGIFImageMime);
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                             "nsITransferable::AddDataFlavor(kGIFImageMime) "
                             "failed, but ignored");
        break;
      case 1:  // prefer PNG over JPEG over GIF encoding (default)
      default:
        rvIgnored = aTransferable.AddDataFlavor(kPNGImageMime);
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                             "nsITransferable::AddDataFlavor(kPNGImageMime) "
                             "failed, but ignored");
        rvIgnored = aTransferable.AddDataFlavor(kJPEGImageMime);
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                             "nsITransferable::AddDataFlavor(kJPEGImageMime) "
                             "failed, but ignored");
        rvIgnored = aTransferable.AddDataFlavor(kJPGImageMime);
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                             "nsITransferable::AddDataFlavor(kJPGImageMime) "
                             "failed, but ignored");
        rvIgnored = aTransferable.AddDataFlavor(kGIFImageMime);
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                             "nsITransferable::AddDataFlavor(kGIFImageMime) "
                             "failed, but ignored");
        break;
      case 2:  // prefer GIF over JPEG over PNG encoding
        rvIgnored = aTransferable.AddDataFlavor(kGIFImageMime);
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                             "nsITransferable::AddDataFlavor(kGIFImageMime) "
                             "failed, but ignored");
        rvIgnored = aTransferable.AddDataFlavor(kJPEGImageMime);
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                             "nsITransferable::AddDataFlavor(kJPEGImageMime) "
                             "failed, but ignored");
        rvIgnored = aTransferable.AddDataFlavor(kJPGImageMime);
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                             "nsITransferable::AddDataFlavor(kJPGImageMime) "
                             "failed, but ignored");
        rvIgnored = aTransferable.AddDataFlavor(kPNGImageMime);
        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                             "nsITransferable::AddDataFlavor(kPNGImageMime) "
                             "failed, but ignored");
        break;
    }
  }
  DebugOnly<nsresult> rvIgnored = aTransferable.AddDataFlavor(kTextMime);
  NS_WARNING_ASSERTION(
      NS_SUCCEEDED(rvIgnored),
      "nsITransferable::AddDataFlavor(kTextMime) failed, but ignored");
  rvIgnored = aTransferable.AddDataFlavor(kMozTextInternal);
  NS_WARNING_ASSERTION(
      NS_SUCCEEDED(rvIgnored),
      "nsITransferable::AddDataFlavor(kMozTextInternal) failed, but ignored");
}

bool FindIntegerAfterString(const char* aLeadingString, const nsCString& aCStr,
                            int32_t& foundNumber) {
  // first obtain offsets from cfhtml str
  int32_t numFront = aCStr.Find(aLeadingString);
  if (numFront == -1) {
    return false;
  }
  numFront += strlen(aLeadingString);

  int32_t numBack = aCStr.FindCharInSet(CRLF, numFront);
  if (numBack == -1) {
    return false;
  }

  nsAutoCString numStr(Substring(aCStr, numFront, numBack - numFront));
  nsresult errorCode;
  foundNumber = numStr.ToInteger(&errorCode);
  return true;
}

void RemoveFragComments(nsCString& aStr) {
  // remove the StartFragment/EndFragment comments from the str, if present
  int32_t startCommentIndx = aStr.Find("<!--StartFragment");
  if (startCommentIndx >= 0) {
    int32_t startCommentEnd = aStr.Find("-->", startCommentIndx);
    if (startCommentEnd > startCommentIndx) {
      aStr.Cut(startCommentIndx, (startCommentEnd + 3) - startCommentIndx);
    }
  }
  int32_t endCommentIndx = aStr.Find("<!--EndFragment");
  if (endCommentIndx >= 0) {
    int32_t endCommentEnd = aStr.Find("-->", endCommentIndx);
    if (endCommentEnd > endCommentIndx) {
      aStr.Cut(endCommentIndx, (endCommentEnd + 3) - endCommentIndx);
    }
  }
}

nsresult HTMLEditor::ParseCFHTML(const nsCString& aCfhtml,
                                 char16_t** aStuffToPaste,
                                 char16_t** aCfcontext) {
  // First obtain offsets from cfhtml str.
  int32_t startHTML, endHTML, startFragment, endFragment;
  if (!FindIntegerAfterString("StartHTML:", aCfhtml, startHTML) ||
      startHTML < -1) {
    return NS_ERROR_FAILURE;
  }
  if (!FindIntegerAfterString("EndHTML:", aCfhtml, endHTML) || endHTML < -1) {
    return NS_ERROR_FAILURE;
  }
  if (!FindIntegerAfterString("StartFragment:", aCfhtml, startFragment) ||
      startFragment < 0) {
    return NS_ERROR_FAILURE;
  }
  if (!FindIntegerAfterString("EndFragment:", aCfhtml, endFragment) ||
      startFragment < 0) {
    return NS_ERROR_FAILURE;
  }

  // The StartHTML and EndHTML markers are allowed to be -1 to include
  // everything.
  //   See Reference: MSDN doc entitled "HTML Clipboard Format"
  //   http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
  if (startHTML == -1) {
    startHTML = aCfhtml.Find("<!--StartFragment-->");
    if (startHTML == -1) {
      return NS_OK;
    }
  }
  if (endHTML == -1) {
    const char endFragmentMarker[] = "<!--EndFragment-->";
    endHTML = aCfhtml.Find(endFragmentMarker);
    if (endHTML == -1) {
      return NS_OK;
    }
    endHTML += std::size(endFragmentMarker) - 1;
  }

  // create context string
  nsAutoCString contextUTF8(
      Substring(aCfhtml, startHTML, startFragment - startHTML) +
      "<!--" kInsertCookie "-->"_ns +
      Substring(aCfhtml, endFragment, endHTML - endFragment));

  // validate startFragment
  // make sure it's not in the middle of a HTML tag
  // see bug #228879 for more details
  int32_t curPos = startFragment;
  while (curPos > startHTML) {
    if (aCfhtml[curPos] == '>') {
      // working backwards, the first thing we see is the end of a tag
      // so StartFragment is good, so do nothing.
      break;
    }
    if (aCfhtml[curPos] == '<') {
      // if we are at the start, then we want to see the '<'
      if (curPos != startFragment) {
        // working backwards, the first thing we see is the start of a tag
        // so StartFragment is bad, so we need to update it.
        NS_ERROR(
            "StartFragment byte count in the clipboard looks bad, see bug "
            "#228879");
        startFragment = curPos - 1;
      }
      break;
    }
    curPos--;
  }

  // create fragment string
  nsAutoCString fragmentUTF8(
      Substring(aCfhtml, startFragment, endFragment - startFragment));

  // remove the StartFragment/EndFragment comments from the fragment, if present
  RemoveFragComments(fragmentUTF8);

  // remove the StartFragment/EndFragment comments from the context, if present
  RemoveFragComments(contextUTF8);

  // convert both strings to usc2
  const nsString& fragUcs2Str = NS_ConvertUTF8toUTF16(fragmentUTF8);
  const nsString& cntxtUcs2Str = NS_ConvertUTF8toUTF16(contextUTF8);

  // translate platform linebreaks for fragment
  int32_t oldLengthInChars =
      fragUcs2Str.Length() + 1;  // +1 to include null terminator
  int32_t newLengthInChars = 0;
  *aStuffToPaste = nsLinebreakConverter::ConvertUnicharLineBreaks(
      fragUcs2Str.get(), nsLinebreakConverter::eLinebreakAny,
      nsLinebreakConverter::eLinebreakContent, oldLengthInChars,
      &newLengthInChars);
  if (!*aStuffToPaste) {
    NS_WARNING("nsLinebreakConverter::ConvertUnicharLineBreaks() failed");
    return NS_ERROR_FAILURE;
  }

  // translate platform linebreaks for context
  oldLengthInChars =
      cntxtUcs2Str.Length() + 1;  // +1 to include null terminator
  newLengthInChars = 0;
  *aCfcontext = nsLinebreakConverter::ConvertUnicharLineBreaks(
      cntxtUcs2Str.get(), nsLinebreakConverter::eLinebreakAny,
      nsLinebreakConverter::eLinebreakContent, oldLengthInChars,
      &newLengthInChars);
  // it's ok for context to be empty.  frag might be whole doc and contain all
  // its context.

  // we're done!
  return NS_OK;
}

static nsresult ImgFromData(const nsACString& aType, const nsACString& aData,
                            nsString& aOutput) {
  aOutput.AssignLiteral("<IMG src=\"data:");
  AppendUTF8toUTF16(aType, aOutput);
  aOutput.AppendLiteral(";base64,");
  nsresult rv = Base64EncodeAppend(aData, aOutput);
  if (NS_FAILED(rv)) {
    NS_WARNING("Base64Encode() failed");
    return rv;
  }
  aOutput.AppendLiteral("\" alt=\"\" >");
  return NS_OK;
}

NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLEditor::BlobReader)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HTMLEditor::BlobReader)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mBlob)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mHTMLEditor)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPointToInsert)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HTMLEditor::BlobReader)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBlob)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHTMLEditor)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPointToInsert)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

HTMLEditor::BlobReader::BlobReader(BlobImpl* aBlob, HTMLEditor* aHTMLEditor,
                                   SafeToInsertData aSafeToInsertData,
                                   const EditorDOMPoint& aPointToInsert,
                                   DeleteSelectedContent aDeleteSelectedContent,
                                   const Element& aEditingHost)
    : mBlob(aBlob),
      mHTMLEditor(aHTMLEditor),
      mEditingHost(&aEditingHost),
      // "beforeinput" event should've been dispatched before we read blob,
      // but anyway, we need to clone dataTransfer for "input" event.
      mDataTransfer(mHTMLEditor->GetInputEventDataTransfer()),
      mPointToInsert(aPointToInsert),
      mEditAction(aHTMLEditor->GetEditAction()),
      mSafeToInsertData(aSafeToInsertData),
      mDeleteSelectedContent(aDeleteSelectedContent),
      mNeedsToDispatchBeforeInputEvent(
          !mHTMLEditor->HasTriedToDispatchBeforeInputEvent()) {
  MOZ_ASSERT(mBlob);
  MOZ_ASSERT(mHTMLEditor);
  MOZ_ASSERT(mHTMLEditor->IsEditActionDataAvailable());
  MOZ_ASSERT(mDataTransfer);

  // Take only offset here since it's our traditional behavior.
  if (mPointToInsert.IsSet()) {
    AutoEditorDOMPointChildInvalidator storeOnlyWithOffset(mPointToInsert);
  }
}

nsresult HTMLEditor::BlobReader::OnResult(const nsACString& aResult) {
  if (NS_WARN_IF(!mEditingHost)) {
    return NS_ERROR_FAILURE;
  }
  AutoEditActionDataSetter editActionData(*mHTMLEditor, mEditAction);
  editActionData.InitializeDataTransfer(mDataTransfer);
  if (NS_WARN_IF(!editActionData.CanHandle())) {
    return NS_ERROR_FAILURE;
  }

  if (NS_WARN_IF(mNeedsToDispatchBeforeInputEvent)) {
    nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
    if (NS_FAILED(rv)) {
      NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                           "MaybeDispatchBeforeInputEvent(), failed");
      return EditorBase::ToGenericNSResult(rv);
    }
  } else {
    editActionData.MarkAsBeforeInputHasBeenDispatched();
  }

  nsString blobType;
  mBlob->GetType(blobType);

  // TODO: This does not work well.
  // * If the data is not an image file, this inserts <img> element with odd
  //   data URI (bug 1610220).
  // * If the data is valid image file data, an <img> file is inserted with
  //   data URI, but it's not loaded (bug 1610219).
  NS_ConvertUTF16toUTF8 type(blobType);
  nsAutoString stuffToPaste;
  nsresult rv = ImgFromData(type, aResult, stuffToPaste);
  if (NS_FAILED(rv)) {
    NS_WARNING("ImgFormData() failed");
    return EditorBase::ToGenericNSResult(rv);
  }

  RefPtr<HTMLEditor> htmlEditor = std::move(mHTMLEditor);
  AutoPlaceholderBatch treatAsOneTransaction(
      *htmlEditor, ScrollSelectionIntoView::Yes, __FUNCTION__);
  EditorDOMPoint pointToInsert = std::move(mPointToInsert);
  const RefPtr<const Element> editingHost = std::move(mEditingHost);
  rv = htmlEditor->InsertHTMLWithContextAsSubAction(
      stuffToPaste, u""_ns, u""_ns, NS_LITERAL_STRING_FROM_CSTRING(kFileMime),
      mSafeToInsertData, pointToInsert, mDeleteSelectedContent,
      InlineStylesAtInsertionPoint::Preserve, *editingHost);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "HTMLEditor::InsertHTMLWithContextAsSubAction("
                       "InlineStylesAtInsertionPoint::Preserve) failed");
  return EditorBase::ToGenericNSResult(rv);
}

nsresult HTMLEditor::BlobReader::OnError(const nsAString& aError) {
  AutoTArray<nsString, 1> error;
  error.AppendElement(aError);
  nsContentUtils::ReportToConsole(
      nsIScriptError::warningFlag, "Editor"_ns, mHTMLEditor->GetDocument(),
      nsContentUtils::eDOM_PROPERTIES, "EditorFileDropFailed", error);
  return NS_OK;
}

class SlurpBlobEventListener final : public nsIDOMEventListener {
 public:
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_CLASS(SlurpBlobEventListener)

  explicit SlurpBlobEventListener(HTMLEditor::BlobReader* aListener)
      : mListener(aListener) {}

  MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override;

 private:
  ~SlurpBlobEventListener() = default;

  RefPtr<HTMLEditor::BlobReader> mListener;
};

NS_IMPL_CYCLE_COLLECTION(SlurpBlobEventListener, mListener)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SlurpBlobEventListener)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(SlurpBlobEventListener)
NS_IMPL_CYCLE_COLLECTING_RELEASE(SlurpBlobEventListener)

NS_IMETHODIMP SlurpBlobEventListener::HandleEvent(Event* aEvent) {
  EventTarget* target = aEvent->GetTarget();
  if (!target || !mListener) {
    return NS_OK;
  }

  RefPtr<FileReader> reader = do_QueryObject(target);
  if (!reader) {
    return NS_OK;
  }

  EventMessage message = aEvent->WidgetEventPtr()->mMessage;

  RefPtr<HTMLEditor::BlobReader> listener(mListener);
  if (message == eLoad) {
    MOZ_ASSERT(reader->DataFormat() == FileReader::FILE_AS_BINARY);

    // The original data has been converted from Latin1 to UTF-16, this just
    // undoes that conversion.
    DebugOnly<nsresult> rvIgnored =
        listener->OnResult(NS_LossyConvertUTF16toASCII(reader->Result()));
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rvIgnored),
        "HTMLEditor::BlobReader::OnResult() failed, but ignored");
    return NS_OK;
  }

  if (message == eLoadError) {
    nsAutoString errorMessage;
    reader->GetError()->GetErrorMessage(errorMessage);
    DebugOnly<nsresult> rvIgnored = listener->OnError(errorMessage);
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rvIgnored),
        "HTMLEditor::BlobReader::OnError() failed, but ignored");
    return NS_OK;
  }

  return NS_OK;
}

// static
nsresult HTMLEditor::SlurpBlob(Blob* aBlob, nsIGlobalObject* aGlobal,
                               BlobReader* aBlobReader) {
  MOZ_ASSERT(aBlob);
  MOZ_ASSERT(aGlobal);
  MOZ_ASSERT(aBlobReader);

  RefPtr<WeakWorkerRef> workerRef;
  RefPtr<FileReader> reader = new FileReader(aGlobal, workerRef);

  RefPtr<SlurpBlobEventListener> eventListener =
      new SlurpBlobEventListener(aBlobReader);

  nsresult rv = reader->AddEventListener(u"load"_ns, eventListener, false);
  if (NS_FAILED(rv)) {
    NS_WARNING("FileReader::AddEventListener(load) failed");
    return rv;
  }

  rv = reader->AddEventListener(u"error"_ns, eventListener, false);
  if (NS_FAILED(rv)) {
    NS_WARNING("FileReader::AddEventListener(error) failed");
    return rv;
  }

  ErrorResult error;
  reader->ReadAsBinaryString(*aBlob, error);
  NS_WARNING_ASSERTION(!error.Failed(),
                       "FileReader::ReadAsBinaryString() failed");
  return error.StealNSResult();
}

nsresult HTMLEditor::InsertObject(const nsACString& aType, nsISupports* aObject,
                                  SafeToInsertData aSafeToInsertData,
                                  const EditorDOMPoint& aPointToInsert,
                                  DeleteSelectedContent aDeleteSelectedContent,
                                  const Element& aEditingHost) {
  MOZ_ASSERT(IsEditActionDataAvailable());

  // Check to see if we the file is actually an image.
  nsAutoCString type(aType);
  if (type.EqualsLiteral(kFileMime)) {
    if (nsCOMPtr<nsIFile> file = do_QueryInterface(aObject)) {
      nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1");
      if (NS_WARN_IF(!mime)) {
        return NS_ERROR_FAILURE;
      }

      nsresult rv = mime->GetTypeFromFile(file, type);
      if (NS_FAILED(rv)) {
        NS_WARNING("nsIMIMEService::GetTypeFromFile() failed");
        return rv;
      }
    }
  }

  nsCOMPtr<nsISupports> object = aObject;
  if (type.EqualsLiteral(kJPEGImageMime) || type.EqualsLiteral(kJPGImageMime) ||
      type.EqualsLiteral(kPNGImageMime) || type.EqualsLiteral(kGIFImageMime)) {
    if (nsCOMPtr<nsIFile> file = do_QueryInterface(object)) {
      object = new FileBlobImpl(file);
      // Fallthrough to BlobImpl code below.
    } else if (RefPtr<Blob> blob = do_QueryObject(object)) {
      object = blob->Impl();
      // Fallthrough.
    } else if (nsCOMPtr<nsIInputStream> imageStream =
                   do_QueryInterface(object)) {
      nsCString imageData;
      nsresult rv = NS_ConsumeStream(imageStream, UINT32_MAX, imageData);
      if (NS_FAILED(rv)) {
        NS_WARNING("NS_ConsumeStream() failed");
        return rv;
      }

      rv = imageStream->Close();
      if (NS_FAILED(rv)) {
        NS_WARNING("nsIInputStream::Close() failed");
        return rv;
      }

      nsAutoString stuffToPaste;
      rv = ImgFromData(type, imageData, stuffToPaste);
      if (NS_FAILED(rv)) {
        NS_WARNING("ImgFromData() failed");
        return rv;
      }

      AutoPlaceholderBatch treatAsOneTransaction(
          *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
      rv = InsertHTMLWithContextAsSubAction(
          stuffToPaste, u""_ns, u""_ns,
          NS_LITERAL_STRING_FROM_CSTRING(kFileMime), aSafeToInsertData,
          aPointToInsert, aDeleteSelectedContent,
          InlineStylesAtInsertionPoint::Preserve, aEditingHost);
      NS_WARNING_ASSERTION(
          NS_SUCCEEDED(rv),
          "HTMLEditor::InsertHTMLWithContextAsSubAction("
          "InlineStylesAtInsertionPoint::Preserve) failed, but ignored");
      return NS_OK;
    } else {
      NS_WARNING("HTMLEditor::InsertObject: Unexpected type for image mime");
      return NS_OK;
    }
  }

  // We always try to insert BlobImpl even without a known image mime.
  nsCOMPtr<BlobImpl> blob = do_QueryInterface(object);
  if (!blob) {
    return NS_OK;
  }

  RefPtr<BlobReader> br =
      new BlobReader(blob, this, aSafeToInsertData, aPointToInsert,
                     aDeleteSelectedContent, aEditingHost);

  nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(inner);
  if (!global) {
    NS_WARNING("Could not get global");
    return NS_ERROR_FAILURE;
  }

  RefPtr<Blob> domBlob = Blob::Create(global, blob);
  if (!domBlob) {
    NS_WARNING("Blob::Create() failed");
    return NS_ERROR_FAILURE;
  }

  nsresult rv = SlurpBlob(domBlob, global, br);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::::SlurpBlob() failed");
  return rv;
}

static bool GetString(nsISupports* aData, nsAString& aText) {
  if (nsCOMPtr<nsISupportsString> str = do_QueryInterface(aData)) {
    DebugOnly<nsresult> rvIgnored = str->GetData(aText);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                         "nsISupportsString::GetData() failed, but ignored");
    return !aText.IsEmpty();
  }

  return false;
}

static bool GetCString(nsISupports* aData, nsACString& aText) {
  if (nsCOMPtr<nsISupportsCString> str = do_QueryInterface(aData)) {
    DebugOnly<nsresult> rvIgnored = str->GetData(aText);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                         "nsISupportsString::GetData() failed, but ignored");
    return !aText.IsEmpty();
  }

  return false;
}

nsresult HTMLEditor::InsertFromTransferableAtSelection(
    nsITransferable* aTransferable, const nsAString& aContextStr,
    const nsAString& aInfoStr, HavePrivateHTMLFlavor aHavePrivateHTMLFlavor,
    const Element& aEditingHost) {
  nsAutoCString bestFlavor;
  nsCOMPtr<nsISupports> genericDataObj;

  // See `HTMLTransferablePreparer::AddDataFlavorsInBestOrder`.
  nsresult rv = aTransferable->GetAnyTransferData(
      bestFlavor, getter_AddRefs(genericDataObj));
  NS_WARNING_ASSERTION(
      NS_SUCCEEDED(rv),
      "nsITransferable::GetAnyTransferData() failed, but ignored");
  if (NS_SUCCEEDED(rv)) {
    AutoTransactionsConserveSelection dontChangeMySelection(*this);
    nsAutoString flavor;
    CopyASCIItoUTF16(bestFlavor, flavor);
    const SafeToInsertData safeToInsertData = IsSafeToInsertData(nullptr);

    if (bestFlavor.EqualsLiteral(kFileMime) ||
        bestFlavor.EqualsLiteral(kJPEGImageMime) ||
        bestFlavor.EqualsLiteral(kJPGImageMime) ||
        bestFlavor.EqualsLiteral(kPNGImageMime) ||
        bestFlavor.EqualsLiteral(kGIFImageMime)) {
      nsresult rv = InsertObject(bestFlavor, genericDataObj, safeToInsertData,
                                 EditorDOMPoint(), DeleteSelectedContent::Yes,
                                 aEditingHost);
      if (NS_FAILED(rv)) {
        NS_WARNING("HTMLEditor::InsertObject() failed");
        return rv;
      }
    } else if (bestFlavor.EqualsLiteral(kNativeHTMLMime)) {
      // note cf_html uses utf8
      nsAutoCString cfhtml;
      if (GetCString(genericDataObj, cfhtml)) {
        // cfselection left emtpy for now.
        nsString cfcontext, cffragment, cfselection;
        nsresult rv = ParseCFHTML(cfhtml, getter_Copies(cffragment),
                                  getter_Copies(cfcontext));
        if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty()) {
          // If we have our private HTML flavor, we will only use the fragment
          // from the CF_HTML. The rest comes from the clipboard.
          if (aHavePrivateHTMLFlavor == HavePrivateHTMLFlavor::Yes) {
            AutoPlaceholderBatch treatAsOneTransaction(
                *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
            rv = InsertHTMLWithContextAsSubAction(
                cffragment, aContextStr, aInfoStr, flavor, safeToInsertData,
                EditorDOMPoint(), DeleteSelectedContent::Yes,
                InlineStylesAtInsertionPoint::Clear, aEditingHost);
            if (NS_FAILED(rv)) {
              NS_WARNING(
                  "HTMLEditor::InsertHTMLWithContextAsSubAction("
                  "DeleteSelectedContent::Yes, "
                  "InlineStylesAtInsertionPoint::Clear) failed");
              return rv;
            }
          } else {
            AutoPlaceholderBatch treatAsOneTransaction(
                *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
            rv = InsertHTMLWithContextAsSubAction(
                cffragment, cfcontext, cfselection, flavor, safeToInsertData,
                EditorDOMPoint(), DeleteSelectedContent::Yes,
                InlineStylesAtInsertionPoint::Clear, aEditingHost);
            if (NS_FAILED(rv)) {
              NS_WARNING(
                  "HTMLEditor::InsertHTMLWithContextAsSubAction("
                  "DeleteSelectedContent::Yes, "
                  "InlineStylesAtInsertionPoint::Clear) failed");
              return rv;
            }
          }
        } else {
          // In some platforms (like Linux), the clipboard might return data
          // requested for unknown flavors (for example:
          // application/x-moz-nativehtml).  In this case, treat the data
          // to be pasted as mere HTML to get the best chance of pasting it
          // correctly.
          bestFlavor.AssignLiteral(kHTMLMime);
          // Fall through the next case
        }
      }
    }
    if (bestFlavor.EqualsLiteral(kHTMLMime) ||
        bestFlavor.EqualsLiteral(kTextMime) ||
        bestFlavor.EqualsLiteral(kMozTextInternal)) {
      nsAutoString stuffToPaste;
      if (!GetString(genericDataObj, stuffToPaste)) {
        nsAutoCString text;
        if (GetCString(genericDataObj, text)) {
          CopyUTF8toUTF16(text, stuffToPaste);
        }
      }

      if (!stuffToPaste.IsEmpty()) {
        if (bestFlavor.EqualsLiteral(kHTMLMime)) {
          AutoPlaceholderBatch treatAsOneTransaction(
              *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
          nsresult rv = InsertHTMLWithContextAsSubAction(
              stuffToPaste, aContextStr, aInfoStr, flavor, safeToInsertData,
              EditorDOMPoint(), DeleteSelectedContent::Yes,
              InlineStylesAtInsertionPoint::Clear, aEditingHost);
          if (NS_FAILED(rv)) {
            NS_WARNING(
                "HTMLEditor::InsertHTMLWithContextAsSubAction("
                "DeleteSelectedContent::Yes, "
                "InlineStylesAtInsertionPoint::Clear) failed");
            return rv;
          }
        } else {
          AutoPlaceholderBatch treatAsOneTransaction(
              *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
          nsresult rv =
              InsertTextAsSubAction(stuffToPaste, SelectionHandling::Delete);
          if (NS_FAILED(rv)) {
            NS_WARNING("EditorBase::InsertTextAsSubAction() failed");
            return rv;
          }
        }
      }
    }
  }

  // Try to scroll the selection into view if the paste succeeded
  DebugOnly<nsresult> rvIgnored = ScrollSelectionFocusIntoView();
  NS_WARNING_ASSERTION(
      NS_SUCCEEDED(rvIgnored),
      "EditorBase::ScrollSelectionFocusIntoView() failed, but ignored");
  return NS_OK;
}

static void GetStringFromDataTransfer(const DataTransfer* aDataTransfer,
                                      const nsAString& aType, uint32_t aIndex,
                                      nsString& aOutputString) {
  nsCOMPtr<nsIVariant> variant;
  DebugOnly<nsresult> rvIgnored = aDataTransfer->GetDataAtNoSecurityCheck(
      aType, aIndex, getter_AddRefs(variant));
  if (!variant) {
    MOZ_ASSERT(aOutputString.IsEmpty());
    return;
  }
  NS_WARNING_ASSERTION(
      NS_SUCCEEDED(rvIgnored),
      "DataTransfer::GetDataAtNoSecurityCheck() failed, but ignored");
  variant->GetAsAString(aOutputString);
  nsContentUtils::PlatformToDOMLineBreaks(aOutputString);
}

nsresult HTMLEditor::InsertFromDataTransfer(
    const DataTransfer* aDataTransfer, uint32_t aIndex,
    nsIPrincipal* aSourcePrincipal, const EditorDOMPoint& aDroppedAt,
    DeleteSelectedContent aDeleteSelectedContent, const Element& aEditingHost) {
  MOZ_ASSERT(GetEditAction() == EditAction::eDrop ||
             GetEditAction() == EditAction::ePaste);
  MOZ_ASSERT(mPlaceholderBatch,
             "HTMLEditor::InsertFromDataTransfer() should be called by "
             "HandleDropEvent() or paste action and there should've already "
             "been placeholder transaction");
  MOZ_ASSERT_IF(GetEditAction() == EditAction::eDrop, aDroppedAt.IsSet());

  ErrorResult error;
  RefPtr<DOMStringList> types = aDataTransfer->MozTypesAt(aIndex, error);
  if (error.Failed()) {
    NS_WARNING("DataTransfer::MozTypesAt() failed");
    return error.StealNSResult();
  }

  const bool hasPrivateHTMLFlavor =
      types->Contains(NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext));

  const bool isPlaintextEditor = IsPlaintextMailComposer() ||
                                 aEditingHost.IsContentEditablePlainTextOnly();
  const SafeToInsertData safeToInsertData =
      IsSafeToInsertData(aSourcePrincipal);

  uint32_t length = types->Length();
  for (uint32_t i = 0; i < length; i++) {
    nsAutoString type;
    types->Item(i, type);

    if (!isPlaintextEditor) {
      if (type.EqualsLiteral(kFileMime) || type.EqualsLiteral(kJPEGImageMime) ||
          type.EqualsLiteral(kJPGImageMime) ||
          type.EqualsLiteral(kPNGImageMime) ||
          type.EqualsLiteral(kGIFImageMime)) {
        nsCOMPtr<nsIVariant> variant;
        DebugOnly<nsresult> rvIgnored = aDataTransfer->GetDataAtNoSecurityCheck(
            type, aIndex, getter_AddRefs(variant));
        if (variant) {
          NS_WARNING_ASSERTION(
              NS_SUCCEEDED(rvIgnored),
              "DataTransfer::GetDataAtNoSecurityCheck() failed, but ignored");
          nsCOMPtr<nsISupports> object;
          rvIgnored = variant->GetAsISupports(getter_AddRefs(object));
          NS_WARNING_ASSERTION(
              NS_SUCCEEDED(rvIgnored),
              "nsIVariant::GetAsISupports() failed, but ignored");
          nsresult rv = InsertObject(NS_ConvertUTF16toUTF8(type), object,
                                     safeToInsertData, aDroppedAt,
                                     aDeleteSelectedContent, aEditingHost);
          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                               "HTMLEditor::InsertObject() failed");
          return rv;
        }
      } else if (type.EqualsLiteral(kNativeHTMLMime)) {
        // Windows only clipboard parsing.
        nsAutoString text;
        GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
        NS_ConvertUTF16toUTF8 cfhtml(text);

        nsString cfcontext, cffragment,
            cfselection;  // cfselection left emtpy for now

        nsresult rv = ParseCFHTML(cfhtml, getter_Copies(cffragment),
                                  getter_Copies(cfcontext));
        if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty()) {
          if (hasPrivateHTMLFlavor) {
            // If we have our private HTML flavor, we will only use the fragment
            // from the CF_HTML. The rest comes from the clipboard.
            nsAutoString contextString, infoString;
            GetStringFromDataTransfer(
                aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext),
                aIndex, contextString);
            GetStringFromDataTransfer(aDataTransfer,
                                      NS_LITERAL_STRING_FROM_CSTRING(kHTMLInfo),
                                      aIndex, infoString);
            AutoPlaceholderBatch treatAsOneTransaction(
                *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
            nsresult rv = InsertHTMLWithContextAsSubAction(
                cffragment, contextString, infoString, type, safeToInsertData,
                aDroppedAt, aDeleteSelectedContent,
                InlineStylesAtInsertionPoint::Clear, aEditingHost);
            NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                                 "HTMLEditor::InsertHTMLWithContextAsSubAction("
                                 "InlineStylesAtInsertionPoint::Clear) failed");
            return rv;
          }
          AutoPlaceholderBatch treatAsOneTransaction(
              *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
          nsresult rv = InsertHTMLWithContextAsSubAction(
              cffragment, cfcontext, cfselection, type, safeToInsertData,
              aDroppedAt, aDeleteSelectedContent,
              InlineStylesAtInsertionPoint::Clear, aEditingHost);
          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                               "HTMLEditor::InsertHTMLWithContextAsSubAction("
                               "InlineStylesAtInsertionPoint::Clear) failed");
          return rv;
        }
      } else if (type.EqualsLiteral(kHTMLMime)) {
        nsAutoString text, contextString, infoString;
        GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
        GetStringFromDataTransfer(aDataTransfer,
                                  NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext),
                                  aIndex, contextString);
        GetStringFromDataTransfer(aDataTransfer,
                                  NS_LITERAL_STRING_FROM_CSTRING(kHTMLInfo),
                                  aIndex, infoString);
        if (type.EqualsLiteral(kHTMLMime)) {
          AutoPlaceholderBatch treatAsOneTransaction(
              *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
          nsresult rv = InsertHTMLWithContextAsSubAction(
              text, contextString, infoString, type, safeToInsertData,
              aDroppedAt, aDeleteSelectedContent,
              InlineStylesAtInsertionPoint::Clear, aEditingHost);
          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                               "HTMLEditor::InsertHTMLWithContextAsSubAction("
                               "InlineStylesAtInsertionPoint::Clear) failed");
          return rv;
        }
      }
    }

    if (type.EqualsLiteral(kTextMime) || type.EqualsLiteral(kMozTextInternal)) {
      nsAutoString text;
      GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
      AutoPlaceholderBatch treatAsOneTransaction(
          *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
      nsresult rv = InsertTextAt(text, aDroppedAt, aDeleteSelectedContent);
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                           "EditorBase::InsertTextAt() failed");
      return rv;
    }
  }

  return NS_OK;
}

// static
HTMLEditor::HavePrivateHTMLFlavor
HTMLEditor::DataTransferOrClipboardHasPrivateHTMLFlavor(
    DataTransfer* aDataTransfer, nsIClipboard* aClipboard) {
  nsresult rv;
  if (aDataTransfer) {
    return aDataTransfer->HasPrivateHTMLFlavor() ? HavePrivateHTMLFlavor::Yes
                                                 : HavePrivateHTMLFlavor::No;
  }
  // otherwise, fall back to clipboard
  if (NS_WARN_IF(!aClipboard)) {
    return HavePrivateHTMLFlavor::No;
  }

  // check the clipboard for our special kHTMLContext flavor.  If that is there,
  // we know we have our own internal html format on clipboard.
  bool hasPrivateHTMLFlavor = false;
  AutoTArray<nsCString, 1> flavArray = {nsDependentCString(kHTMLContext)};
  rv = aClipboard->HasDataMatchingFlavors(
      flavArray, nsIClipboard::kGlobalClipboard, &hasPrivateHTMLFlavor);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "nsIClipboard::HasDataMatchingFlavors(nsIClipboard::"
                       "kGlobalClipboard) failed");
  return NS_SUCCEEDED(rv) && hasPrivateHTMLFlavor ? HavePrivateHTMLFlavor::Yes
                                                  : HavePrivateHTMLFlavor::No;
}

nsresult HTMLEditor::HandlePaste(AutoEditActionDataSetter& aEditActionData,
                                 nsIClipboard::ClipboardType aClipboardType,
                                 DataTransfer* aDataTransfer) {
  aEditActionData.InitializeDataTransferWithClipboard(
      SettingDataTransfer::eWithFormat, aDataTransfer, aClipboardType);
  nsresult rv = aEditActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
    return rv;
  }
  const RefPtr<Element> editingHost =
      ComputeEditingHost(LimitInBodyElement::No);
  if (NS_WARN_IF(!editingHost)) {
    return NS_ERROR_FAILURE;
  }
  rv = PasteInternal(aClipboardType, aDataTransfer, *editingHost);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::PasteInternal() failed");
  return rv;
}

nsresult HTMLEditor::PasteInternal(nsIClipboard::ClipboardType aClipboardType,
                                   DataTransfer* aDataTransfer,
                                   const Element& aEditingHost) {
  MOZ_ASSERT(IsEditActionDataAvailable());

  if (MOZ_UNLIKELY(!IsModifiable())) {
    return NS_OK;
  }

  // Get Clipboard Service
  nsresult rv = NS_OK;
  nsCOMPtr<nsIClipboard> clipboard =
      do_GetService("@mozilla.org/widget/clipboard;1", &rv);
  if (NS_FAILED(rv)) {
    NS_WARNING("Failed to get nsIClipboard service");
    return rv;
  }

  // Get the nsITransferable interface for getting the data from the clipboard
  nsCOMPtr<nsITransferable> transferable;
  rv = PrepareHTMLTransferable(getter_AddRefs(transferable), &aEditingHost);
  if (NS_FAILED(rv)) {
    NS_WARNING("HTMLEditor::PrepareHTMLTransferable() failed");
    return rv;
  }
  if (!transferable) {
    NS_WARNING("HTMLEditor::PrepareHTMLTransferable() returned nullptr");
    return NS_ERROR_FAILURE;
  }
  // Get the Data from the clipboard
  rv = GetDataFromDataTransferOrClipboard(aDataTransfer, transferable,
                                          aClipboardType);
  if (NS_FAILED(rv)) {
    NS_WARNING("EditorBase::GetDataFromDataTransferOrClipboard() failed");
    return rv;
  }

  // also get additional html copy hints, if present
  nsAutoString contextStr, infoStr;

  // If we have our internal html flavor on the clipboard, there is special
  // context to use instead of cfhtml context.
  const HavePrivateHTMLFlavor clipboardHasPrivateHTMLFlavor =
      DataTransferOrClipboardHasPrivateHTMLFlavor(aDataTransfer, clipboard);
  if (clipboardHasPrivateHTMLFlavor == HavePrivateHTMLFlavor::Yes) {
    nsCOMPtr<nsITransferable> contextTransferable =
        do_CreateInstance("@mozilla.org/widget/transferable;1");
    if (!contextTransferable) {
      NS_WARNING(
          "do_CreateInstance() failed to create nsITransferable instance");
      return NS_ERROR_FAILURE;
    }
    DebugOnly<nsresult> rvIgnored = contextTransferable->Init(nullptr);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                         "nsITransferable::Init() failed, but ignored");
    contextTransferable->SetIsPrivateData(transferable->GetIsPrivateData());
    rvIgnored = contextTransferable->AddDataFlavor(kHTMLContext);
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rvIgnored),
        "nsITransferable::AddDataFlavor(kHTMLContext) failed, but ignored");
    GetDataFromDataTransferOrClipboard(aDataTransfer, contextTransferable,
                                       aClipboardType);
    nsCOMPtr<nsISupports> contextDataObj;
    rv = contextTransferable->GetTransferData(kHTMLContext,
                                              getter_AddRefs(contextDataObj));
    if (NS_SUCCEEDED(rv) && contextDataObj) {
      if (nsCOMPtr<nsISupportsString> str = do_QueryInterface(contextDataObj)) {
        DebugOnly<nsresult> rvIgnored = str->GetData(contextStr);
        NS_WARNING_ASSERTION(
            NS_SUCCEEDED(rvIgnored),
            "nsISupportsString::GetData() failed, but ignored");
      }
    }

    nsCOMPtr<nsITransferable> infoTransferable =
        do_CreateInstance("@mozilla.org/widget/transferable;1");
    if (!infoTransferable) {
      NS_WARNING(
          "do_CreateInstance() failed to create nsITransferable instance");
      return NS_ERROR_FAILURE;
    }
    rvIgnored = infoTransferable->Init(nullptr);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                         "nsITransferable::Init() failed, but ignored");
    contextTransferable->SetIsPrivateData(transferable->GetIsPrivateData());
    rvIgnored = infoTransferable->AddDataFlavor(kHTMLInfo);
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rvIgnored),
        "nsITransferable::AddDataFlavor(kHTMLInfo) failed, but ignored");

    GetDataFromDataTransferOrClipboard(aDataTransfer, infoTransferable,
                                       aClipboardType);
    nsCOMPtr<nsISupports> infoDataObj;
    rv = infoTransferable->GetTransferData(kHTMLInfo,
                                           getter_AddRefs(infoDataObj));
    if (NS_SUCCEEDED(rv) && infoDataObj) {
      if (nsCOMPtr<nsISupportsString> str = do_QueryInterface(infoDataObj)) {
        DebugOnly<nsresult> rvIgnored = str->GetData(infoStr);
        NS_WARNING_ASSERTION(
            NS_SUCCEEDED(rvIgnored),
            "nsISupportsString::GetData() failed, but ignored");
      }
    }
  }

  rv = InsertFromTransferableAtSelection(transferable, contextStr, infoStr,
                                         clipboardHasPrivateHTMLFlavor,
                                         aEditingHost);
  NS_WARNING_ASSERTION(
      NS_SUCCEEDED(rv),
      "HTMLEditor::InsertFromTransferableAtSelection() failed");
  return rv;
}

nsresult HTMLEditor::HandlePasteTransferable(
    AutoEditActionDataSetter& aEditActionData, nsITransferable& aTransferable) {
  // InitializeDataTransfer may fetch input stream in aTransferable, so it
  // may be invalid after calling this.
  aEditActionData.InitializeDataTransfer(&aTransferable);

  nsresult rv = aEditActionData.MaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "MaybeDispatchBeforeInputEvent(), failed");
    return rv;
  }

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

  RefPtr<DataTransfer> dataTransfer = GetInputEventDataTransfer();
  if (dataTransfer->HasFile() && dataTransfer->MozItemCount() > 0) {
    // Now aTransferable has moved to DataTransfer. Use DataTransfer.
    AutoPlaceholderBatch treatAsOneTransaction(
        *this, ScrollSelectionIntoView::Yes, __FUNCTION__);

    rv = InsertFromDataTransfer(dataTransfer, 0, nullptr, EditorDOMPoint(),
                                DeleteSelectedContent::Yes, *editingHost);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                         "HTMLEditor::InsertFromDataTransfer("
                         "DeleteSelectedContent::Yes) failed");
    return rv;
  }

  nsAutoString contextStr, infoStr;
  rv = InsertFromTransferableAtSelection(&aTransferable, contextStr, infoStr,
                                         HavePrivateHTMLFlavor::No,
                                         *editingHost);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "HTMLEditor::InsertFromTransferableAtSelection("
                       "HavePrivateHTMLFlavor::No) failed");
  return rv;
}

nsresult HTMLEditor::PasteNoFormattingAsAction(
    nsIClipboard::ClipboardType aClipboardType,
    DispatchPasteEvent aDispatchPasteEvent,
    DataTransfer* aDataTransfer /* = nullptr */,
    nsIPrincipal* aPrincipal /* = nullptr */) {
  if (IsReadonly()) {
    return NS_OK;
  }
  // Create the same DataTransfer object here so we can share it between
  // the clipboard event and its data with the call to
  // InsertFromTransferableWithSelection below. This prevents
  // race conditions with Content Analysis on like we see in bug 1918027.
  RefPtr<DataTransfer> dataTransfer =
      aDataTransfer ? RefPtr<DataTransfer>(aDataTransfer)
                    : RefPtr<DataTransfer>(CreateDataTransferForPaste(
                          ePasteNoFormatting, aClipboardType));

  auto clearDataTransfer = MakeScopeExit([&] {
    // If the caller passed in aDataTransfer, they are responsible for clearing
    // this.
    if (!aDataTransfer && dataTransfer) {
      dataTransfer->ClearForPaste();
    }
  });

  AutoEditActionDataSetter editActionData(*this, EditAction::ePaste,
                                          aPrincipal);
  if (NS_WARN_IF(!editActionData.CanHandle())) {
    return NS_ERROR_NOT_INITIALIZED;
  }
  editActionData.InitializeDataTransferWithClipboard(
      SettingDataTransfer::eWithoutFormat, dataTransfer, aClipboardType);

  if (aDispatchPasteEvent == DispatchPasteEvent::Yes) {
    RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager();
    if (NS_WARN_IF(!focusManager)) {
      return NS_ERROR_UNEXPECTED;
    }
    const RefPtr<Element> focusedElement = focusManager->GetFocusedElement();

    Result<ClipboardEventResult, nsresult> ret = Err(NS_ERROR_FAILURE);
    {
      // This method is not set up to pass back the new aDataTransfer
      // if it changes. If we need this in the future, we can change
      // aDataTransfer to be a RefPtr<DataTransfer>*.
      MOZ_ASSERT(!aDataTransfer);
      AutoTrackDataTransferForPaste trackDataTransfer(*this, dataTransfer);

      ret = DispatchClipboardEventAndUpdateClipboard(
          ePasteNoFormatting, Some(aClipboardType), dataTransfer);
      if (MOZ_UNLIKELY(ret.isErr())) {
        NS_WARNING(
            "EditorBase::DispatchClipboardEventAndUpdateClipboard("
            "ePasteNoFormatting) failed");
        return EditorBase::ToGenericNSResult(ret.unwrapErr());
      }
    }
    switch (ret.inspect()) {
      case ClipboardEventResult::DoDefault:
        break;
      case ClipboardEventResult::DefaultPreventedOfPaste:
      case ClipboardEventResult::IgnoredOrError:
        return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
      case ClipboardEventResult::CopyOrCutHandled:
        MOZ_ASSERT_UNREACHABLE("Invalid result for ePaste");
    }

    // If focus is changed by a "paste" event listener, we should keep handling
    // the "pasting" in new focused editor because Chrome works as so.
    const RefPtr<Element> newFocusedElement = focusManager->GetFocusedElement();
    if (MOZ_UNLIKELY(focusedElement != newFocusedElement)) {
      // For the privacy reason, let's top handling it if new focused element is
      // in different document.
      if (focusManager->GetFocusedWindow() != GetWindow()) {
        return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
      }
      RefPtr<EditorBase> editorBase =
          nsContentUtils::GetActiveEditor(GetPresContext());
      if (!editorBase || (editorBase->IsHTMLEditor() &&
                          !editorBase->AsHTMLEditor()->IsActiveInDOMWindow())) {
        return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
      }
      if (editorBase != this) {
        if (editorBase->IsHTMLEditor()) {
          nsresult rv = MOZ_KnownLive(editorBase->AsHTMLEditor())
                            ->PasteNoFormattingAsAction(
                                aClipboardType, DispatchPasteEvent::No,
                                dataTransfer, aPrincipal);
          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                               "HTMLEditor::PasteNoFormattingAsAction("
                               "DispatchPasteEvent::No) failed");
          return EditorBase::ToGenericNSResult(rv);
        }
        nsresult rv = editorBase->PasteAsAction(
            aClipboardType, DispatchPasteEvent::No, dataTransfer, aPrincipal);
        NS_WARNING_ASSERTION(
            NS_SUCCEEDED(rv),
            "EditorBase::PasteAsAction(DispatchPasteEvent::No) failed");
        return EditorBase::ToGenericNSResult(rv);
      }
    }
  }

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

  // Dispatch "beforeinput" event after "paste" event.  And perhaps, before
  // committing composition because if pasting is canceled, we don't need to
  // commit the active composition.
  nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "MaybeDispatchBeforeInputEvent(), failed");
    return EditorBase::ToGenericNSResult(rv);
  }

  DebugOnly<nsresult> rvIgnored = CommitComposition();
  if (NS_WARN_IF(Destroyed())) {
    return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
  }
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "EditorBase::CommitComposition() failed, but ignored");
  if (MOZ_UNLIKELY(!IsModifiable())) {
    return NS_OK;
  }

  Result<nsCOMPtr<nsITransferable>, nsresult> maybeTransferable =
      EditorUtils::CreateTransferableForPlainText(*GetDocument());
  if (maybeTransferable.isErr()) {
    NS_WARNING("EditorUtils::CreateTransferableForPlainText() failed");
    return EditorBase::ToGenericNSResult(maybeTransferable.unwrapErr());
  }
  nsCOMPtr<nsITransferable> transferable(maybeTransferable.unwrap());
  if (!transferable) {
    NS_WARNING(
        "EditorUtils::CreateTransferableForPlainText() returned nullptr, but "
        "ignored");
    return NS_OK;
  }
  rv = GetDataFromDataTransferOrClipboard(dataTransfer, transferable,
                                          aClipboardType);
  if (NS_FAILED(rv)) {
    NS_WARNING("EditorBase::GetDataFromDataTransferOrClipboard() failed");
    return rv;
  }

  rv = InsertFromTransferableAtSelection(
      transferable, u""_ns, u""_ns, HavePrivateHTMLFlavor::No, *editingHost);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "HTMLEditor::InsertFromTransferableAtSelection("
                       "HavePrivateHTMLFlavor::No) failed");
  return EditorBase::ToGenericNSResult(rv);
}

// The following arrays contain the MIME types that we can paste. The arrays
// are used by CanPaste() and CanPasteTransferable() below.

static const char* textEditorFlavors[] = {kTextMime};
static const char* textHtmlEditorFlavors[] = {kTextMime,      kHTMLMime,
                                              kJPEGImageMime, kJPGImageMime,
                                              kPNGImageMime,  kGIFImageMime};

bool HTMLEditor::CanPaste(nsIClipboard::ClipboardType aClipboardType) const {
  if (AreClipboardCommandsUnconditionallyEnabled()) {
    return true;
  }

  // can't paste if readonly
  if (!IsModifiable()) {
    return false;
  }

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

  nsresult rv;
  nsCOMPtr<nsIClipboard> clipboard(
      do_GetService("@mozilla.org/widget/clipboard;1", &rv));
  if (NS_FAILED(rv)) {
    NS_WARNING("Failed to get nsIClipboard service");
    return false;
  }

  // Use the flavors depending on the current editor mask
  if (IsPlaintextMailComposer() ||
      editingHost->IsContentEditablePlainTextOnly()) {
    AutoTArray<nsCString, std::size(textEditorFlavors)> flavors;
    flavors.AppendElements<const char*>(Span<const char*>(textEditorFlavors));
    bool haveFlavors;
    nsresult rv = clipboard->HasDataMatchingFlavors(flavors, aClipboardType,
                                                    &haveFlavors);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                         "nsIClipboard::HasDataMatchingFlavors() failed");
    return NS_SUCCEEDED(rv) && haveFlavors;
  }

  AutoTArray<nsCString, std::size(textHtmlEditorFlavors)> flavors;
  flavors.AppendElements<const char*>(Span<const char*>(textHtmlEditorFlavors));
  bool haveFlavors;
  rv = clipboard->HasDataMatchingFlavors(flavors, aClipboardType, &haveFlavors);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "nsIClipboard::HasDataMatchingFlavors() failed");
  return NS_SUCCEEDED(rv) && haveFlavors;
}

bool HTMLEditor::CanPasteTransferable(nsITransferable* aTransferable) {
  // can't paste if readonly
  if (!IsModifiable()) {
    return false;
  }

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

  // If |aTransferable| is null, assume that a paste will succeed.
  if (!aTransferable) {
    return true;
  }

  // Peek in |aTransferable| to see if it contains a supported MIME type.

  // Use the flavors depending on the current editor mask
  const char** flavors;
  size_t length;
  if (IsPlaintextMailComposer() ||
      editingHost->IsContentEditablePlainTextOnly()) {
    flavors = textEditorFlavors;
    length = std::size(textEditorFlavors);
  } else {
    flavors = textHtmlEditorFlavors;
    length = std::size(textHtmlEditorFlavors);
  }

  for (size_t i = 0; i < length; i++, flavors++) {
    nsCOMPtr<nsISupports> data;
    nsresult rv =
        aTransferable->GetTransferData(*flavors, getter_AddRefs(data));
    if (NS_SUCCEEDED(rv) && data) {
      return true;
    }
  }

  return false;
}

nsresult HTMLEditor::HandlePasteAsQuotation(
    AutoEditActionDataSetter& aEditActionData,
    nsIClipboard::ClipboardType aClipboardType, DataTransfer* aDataTransfer) {
  MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
             aClipboardType == nsIClipboard::kSelectionClipboard);
  aEditActionData.InitializeDataTransferWithClipboard(
      SettingDataTransfer::eWithFormat, aDataTransfer, aClipboardType);
  if (NS_WARN_IF(!aEditActionData.CanHandle())) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  nsresult rv = aEditActionData.MaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "MaybeDispatchBeforeInputEvent(), failed");
    return rv;
  }

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

  if (IsPlaintextMailComposer() ||
      editingHost->IsContentEditablePlainTextOnly()) {
    nsresult rv =
        PasteAsPlaintextQuotation(aClipboardType, aDataTransfer, *editingHost);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                         "HTMLEditor::PasteAsPlaintextQuotation() failed");
    return rv;
  }

  // If it's not in plain text edit mode, paste text into new
  // <blockquote type="cite"> element after removing selection.

  {
    // XXX Why don't we test these first?
    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;
    }
  }

  UndefineCaretBidiLevel();

  AutoPlaceholderBatch treatAsOneTransaction(
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
  IgnoredErrorResult ignoredError;
  AutoEditSubActionNotifier startToHandleEditSubAction(
      *this, EditSubAction::eInsertQuotation, nsIEditor::eNext, ignoredError);
  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
    return ignoredError.StealNSResult();
  }
  NS_WARNING_ASSERTION(
      !ignoredError.Failed(),
      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");

  rv = EnsureNoPaddingBRElementForEmptyEditor();
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    return 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 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 NS_ERROR_EDITOR_DESTROYED;
      }
      NS_WARNING_ASSERTION(
          NS_SUCCEEDED(rv),
          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
    }
  }

  // Remove Selection and create `<blockquote type="cite">` now.
  // XXX Why don't we insert the `<blockquote>` into the DOM tree after
  //     pasting the content in clipboard into it?
  Result<RefPtr<Element>, nsresult> blockquoteElementOrError =
      DeleteSelectionAndCreateElement(
          *nsGkAtoms::blockquote,
          // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
          [](HTMLEditor&, Element& aBlockquoteElement, const EditorDOMPoint&)
              MOZ_CAN_RUN_SCRIPT_BOUNDARY {
                DebugOnly<nsresult> rvIgnored = aBlockquoteElement.SetAttr(
                    kNameSpaceID_None, nsGkAtoms::type, u"cite"_ns,
                    aBlockquoteElement.IsInComposedDoc());
                NS_WARNING_ASSERTION(
                    NS_SUCCEEDED(rvIgnored),
                    nsPrintfCString(
                        "Element::SetAttr(nsGkAtoms::type, \"cite\", %s) "
                        "failed, but ignored",
                        aBlockquoteElement.IsInComposedDoc() ? "true" : "false")
                        .get());
                return NS_OK;
              });
  if (MOZ_UNLIKELY(blockquoteElementOrError.isErr()) ||
      NS_WARN_IF(Destroyed())) {
    NS_WARNING(
        "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::blockquote) "
        "failed");
    return Destroyed() ? NS_ERROR_EDITOR_DESTROYED
                       : blockquoteElementOrError.unwrapErr();
  }
  MOZ_ASSERT(blockquoteElementOrError.inspect());

  // Collapse Selection in the new `<blockquote>` element.
  rv = CollapseSelectionToStartOf(
      MOZ_KnownLive(*blockquoteElementOrError.inspect()));
  if (NS_FAILED(rv)) {
    NS_WARNING("EditorBase::CollapseSelectionToStartOf() failed");
    return rv;
  }

  rv = PasteInternal(aClipboardType, aDataTransfer, *editingHost);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::PasteInternal() failed");
  return rv;
}

nsresult HTMLEditor::PasteAsPlaintextQuotation(
    nsIClipboard::ClipboardType aSelectionType, DataTransfer* aDataTransfer,
    const Element& aEditingHost) {
  nsresult rv;
  // Create generic Transferable for getting the data
  nsCOMPtr<nsITransferable> transferable =
      do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
  if (NS_FAILED(rv)) {
    NS_WARNING("do_CreateInstance() failed to create nsITransferable instance");
    return rv;
  }
  if (!transferable) {
    NS_WARNING("do_CreateInstance() returned nullptr");
    return NS_ERROR_FAILURE;
  }

  RefPtr<Document> destdoc = GetDocument();
  auto* windowContext = GetDocument()->GetWindowContext();
  if (!windowContext) {
    NS_WARNING("Editor didn't have document window context");
    return NS_ERROR_FAILURE;
  }

  nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
  DebugOnly<nsresult> rvIgnored = transferable->Init(loadContext);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                       "nsITransferable::Init() failed, but ignored");

  // We only handle plaintext pastes here
  rvIgnored = transferable->AddDataFlavor(kTextMime);
  NS_WARNING_ASSERTION(
      NS_SUCCEEDED(rvIgnored),
      "nsITransferable::AddDataFlavor(kTextMime) failed, but ignored");

  // Get the Data from the clipboard
  GetDataFromDataTransferOrClipboard(aDataTransfer, transferable,
                                     aSelectionType);

  // Now we ask the transferable for the data
  // it still owns the data, we just have a pointer to it.
  // If it can't support a "text" output of the data the call will fail
  nsCOMPtr<nsISupports> genericDataObj;
  nsAutoCString flavor;
  rv = transferable->GetAnyTransferData(flavor, getter_AddRefs(genericDataObj));
  if (NS_FAILED(rv)) {
    NS_WARNING("nsITransferable::GetAnyTransferData() failed");
    return rv;
  }

  if (!flavor.EqualsLiteral(kTextMime)) {
    return NS_OK;
  }

  nsAutoString stuffToPaste;
  if (!GetString(genericDataObj, stuffToPaste)) {
    return NS_OK;
  }

  AutoPlaceholderBatch treatAsOneTransaction(
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
  rv = InsertAsPlaintextQuotation(stuffToPaste, AddCites::Yes, aEditingHost);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "HTMLEditor::InsertAsPlaintextQuotation() failed");
  return rv;
}

nsresult HTMLEditor::InsertWithQuotationsAsSubAction(
    const nsAString& aQuotedText) {
  MOZ_ASSERT(IsEditActionDataAvailable());

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

  {
    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;
    }
  }

  UndefineCaretBidiLevel();

  // Let the citer quote it for us:
  nsString quotedStuff;
  InternetCiter::GetCiteString(aQuotedText, quotedStuff);

  // It's best to put a blank line after the quoted text so that mails
  // written without thinking won't be so ugly.
  if (!aQuotedText.IsEmpty() &&
      (aQuotedText.Last() != HTMLEditUtils::kNewLine)) {
    quotedStuff.Append(HTMLEditUtils::kNewLine);
  }

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

  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    return 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 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 NS_ERROR_EDITOR_DESTROYED;
      }
      NS_WARNING_ASSERTION(
          NS_SUCCEEDED(rv),
          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
    }
  }

  rv = InsertTextAsSubAction(quotedStuff, SelectionHandling::Delete);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "EditorBase::InsertTextAsSubAction() failed");
  return rv;
}

NS_IMETHODIMP HTMLEditor::InsertTextWithQuotations(
    const nsAString& aStringToInsert) {
  AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText);
  MOZ_ASSERT(!aStringToInsert.IsVoid());
  editActionData.SetData(aStringToInsert);
  nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
    return EditorBase::ToGenericNSResult(rv);
  }
  if (aStringToInsert.IsEmpty()) {
    return NS_OK;
  }

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

  // The whole operation should be undoable in one transaction:
  // XXX Why isn't enough to use only AutoPlaceholderBatch here?
  AutoTransactionBatch bundleAllTransactions(*this, __FUNCTION__);
  AutoPlaceholderBatch treatAsOneTransaction(
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);

  rv = InsertTextWithQuotationsInternal(aStringToInsert, *editingHost);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "HTMLEditor::InsertTextWithQuotationsInternal() failed");
  return EditorBase::ToGenericNSResult(rv);
}

nsresult HTMLEditor::InsertTextWithQuotationsInternal(
    const nsAString& aStringToInsert, const Element& aEditingHost) {
  MOZ_ASSERT(!aStringToInsert.IsEmpty());
  // We're going to loop over the string, collecting up a "hunk"
  // that's all the same type (quoted or not),
  // Whenever the quotedness changes (or we reach the string's end)
  // we will insert the hunk all at once, quoted or non.
  static const char16_t cite('>');
  bool curHunkIsQuoted = (aStringToInsert.First() == cite);

  nsAString::const_iterator hunkStart, strEnd;
  aStringToInsert.BeginReading(hunkStart);
  aStringToInsert.EndReading(strEnd);

  // In the loop below, we only look for DOM newlines (\n),
  // because we don't have a FindChars method that can look
  // for both \r and \n.  \r is illegal in the dom anyway,
  // but in debug builds, let's take the time to verify that
  // there aren't any there:
#ifdef DEBUG
  nsAString::const_iterator dbgStart(hunkStart);
  if (FindCharInReadable(HTMLEditUtils::kCarriageReturn, dbgStart, strEnd)) {
    NS_ASSERTION(
        false,
        "Return characters in DOM! InsertTextWithQuotations may be wrong");
  }
#endif /* DEBUG */

  // Loop over lines:
  nsresult rv = NS_OK;
  nsAString::const_iterator lineStart(hunkStart);
  // We will break from inside when we run out of newlines.
  for (;;) {
    // Search for the end of this line (dom newlines, see above):
    bool found = FindCharInReadable(HTMLEditUtils::kNewLine, lineStart, strEnd);
    bool quoted = false;
    if (found) {
      // if there's another newline, lineStart now points there.
      // Loop over any consecutive newline chars:
      nsAString::const_iterator firstNewline(lineStart);
      while (*lineStart == HTMLEditUtils::kNewLine) {
        ++lineStart;
      }
      quoted = (*lineStart == cite);
      if (quoted == curHunkIsQuoted) {
        continue;
      }
      // else we're changing state, so we need to insert
      // from curHunk to lineStart then loop around.

      // But if the current hunk is quoted, then we want to make sure
      // that any extra newlines on the end do not get included in
      // the quoted section: blank lines flaking a quoted section
      // should be considered unquoted, so that if the user clicks
      // there and starts typing, the new text will be outside of
      // the quoted block.
      if (curHunkIsQuoted) {
        lineStart = firstNewline;

        // 'firstNewline' points to the first '\n'. We want to
        // ensure that this first newline goes into the hunk
        // since quoted hunks can be displayed as blocks
        // (and the newline should become invisible in this case).
        // So the next line needs to start at the next character.
        lineStart++;
      }
    }

    // If no newline found, lineStart is now strEnd and we can finish up,
    // inserting from curHunk to lineStart then returning.
    const nsAString& curHunk = Substring(hunkStart, lineStart);
    if (curHunkIsQuoted) {
      rv = InsertAsPlaintextQuotation(curHunk, AddCites::No, aEditingHost);
      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
        return NS_ERROR_EDITOR_DESTROYED;
      }
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                           "HTMLEditor::InsertAsPlaintextQuotation() failed, "
                           "but might be ignored");
    } else {
      rv = InsertTextAsSubAction(curHunk, SelectionHandling::Delete);
      NS_WARNING_ASSERTION(
          NS_SUCCEEDED(rv),
          "EditorBase::InsertTextAsSubAction() failed, but might be ignored");
    }
    if (!found) {
      break;
    }
    curHunkIsQuoted = quoted;
    hunkStart = lineStart;
  }

  // XXX This returns the last result of InsertAsPlaintextQuotation() or
  //     InsertTextAsSubAction() in the loop.  This must be a bug.
  return rv;
}

nsresult HTMLEditor::InsertAsQuotation(const nsAString& aQuotedText,
                                       nsINode** aNodeInserted) {
  const RefPtr<Element> editingHost =
      ComputeEditingHost(LimitInBodyElement::No);
  if (NS_WARN_IF(!editingHost)) {
    return NS_ERROR_FAILURE;
  }

  if (IsPlaintextMailComposer() ||
      editingHost->IsContentEditablePlainTextOnly()) {
    AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText);
    MOZ_ASSERT(!aQuotedText.IsVoid());
    editActionData.SetData(aQuotedText);
    nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
    if (NS_FAILED(rv)) {
      NS_WARNING_ASSERTION(
          rv == NS_ERROR_EDITOR_ACTION_CANCELED,
          "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
      return EditorBase::ToGenericNSResult(rv);
    }
    AutoPlaceholderBatch treatAsOneTransaction(
        *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
    rv = InsertAsPlaintextQuotation(aQuotedText, AddCites::Yes, *editingHost,
                                    aNodeInserted);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                         "HTMLEditor::InsertAsPlaintextQuotation() failed");
    return EditorBase::ToGenericNSResult(rv);
  }

  AutoEditActionDataSetter editActionData(*this,
                                          EditAction::eInsertBlockquoteElement);
  nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
    return EditorBase::ToGenericNSResult(rv);
  }

  AutoPlaceholderBatch treatAsOneTransaction(
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
  nsAutoString citation;
  rv = InsertAsCitedQuotationInternal(aQuotedText, citation, false,
                                      *editingHost, aNodeInserted);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "HTMLEditor::InsertAsCitedQuotationInternal() failed");
  return EditorBase::ToGenericNSResult(rv);
}

// Insert plaintext as a quotation, with cite marks (e.g. "> ").
// This differs from its corresponding method in TextEditor
// in that here, quoted material is enclosed in a <pre> tag
// in order to preserve the original line wrapping.
nsresult HTMLEditor::InsertAsPlaintextQuotation(const nsAString& aQuotedText,
                                                AddCites aAddCites,
                                                const Element& aEditingHost,
                                                nsINode** aNodeInserted) {
  MOZ_ASSERT(IsEditActionDataAvailable());

  if (aNodeInserted) {
    *aNodeInserted = nullptr;
  }

  {
    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;
    }
  }

  UndefineCaretBidiLevel();

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

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

  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
    nsresult rv = EnsureCaretNotAfterInvisibleBRElement(aEditingHost);
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
      return 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 NS_ERROR_EDITOR_DESTROYED;
      }
      NS_WARNING_ASSERTION(
          NS_SUCCEEDED(rv),
          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
    }
  }

  RefPtr<Element> containerSpanElement;
  if (!aEditingHost.IsContentEditablePlainTextOnly()) {
    // Wrap the inserted quote in a <span> so we can distinguish it. If we're
    // inserting into the <body>, we use a <span> which is displayed as a block
    // and sized to the screen using 98 viewport width units.
    // We could use 100vw, but 98vw avoids a horizontal scroll bar where
    // possible. All this is done to wrap overlong lines to the screen and not
    // to the container element, the width-restricted body.
    // XXX I think that we don't need to do this in the web.  This should be
    // done only for Thunderbird.
    Result<RefPtr<Element>, nsresult> spanElementOrError =
        DeleteSelectionAndCreateElement(
            *nsGkAtoms::span, [](HTMLEditor&, Element& aSpanElement,
                                 const EditorDOMPoint& aPointToInsert) {
              // Add an attribute on the pre node so we'll know it's a
              // quotation.
              DebugOnly<nsresult> rvIgnored = aSpanElement.SetAttr(
                  kNameSpaceID_None, nsGkAtoms::mozquote, u"true"_ns,
                  aSpanElement.IsInComposedDoc());
              NS_WARNING_ASSERTION(
                  NS_SUCCEEDED(rvIgnored),
                  nsPrintfCString(
                      "Element::SetAttr(nsGkAtoms::mozquote, \"true\", %s) "
                      "failed",
                      aSpanElement.IsInComposedDoc() ? "true" : "false")
                      .get());
              // Allow wrapping on spans so long lines get wrapped to the
              // screen.
              if (aPointToInsert.IsContainerHTMLElement(nsGkAtoms::body)) {
                DebugOnly<nsresult> rvIgnored = aSpanElement.SetAttr(
                    kNameSpaceID_None, nsGkAtoms::style,
                    nsLiteralString(u"white-space: pre-wrap; display: block; "
                                    u"width: 98vw;"),
                    false);
                NS_WARNING_ASSERTION(
                    NS_SUCCEEDED(rvIgnored),
                    "Element::SetAttr(nsGkAtoms::style, \"pre-wrap, block\", "
                    "false) failed, but ignored");
              } else {
                DebugOnly<nsresult> rvIgnored =
                    aSpanElement.SetAttr(kNameSpaceID_None, nsGkAtoms::style,
                                         u"white-space: pre-wrap;"_ns, false);
                NS_WARNING_ASSERTION(
                    NS_SUCCEEDED(rvIgnored),
                    "Element::SetAttr(nsGkAtoms::style, "
                    "\"pre-wrap\", false) failed, but ignored");
              }
              return NS_OK;
            });
    if (MOZ_UNLIKELY(spanElementOrError.isErr())) {
      NS_WARNING(
          "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::span) "
          "failed");
      return NS_OK;
    }
    // If this succeeded, then set selection inside the pre
    // so the inserted text will end up there.
    // If it failed, we don't care what the return value was,
    // but we'll fall through and try to insert the text anyway.
    MOZ_ASSERT(spanElementOrError.inspect());
    nsresult rv = CollapseSelectionToStartOf(
        MOZ_KnownLive(*spanElementOrError.inspect()));
    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
      NS_WARNING(
          "EditorBase::CollapseSelectionToStartOf() caused destroying the "
          "editor");
      return NS_ERROR_EDITOR_DESTROYED;
    }
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rv),
        "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
    containerSpanElement = spanElementOrError.unwrap();
  }

  // TODO: We should insert text at specific point rather than at selection.
  //       Then, we can do this before inserting the <span> element.
  if (aAddCites == AddCites::Yes) {
    nsresult rv = InsertWithQuotationsAsSubAction(aQuotedText);
    if (NS_FAILED(rv)) {
      NS_WARNING("HTMLEditor::InsertWithQuotationsAsSubAction() failed");
      return rv;
    }
  } else {
    nsresult rv = InsertTextAsSubAction(aQuotedText, SelectionHandling::Delete);
    if (NS_FAILED(rv)) {
      NS_WARNING("EditorBase::InsertTextAsSubAction() failed");
      return rv;
    }
  }

  // Set the selection to after the <span> if and only if we wrap the text into
  // it.
  if (containerSpanElement) {
    EditorRawDOMPoint afterNewSpanElement(
        EditorRawDOMPoint::After(*containerSpanElement));
    NS_WARNING_ASSERTION(
        afterNewSpanElement.IsSet(),
        "Failed to set after the new <span> element, but ignored");
    if (afterNewSpanElement.IsSet()) {
      nsresult rv = CollapseSelectionTo(afterNewSpanElement);
      if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
        NS_WARNING(
            "EditorBase::CollapseSelectionTo() caused destroying the editor");
        return NS_ERROR_EDITOR_DESTROYED;
      }
      NS_WARNING_ASSERTION(
          NS_SUCCEEDED(rv),
          "EditorBase::CollapseSelectionTo() failed, but ignored");
    }

    // Note that if !aAddCites, aNodeInserted isn't set.
    // That's okay because the routines that use aAddCites
    // don't need to know the inserted node.
    if (aNodeInserted) {
      containerSpanElement.forget(aNodeInserted);
    }
  }

  return NS_OK;
}

NS_IMETHODIMP HTMLEditor::Rewrap(bool aRespectNewlines) {
  AutoEditActionDataSetter editActionData(*this, EditAction::eRewrap);
  nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
    return EditorBase::ToGenericNSResult(rv);
  }

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

  // Rewrap makes no sense if there's no wrap column; default to 72.
  int32_t wrapWidth = WrapWidth();
  if (wrapWidth <= 0) {
    wrapWidth = 72;
  }

  nsAutoString current;
  const bool isCollapsed = SelectionRef().IsCollapsed();
  uint32_t flags = nsIDocumentEncoder::OutputFormatted |
                   nsIDocumentEncoder::OutputLFLineBreak;
  if (!isCollapsed) {
    flags |= nsIDocumentEncoder::OutputSelectionOnly;
  }
  rv = ComputeValueInternal(u"text/plain"_ns, flags, current);
  if (NS_FAILED(rv)) {
    NS_WARNING("EditorBase::ComputeValueInternal(text/plain) failed");
    return EditorBase::ToGenericNSResult(rv);
  }

  if (current.IsEmpty()) {
    return NS_OK;
  }

  nsString wrapped;
  uint32_t firstLineOffset = 0;  // XXX need to reset this if there is a
                                 //     selection
  InternetCiter::Rewrap(current, wrapWidth, firstLineOffset, aRespectNewlines,
                        wrapped);

  if (wrapped.IsEmpty()) {
    return NS_OK;
  }

  if (isCollapsed) {
    DebugOnly<nsresult> rvIgnored = SelectAllInternal();
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                         "HTMLEditor::SelectAllInternal() failed");
  }

  // The whole operation in InsertTextWithQuotationsInternal() should be
  // undoable in one transaction.
  // XXX Why isn't enough to use only AutoPlaceholderBatch here?
  AutoTransactionBatch bundleAllTransactions(*this, __FUNCTION__);
  AutoPlaceholderBatch treatAsOneTransaction(
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
  rv = InsertTextWithQuotationsInternal(wrapped, *editingHost);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "HTMLEditor::InsertTextWithQuotationsInternal() failed");
  return EditorBase::ToGenericNSResult(rv);
}

NS_IMETHODIMP HTMLEditor::InsertAsCitedQuotation(const nsAString& aQuotedText,
                                                 const nsAString& aCitation,
                                                 bool aInsertHTML,
                                                 nsINode** aNodeInserted) {
  const RefPtr<Element> editingHost =
      ComputeEditingHost(LimitInBodyElement::No);
  if (NS_WARN_IF(!editingHost)) {
    return NS_ERROR_FAILURE;
  }

  // Don't let anyone insert HTML when we're in plaintext mode.
  if (IsPlaintextMailComposer() ||
      editingHost->IsContentEditablePlainTextOnly()) {
    NS_ASSERTION(
        !aInsertHTML,
        "InsertAsCitedQuotation: trying to insert html into plaintext editor");

    AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText);
    MOZ_ASSERT(!aQuotedText.IsVoid());
    editActionData.SetData(aQuotedText);
    nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
    if (NS_FAILED(rv)) {
      NS_WARNING_ASSERTION(
          rv == NS_ERROR_EDITOR_ACTION_CANCELED,
          "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
      return EditorBase::ToGenericNSResult(rv);
    }

    AutoPlaceholderBatch treatAsOneTransaction(
        *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
    rv = InsertAsPlaintextQuotation(aQuotedText, AddCites::Yes, *editingHost,
                                    aNodeInserted);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                         "HTMLEditor::InsertAsPlaintextQuotation() failed");
    return EditorBase::ToGenericNSResult(rv);
  }

  AutoEditActionDataSetter editActionData(*this,
                                          EditAction::eInsertBlockquoteElement);
  nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
  if (NS_FAILED(rv)) {
    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                         "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
    return EditorBase::ToGenericNSResult(rv);
  }

  AutoPlaceholderBatch treatAsOneTransaction(
      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
  rv = InsertAsCitedQuotationInternal(aQuotedText, aCitation, aInsertHTML,
                                      *editingHost, aNodeInserted);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "HTMLEditor::InsertAsCitedQuotationInternal() failed");
  return EditorBase::ToGenericNSResult(rv);
}

nsresult HTMLEditor::InsertAsCitedQuotationInternal(
    const nsAString& aQuotedText, const nsAString& aCitation, bool aInsertHTML,
    const Element& aEditingHost, nsINode** aNodeInserted) {
  MOZ_ASSERT(IsEditActionDataAvailable());
  MOZ_ASSERT(!IsPlaintextMailComposer());

  {
    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;
    }
  }

  UndefineCaretBidiLevel();

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

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

  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
    nsresult rv = EnsureCaretNotAfterInvisibleBRElement(aEditingHost);
    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
      return 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 NS_ERROR_EDITOR_DESTROYED;
      }
      NS_WARNING_ASSERTION(
          NS_SUCCEEDED(rv),
          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
    }
  }

  Result<RefPtr<Element>, nsresult> blockquoteElementOrError =
      DeleteSelectionAndCreateElement(
          *nsGkAtoms::blockquote,
          // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
          [&aCitation](HTMLEditor&, Element& aBlockquoteElement,
                       const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
            // Try to set type=cite.  Ignore it if this fails.
            DebugOnly<nsresult> rvIgnored = aBlockquoteElement.SetAttr(
                kNameSpaceID_None, nsGkAtoms::type, u"cite"_ns,
                aBlockquoteElement.IsInComposedDoc());
            NS_WARNING_ASSERTION(
                NS_SUCCEEDED(rvIgnored),
                nsPrintfCString(
                    "Element::SetAttr(nsGkAtoms::type, \"cite\", %s) failed, "
                    "but ignored",
                    aBlockquoteElement.IsInComposedDoc() ? "true" : "false")
                    .get());
            if (!aCitation.IsEmpty()) {
              DebugOnly<nsresult> rvIgnored = aBlockquoteElement.SetAttr(
                  kNameSpaceID_None, nsGkAtoms::cite, aCitation,
                  aBlockquoteElement.IsInComposedDoc());
              NS_WARNING_ASSERTION(
                  NS_SUCCEEDED(rvIgnored),
                  nsPrintfCString(
                      "Element::SetAttr(nsGkAtoms::cite, \"...\", %s) failed, "
                      "but ignored",
                      aBlockquoteElement.IsInComposedDoc() ? "true" : "false")
                      .get());
            }
            return NS_OK;
          });
  if (MOZ_UNLIKELY(blockquoteElementOrError.isErr() ||
                   NS_WARN_IF(Destroyed()))) {
    NS_WARNING(
        "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::blockquote) "
        "failed");
    return Destroyed() ? NS_ERROR_EDITOR_DESTROYED
                       : blockquoteElementOrError.unwrapErr();
  }
  MOZ_ASSERT(blockquoteElementOrError.inspect());

  // Set the selection inside the blockquote so aQuotedText will go there:
  rv = CollapseSelectionTo(
      EditorRawDOMPoint(blockquoteElementOrError.inspect(), 0u));
  if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
    NS_WARNING(
        "EditorBase::CollapseSelectionTo() caused destroying the editor");
    return NS_ERROR_EDITOR_DESTROYED;
  }
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "EditorBase::CollapseSelectionTo() failed, but ignored");

  // TODO: We should insert text at specific point rather than at selection.
  //       Then, we can do this before inserting the <blockquote> element.
  if (aInsertHTML) {
    rv = LoadHTML(aQuotedText);
    if (NS_WARN_IF(Destroyed())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_FAILED(rv)) {
      NS_WARNING("HTMLEditor::LoadHTML() failed");
      return rv;
    }
  } else {
    rv = InsertTextAsSubAction(
        aQuotedText, SelectionHandling::Delete);  // XXX ignore charset
    if (NS_WARN_IF(Destroyed())) {
      return NS_ERROR_EDITOR_DESTROYED;
    }
    if (NS_FAILED(rv)) {
      NS_WARNING("HTMLEditor::LoadHTML() failed");
      return rv;
    }
  }

  // Set the selection to just after the inserted node:
  EditorRawDOMPoint afterNewBlockquoteElement(
      EditorRawDOMPoint::After(blockquoteElementOrError.inspect()));
  NS_WARNING_ASSERTION(
      afterNewBlockquoteElement.IsSet(),
      "Failed to set after new <blockquote> element, but ignored");
  if (afterNewBlockquoteElement.IsSet()) {
    nsresult rv = CollapseSelectionTo(afterNewBlockquoteElement);
    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
      NS_WARNING(
          "EditorBase::CollapseSelectionTo() caused destroying the editor");
      return NS_ERROR_EDITOR_DESTROYED;
    }
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rv),
        "EditorBase::CollapseSelectionTo() failed, but ignored");
  }

  if (aNodeInserted) {
    blockquoteElementOrError.unwrap().forget(aNodeInserted);
  }

  return NS_OK;
}

void HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
    RemoveHeadChildAndStealBodyChildsChildren(nsINode& aNode) {
  nsCOMPtr<nsIContent> body, head;
  // find the body and head nodes if any.
  // look only at immediate children of aNode.
  for (nsCOMPtr<nsIContent> child = aNode.GetFirstChild(); child;
       child = child->GetNextSibling()) {
    if (child->IsHTMLElement(nsGkAtoms::body)) {
      body = child;
    } else if (child->IsHTMLElement(nsGkAtoms::head)) {
      head = child;
    }
  }
  if (head) {
    ErrorResult ignored;
    aNode.RemoveChild(*head, ignored);
  }
  if (body) {
    nsCOMPtr<nsIContent> child = body->GetFirstChild();
    while (child) {
      ErrorResult ignored;
      aNode.InsertBefore(*child, body, ignored);
      child = body->GetFirstChild();
    }

    ErrorResult ignored;
    aNode.RemoveChild(*body, ignored);
  }
}

// static
void HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
    RemoveIncompleteDescendantsFromInsertingFragment(nsINode& aNode) {
  nsIContent* child = aNode.GetFirstChild();
  while (child) {
    bool isEmptyNodeShouldNotInserted = false;
    if (HTMLEditUtils::IsAnyListElement(child)) {
      // Current limitation of HTMLEditor:
      //   Cannot put caret in a list element which does not have list item
      //   element even as a descendant.  I.e., HTMLEditor does not support
      //   editing in such empty list element, and does not support to delete
      //   it from outside.  Therefore, HTMLWithContextInserter should not
      //   insert empty list element.
      isEmptyNodeShouldNotInserted = HTMLEditUtils::IsEmptyNode(
          *child,
          {
              // Although we don't check relation between list item element
              // and parent list element, but it should not be a problem in the
              // wild because appearing such invalid list element is an edge
              // case and anyway HTMLEditor supports editing in them.
              EmptyCheckOption::TreatListItemAsVisible,
              // A non-editable list item element may make the list element
              // visible.  Although HTMLEditor does not support to edit list
              // elements which have only non-editable list item elements, but
              // it should be deleted from outside.  Therefore, don't treat
              // non-editable things as invisible.
              // TODO: Currently, HTMLEditor does not support deleting such list
              //       element with Backspace.  We should fix this issue.
          });
    }
    // TODO: Perhaps, we should delete <table>s if they have no <td>/<th>
    //       element, or something other elements which must have specific
    //       children but they don't.
    if (isEmptyNodeShouldNotInserted) {
      nsIContent* nextChild = child->GetNextSibling();
      OwningNonNull<nsIContent> removingChild(*child);
      removingChild->Remove();
      child = nextChild;
      continue;
    }
    if (child->HasChildNodes()) {
      RemoveIncompleteDescendantsFromInsertingFragment(*child);
    }
    child = child->GetNextSibling();
  }
}

// static
bool HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
    IsInsertionCookie(const nsIContent& aContent) {
  // Is this child the magical cookie?
  if (const auto* comment = Comment::FromNode(&aContent)) {
    nsAutoString data;
    comment->GetData(data);

    return data.EqualsLiteral(kInsertCookie);
  }

  return false;
}

/**
 * This function finds the target node that we will be pasting into. aStart is
 * the context that we're given and aResult will be the target. Initially,
 * *aResult must be nullptr.
 *
 * The target for a paste is found by either finding the node that contains
 * the magical comment node containing kInsertCookie or, failing that, the
 * firstChild of the firstChild (until we reach a leaf).
 */

bool HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
    FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie(
        nsINode& aStart, nsCOMPtr<nsINode>& aResult) {
  nsIContent* firstChild = aStart.GetFirstChild();
  if (!firstChild) {
    // If the current result is nullptr, then aStart is a leaf, and is the
    // fallback result.
    if (!aResult) {
      aResult = &aStart;
    }
    return false;
  }

  for (nsCOMPtr<nsIContent> child = firstChild; child;
       child = child->GetNextSibling()) {
    if (FragmentFromPasteCreator::IsInsertionCookie(*child)) {
      // Yes it is! Return an error so we bubble out and short-circuit the
      // search.
      aResult = &aStart;

      child->Remove();

      return true;
    }

    if (FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie(*child,
                                                                     aResult)) {
      return true;
    }
  }

  return false;
}

class MOZ_STACK_CLASS HTMLEditor::HTMLWithContextInserter::FragmentParser
    final {
 public:
  FragmentParser(const Document& aDocument, SafeToInsertData aSafeToInsertData);

  [[nodiscard]] nsresult ParseContext(const nsAString& aContextString,
                                      DocumentFragment** aFragment);

  [[nodiscard]] nsresult ParsePastedHTML(const nsAString& aInputString,
                                         nsAtom* aContextLocalNameAtom,
                                         DocumentFragment** aFragment);

 private:
  static nsresult ParseFragment(const nsAString& aStr,
                                nsAtom* aContextLocalName,
                                const Document* aTargetDoc,
                                dom::DocumentFragment** aFragment,
                                SafeToInsertData aSafeToInsertData);

  const Document& mDocument;
  const SafeToInsertData mSafeToInsertData;
};

HTMLEditor::HTMLWithContextInserter::FragmentParser::FragmentParser(
    const Document& aDocument, SafeToInsertData aSafeToInsertData)
    : mDocument{aDocument}, mSafeToInsertData{aSafeToInsertData} {}

nsresult HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext(
    const nsAString& aContextStr, DocumentFragment** aFragment) {
  return FragmentParser::ParseFragment(aContextStr, nullptr, &mDocument,
                                       aFragment, mSafeToInsertData);
}

nsresult HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML(
    const nsAString& aInputString, nsAtom* aContextLocalNameAtom,
    DocumentFragment** aFragment) {
  return FragmentParser::ParseFragment(aInputString, aContextLocalNameAtom,
                                       &mDocument, aFragment,
                                       mSafeToInsertData);
}

nsresult HTMLEditor::HTMLWithContextInserter::CreateDOMFragmentFromPaste(
    const nsAString& aInputString, const nsAString& aContextStr,
    const nsAString& aInfoStr, nsCOMPtr<nsINode>* aOutFragNode,
    nsCOMPtr<nsINode>* aOutStartNode, nsCOMPtr<nsINode>* aOutEndNode,
    uint32_t* aOutStartOffset, uint32_t* aOutEndOffset,
    SafeToInsertData aSafeToInsertData) const {
  if (NS_WARN_IF(!aOutFragNode) || NS_WARN_IF(!aOutStartNode) ||
      NS_WARN_IF(!aOutEndNode) || NS_WARN_IF(!aOutStartOffset) ||
      NS_WARN_IF(!aOutEndOffset)) {
    return NS_ERROR_INVALID_ARG;
  }

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

  FragmentFromPasteCreator fragmentFromPasteCreator;

  const nsresult rv = fragmentFromPasteCreator.Run(
      *document, aInputString, aContextStr, aInfoStr, aOutFragNode,
      aOutStartNode, aOutEndNode, aSafeToInsertData);

  *aOutStartOffset = 0;
  *aOutEndOffset = (*aOutEndNode)->Length();

  return rv;
}

// static
nsAtom* HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
    DetermineContextLocalNameForParsingPastedHTML(
        const nsIContent* aParentContentOfPastedHTMLInContext) {
  if (!aParentContentOfPastedHTMLInContext) {
    return nsGkAtoms::body;
  }

  nsAtom* contextLocalNameAtom =
      aParentContentOfPastedHTMLInContext->NodeInfo()->NameAtom();

  return (aParentContentOfPastedHTMLInContext->IsHTMLElement(nsGkAtoms::html))
             ? nsGkAtoms::body
             : contextLocalNameAtom;
}

// static
nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
    MergeAndPostProcessFragmentsForPastedHTMLAndContext(
        DocumentFragment& aDocumentFragmentForPastedHTML,
        DocumentFragment& aDocumentFragmentForContext,
        nsIContent& aTargetContentOfContextForPastedHTML) {
  FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren(
      aDocumentFragmentForPastedHTML);

  FragmentFromPasteCreator::RemoveIncompleteDescendantsFromInsertingFragment(
      aDocumentFragmentForPastedHTML);

  // unite the two trees
  IgnoredErrorResult ignoredError;
  aTargetContentOfContextForPastedHTML.AppendChild(
      aDocumentFragmentForPastedHTML, ignoredError);
  NS_WARNING_ASSERTION(!ignoredError.Failed(),
                       "nsINode::AppendChild() failed, but ignored");
  const nsresult rv = FragmentFromPasteCreator::
      RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
          aDocumentFragmentForContext, NodesToRemove::eOnlyListItems);

  if (NS_FAILED(rv)) {
    NS_WARNING(
        "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
        "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces()"
        " failed");
    return rv;
  }

  return rv;
}

// static
nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
    PostProcessFragmentForPastedHTMLWithoutContext(
        DocumentFragment& aDocumentFragmentForPastedHTML) {
  FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren(
      aDocumentFragmentForPastedHTML);

  FragmentFromPasteCreator::RemoveIncompleteDescendantsFromInsertingFragment(
      aDocumentFragmentForPastedHTML);

  const nsresult rv = FragmentFromPasteCreator::
      RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
          aDocumentFragmentForPastedHTML, NodesToRemove::eOnlyListItems);

  if (NS_FAILED(rv)) {
    NS_WARNING(
        "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
        "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces() "
        "failed");
    return rv;
  }

  return rv;
}

// static
nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
    PreProcessContextDocumentFragmentForMerging(
        DocumentFragment& aDocumentFragmentForContext) {
  // The context is expected to contain text nodes only in block level
  // elements. Hence, if they contain only whitespace, they're invisible.
  const nsresult rv = FragmentFromPasteCreator::
      RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
          aDocumentFragmentForContext, NodesToRemove::eAll);
  if (NS_FAILED(rv)) {
    NS_WARNING(
        "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
        "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces() "
        "failed");
    return rv;
  }

  FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren(
      aDocumentFragmentForContext);

  return rv;
}

nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
    CreateDocumentFragmentAndGetParentOfPastedHTMLInContext(
        const Document& aDocument, const nsAString& aInputString,
        const nsAString& aContextStr, SafeToInsertData aSafeToInsertData,
        nsCOMPtr<nsINode>& aParentNodeOfPastedHTMLInContext,
        RefPtr<DocumentFragment>& aDocumentFragmentToInsert) const {
  // if we have context info, create a fragment for that
  RefPtr<DocumentFragment> documentFragmentForContext;

  FragmentParser fragmentParser{aDocument, aSafeToInsertData};
  if (!aContextStr.IsEmpty()) {
    nsresult rv = fragmentParser.ParseContext(
        aContextStr, getter_AddRefs(documentFragmentForContext));
    if (NS_FAILED(rv)) {
      NS_WARNING(
          "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext() "
          "failed");
      return rv;
    }
    if (!documentFragmentForContext) {
      NS_WARNING(
          "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext() "
          "returned nullptr");
      return NS_ERROR_FAILURE;
    }

    rv = FragmentFromPasteCreator::PreProcessContextDocumentFragmentForMerging(
        *documentFragmentForContext);
    if (NS_FAILED(rv)) {
      NS_WARNING(
          "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
          "PreProcessContextDocumentFragmentForMerging() failed.");
      return rv;
    }

    FragmentFromPasteCreator::
        FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie(
            *documentFragmentForContext, aParentNodeOfPastedHTMLInContext);
    MOZ_ASSERT(aParentNodeOfPastedHTMLInContext);
  }

  nsCOMPtr<nsIContent> parentContentOfPastedHTMLInContext =
      nsIContent::FromNodeOrNull(aParentNodeOfPastedHTMLInContext);
  MOZ_ASSERT_IF(aParentNodeOfPastedHTMLInContext,
                parentContentOfPastedHTMLInContext);

  nsAtom* contextLocalNameAtom =
      FragmentFromPasteCreator::DetermineContextLocalNameForParsingPastedHTML(
          parentContentOfPastedHTMLInContext);
  RefPtr<DocumentFragment> documentFragmentForPastedHTML;
  nsresult rv = fragmentParser.ParsePastedHTML(
      aInputString, contextLocalNameAtom,
      getter_AddRefs(documentFragmentForPastedHTML));
  if (NS_FAILED(rv)) {
    NS_WARNING(
        "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML()"
        " failed");
    return rv;
  }
  if (!documentFragmentForPastedHTML) {
    NS_WARNING(
        "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML()"
        " returned nullptr");
    return NS_ERROR_FAILURE;
  }

  if (aParentNodeOfPastedHTMLInContext) {
    const nsresult rv = FragmentFromPasteCreator::
        MergeAndPostProcessFragmentsForPastedHTMLAndContext(
            *documentFragmentForPastedHTML, *documentFragmentForContext,
            *parentContentOfPastedHTMLInContext);
    if (NS_FAILED(rv)) {
      NS_WARNING(
          "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
          "MergeAndPostProcessFragmentsForPastedHTMLAndContext() failed.");
      return rv;
    }
    aDocumentFragmentToInsert = std::move(documentFragmentForContext);
  } else {
    const nsresult rv = PostProcessFragmentForPastedHTMLWithoutContext(
        *documentFragmentForPastedHTML);
    if (NS_FAILED(rv)) {
      NS_WARNING(
          "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
          "PostProcessFragmentForPastedHTMLWithoutContext() failed.");
      return rv;
    }

    aDocumentFragmentToInsert = std::move(documentFragmentForPastedHTML);
  }

  return rv;
}

nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::Run(
    const Document& aDocument, const nsAString& aInputString,
    const nsAString& aContextStr, const nsAString& aInfoStr,
    nsCOMPtr<nsINode>* aOutFragNode, nsCOMPtr<nsINode>* aOutStartNode,
    nsCOMPtr<nsINode>* aOutEndNode, SafeToInsertData aSafeToInsertData) const {
  MOZ_ASSERT(aOutFragNode);
  MOZ_ASSERT(aOutStartNode);
  MOZ_ASSERT(aOutEndNode);

  nsCOMPtr<nsINode> parentNodeOfPastedHTMLInContext;
  RefPtr<DocumentFragment> documentFragmentToInsert;
  nsresult rv = CreateDocumentFragmentAndGetParentOfPastedHTMLInContext(
      aDocument, aInputString, aContextStr, aSafeToInsertData,
      parentNodeOfPastedHTMLInContext, documentFragmentToInsert);
  if (NS_FAILED(rv)) {
    NS_WARNING(
        "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
        "CreateDocumentFragmentAndGetParentOfPastedHTMLInContext() failed.");
    return rv;
  }

  // If there was no context, then treat all of the data we did get as the
  // pasted data.
  if (parentNodeOfPastedHTMLInContext) {
    *aOutEndNode = *aOutStartNode = parentNodeOfPastedHTMLInContext;
  } else {
    *aOutEndNode = *aOutStartNode = documentFragmentToInsert;
  }

  *aOutFragNode = std::move(documentFragmentToInsert);

  if (!aInfoStr.IsEmpty()) {
    const nsresult rv =
        FragmentFromPasteCreator::MoveStartAndEndAccordingToHTMLInfo(
            aInfoStr, aOutStartNode, aOutEndNode);
    if (NS_FAILED(rv)) {
      NS_WARNING(
          "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
          "MoveStartAndEndAccordingToHTMLInfo() failed");
      return rv;
    }
  }

  return NS_OK;
}

// static
nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
    MoveStartAndEndAccordingToHTMLInfo(const nsAString& aInfoStr,
                                       nsCOMPtr<nsINode>* aOutStartNode,
                                       nsCOMPtr<nsINode>* aOutEndNode) {
  int32_t sep = aInfoStr.FindChar((char16_t)',');
  nsAutoString numstr1(Substring(aInfoStr, 0, sep));
  nsAutoString numstr2(
      Substring(aInfoStr, sep + 1, aInfoStr.Length() - (sep + 1)));

  // Move the start and end children.
  nsresult rvIgnored;
  int32_t num = numstr1.ToInteger(&rvIgnored);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                       "nsAString::ToInteger() failed, but ignored");
  while (num--) {
    nsINode* tmp = (*aOutStartNode)->GetFirstChild();
    if (!tmp) {
      NS_WARNING("aOutStartNode did not have children");
      return NS_ERROR_FAILURE;
    }
    *aOutStartNode = tmp;
  }

  num = numstr2.ToInteger(&rvIgnored);
  while (num--) {
    nsINode* tmp = (*aOutEndNode)->GetLastChild();
    if (!tmp) {
      NS_WARNING("aOutEndNode did not have children");
      return NS_ERROR_FAILURE;
    }
    *aOutEndNode = tmp;
  }

  return NS_OK;
}

// static
nsresult HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseFragment(
    const nsAString& aFragStr, nsAtom* aContextLocalName,
    const Document* aTargetDocument, DocumentFragment** aFragment,
    SafeToInsertData aSafeToInsertData) {
  nsAutoScriptBlockerSuppressNodeRemoved autoBlocker;

  nsCOMPtr<Document> doc =
      nsContentUtils::CreateInertHTMLDocument(aTargetDocument);
  if (!doc) {
    return NS_ERROR_FAILURE;
  }
  RefPtr<DocumentFragment> fragment =
      new (doc->NodeInfoManager()) DocumentFragment(doc->NodeInfoManager());
  nsresult rv = nsContentUtils::ParseFragmentHTML(
      aFragStr, fragment,
      aContextLocalName ? aContextLocalName : nsGkAtoms::body,
      kNameSpaceID_XHTML, falsetrue);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "nsContentUtils::ParseFragmentHTML() failed");
  if (aSafeToInsertData == SafeToInsertData::No) {
    nsTreeSanitizer sanitizer(aContextLocalName
                                  ? nsIParserUtils::SanitizerAllowStyle
                                  : nsIParserUtils::SanitizerAllowComments);
    sanitizer.Sanitize(fragment);
  }
  fragment.forget(aFragment);
  return rv;
}

// static
void HTMLEditor::HTMLWithContextInserter::
    CollectTopMostChildContentsCompletelyInRange(
        const EditorRawDOMPoint& aStartPoint,
        const EditorRawDOMPoint& aEndPoint,
        nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents) {
  MOZ_ASSERT(aStartPoint.IsSetAndValid());
  MOZ_ASSERT(aEndPoint.IsSetAndValid());

  RefPtr<nsRange> range =
      nsRange::Create(aStartPoint.ToRawRangeBoundary(),
                      aEndPoint.ToRawRangeBoundary(), IgnoreErrors());
  if (!range) {
    NS_WARNING("nsRange::Create() failed");
    return;
  }
  DOMSubtreeIterator iter;
  if (NS_FAILED(iter.Init(*range))) {
    NS_WARNING("DOMSubtreeIterator::Init() failed, but ignored");
    return;
  }

  iter.AppendAllNodesToArray(aOutArrayOfContents);
}

/******************************************************************************
 * HTMLEditor::AutoHTMLFragmentBoundariesFixer
 ******************************************************************************/


HTMLEditor::AutoHTMLFragmentBoundariesFixer::AutoHTMLFragmentBoundariesFixer(
    nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents) {
  EnsureBeginsOrEndsWithValidContent(StartOrEnd::start,
                                     aArrayOfTopMostChildContents);
  EnsureBeginsOrEndsWithValidContent(StartOrEnd::end,
                                     aArrayOfTopMostChildContents);
}

// static
void HTMLEditor::AutoHTMLFragmentBoundariesFixer::
    CollectTableAndAnyListElementsOfInclusiveAncestorsAt(
        nsIContent& aContent,
        nsTArray<OwningNonNull<Element>>& aOutArrayOfListAndTableElements) {
  for (Element* element = aContent.GetAsElementOrParentElement(); element;
       element = element->GetParentElement()) {
    if (HTMLEditUtils::IsAnyListElement(element) ||
        HTMLEditUtils::IsTable(element)) {
      aOutArrayOfListAndTableElements.AppendElement(*element);
    }
  }
}

// static
Element* HTMLEditor::AutoHTMLFragmentBoundariesFixer::
    GetMostDistantAncestorListOrTableElement(
        const nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents,
        const nsTArray<OwningNonNull<Element>>&
            aInclusiveAncestorsTableOrListElements) {
  Element* lastFoundAncestorListOrTableElement = nullptr;
  for (auto& content : aArrayOfTopMostChildContents) {
    if (HTMLEditUtils::IsAnyTableElementButNotTable(content)) {
      Element* tableElement =
          HTMLEditUtils::GetClosestAncestorTableElement(*content);
      if (!tableElement) {
        continue;
      }
      // If we find a `<table>` element which is an ancestor of a table
      // related element and is not an acestor of first nor last of
      // aArrayOfNodes, return the last found list or `<table>` element.
      // XXX Is that really expected that this returns a list element in this
      //     case?
      if (!aInclusiveAncestorsTableOrListElements.Contains(tableElement)) {
        return lastFoundAncestorListOrTableElement;
      }
      // If we find a `<table>` element which is topmost list or `<table>`
      // element at first or last of aArrayOfNodes, return it.
      if (aInclusiveAncestorsTableOrListElements.LastElement().get() ==
          tableElement) {
        return tableElement;
      }
      // Otherwise, store the `<table>` element which is an ancestor but
      // not topmost ancestor of first or last of aArrayOfNodes.
      lastFoundAncestorListOrTableElement = tableElement;
      continue;
    }

    if (!HTMLEditUtils::IsListItem(content)) {
      continue;
    }
    Element* listElement =
        HTMLEditUtils::GetClosestAncestorAnyListElement(*content);
    if (!listElement) {
      continue;
    }
    // If we find a list element which is ancestor of a list item element and
    // is not an acestor of first nor last of aArrayOfNodes, return the last
    // found list or `<table>` element.
    // XXX Is that really expected that this returns a `<table>` element in
    //     this case?
    if (!aInclusiveAncestorsTableOrListElements.Contains(listElement)) {
      return lastFoundAncestorListOrTableElement;
    }
    // If we find a list element which is topmost list or `<table>` element at
    // first or last of aArrayOfNodes, return it.
    if (aInclusiveAncestorsTableOrListElements.LastElement().get() ==
        listElement) {
      return listElement;
    }
    // Otherwise, store the list element which is an ancestor but not topmost
    // ancestor of first or last of aArrayOfNodes.
    lastFoundAncestorListOrTableElement = listElement;
  }

  // If we find only non-topmost list or `<table>` element, returns the last
  // found one (meaning bottommost one).  Otherwise, nullptr.
  return lastFoundAncestorListOrTableElement;
}

Element*
HTMLEditor::AutoHTMLFragmentBoundariesFixer::FindReplaceableTableElement(
    Element& aTableElement, nsIContent& aContentMaybeInTableElement) const {
  MOZ_ASSERT(aTableElement.IsHTMLElement(nsGkAtoms::table));
  // Perhaps, this is designed for climbing up the DOM tree from
  // aContentMaybeInTableElement to aTableElement and making sure that
  // aContentMaybeInTableElement itself or its ancestor is a `<td>`, `<th>`,
  // `<tr>`, `<thead>`, `<tbody>`, `<tfoot>` or `<caption>`.
  // But this looks really buggy because this loop may skip aTableElement
  // as the following NS_ASSERTION.  We should write automated tests and
  // check right behavior.
  for (Element* element =
           aContentMaybeInTableElement.GetAsElementOrParentElement();
       element; element = element->GetParentElement()) {
    if (!HTMLEditUtils::IsAnyTableElement(element) ||
        element->IsHTMLElement(nsGkAtoms::table)) {
      // XXX Perhaps, the original developer of this method assumed that
      //     aTableElement won't be skipped because if it's assumed, we can
      //     stop climbing up the tree in that case.
      NS_ASSERTION(element != &aTableElement,
                   "The table element which is looking for is ignored");
      continue;
    }
    Element* tableElement = nullptr;
    for (Element* maybeTableElement = element->GetParentElement();
         maybeTableElement;
         maybeTableElement = maybeTableElement->GetParentElement()) {
      if (maybeTableElement->IsHTMLElement(nsGkAtoms::table)) {
        tableElement = maybeTableElement;
        break;
      }
    }
    if (tableElement == &aTableElement) {
      return element;
    }
    // XXX If we find another `<table>` element, why don't we keep searching
    //     from its parent?
  }
  return nullptr;
}

bool HTMLEditor::AutoHTMLFragmentBoundariesFixer::IsReplaceableListElement(
    Element& aListElement, nsIContent& aContentMaybeInListElement) const {
  MOZ_ASSERT(HTMLEditUtils::IsAnyListElement(&aListElement));
  // Perhaps, this is designed for climbing up the DOM tree from
  // aContentMaybeInListElement to aListElement and making sure that
  // aContentMaybeInListElement itself or its ancestor is an list item.
  // But this looks really buggy because this loop may skip aListElement
  // as the following NS_ASSERTION.  We should write automated tests and
  // check right behavior.
  for (Element* element =
           aContentMaybeInListElement.GetAsElementOrParentElement();
       element; element = element->GetParentElement()) {
    if (!HTMLEditUtils::IsListItem(element)) {
      // XXX Perhaps, the original developer of this method assumed that
      //     aListElement won't be skipped because if it's assumed, we can
      //     stop climbing up the tree in that case.
      NS_ASSERTION(element != &aListElement,
                   "The list element which is looking for is ignored");
      continue;
    }
    Element* listElement =
        HTMLEditUtils::GetClosestAncestorAnyListElement(*element);
    if (listElement == &aListElement) {
      return true;
    }
    // XXX If we find another list element, why don't we keep searching
    //     from its parent?
  }
  return false;
}

void HTMLEditor::AutoHTMLFragmentBoundariesFixer::
    EnsureBeginsOrEndsWithValidContent(StartOrEnd aStartOrEnd,
                                       nsTArray<OwningNonNull<nsIContent>>&
                                           aArrayOfTopMostChildContents) const {
  MOZ_ASSERT(!aArrayOfTopMostChildContents.IsEmpty());

  // Collect list elements and table related elements at first or last node
  // in aArrayOfTopMostChildContents.
  AutoTArray<OwningNonNull<Element>, 4> inclusiveAncestorsListOrTableElements;
  CollectTableAndAnyListElementsOfInclusiveAncestorsAt(
      aStartOrEnd == StartOrEnd::end
          ? aArrayOfTopMostChildContents.LastElement()
          : aArrayOfTopMostChildContents[0],
      inclusiveAncestorsListOrTableElements);
  if (inclusiveAncestorsListOrTableElements.IsEmpty()) {
    return;
  }

  // Get most ancestor list or `<table>` element in
  // inclusiveAncestorsListOrTableElements which contains earlier
  // node in aArrayOfTopMostChildContents as far as possible.
  // XXX With inclusiveAncestorsListOrTableElements, this returns a
  //     list or `<table>` element which contains first or last node of
  //     aArrayOfTopMostChildContents.  However, this seems slow when
  //     aStartOrEnd is StartOrEnd::end and only the last node is in
  //     different list or `<table>`.  But I'm not sure whether it's
  //     possible case or not.  We need to add tests to
  //     test_content_iterator_subtree.html for checking how
  //     SubtreeContentIterator works.
  Element* listOrTableElement = GetMostDistantAncestorListOrTableElement(
      aArrayOfTopMostChildContents, inclusiveAncestorsListOrTableElements);
  if (!listOrTableElement) {
    return;
  }

  // If we have pieces of tables or lists to be inserted, let's force the
  // insertion to deal with table elements right away, so that it doesn't
  // orphan some table or list contents outside the table or list.

  OwningNonNull<nsIContent>& firstOrLastChildContent =
      aStartOrEnd == StartOrEnd::end
          ? aArrayOfTopMostChildContents.LastElement()
          : aArrayOfTopMostChildContents[0];

  // Find substructure of list or table that must be included in paste.
  Element* replaceElement;
  if (HTMLEditUtils::IsAnyListElement(listOrTableElement)) {
    if (!IsReplaceableListElement(*listOrTableElement,
                                  firstOrLastChildContent)) {
      return;
    }
    replaceElement = listOrTableElement;
  } else {
    MOZ_ASSERT(listOrTableElement->IsHTMLElement(nsGkAtoms::table));
    replaceElement = FindReplaceableTableElement(*listOrTableElement,
                                                 firstOrLastChildContent);
    if (!replaceElement) {
      return;
    }
  }

  // If we can replace the given list element or found a table related element
  // in the `<table>` element, insert it into aArrayOfTopMostChildContents which
  // is tompost children to be inserted instead of descendants of them in
  // aArrayOfTopMostChildContents.
  for (size_t i = 0; i < aArrayOfTopMostChildContents.Length();) {
    OwningNonNull<nsIContent>& content = aArrayOfTopMostChildContents[i];
    if (content == replaceElement) {
      // If the element is n aArrayOfTopMostChildContents, its descendants must
      // not be in the array.  Therefore, we don't need to optimize this case.
      // XXX Perhaps, we can break this loop right now.
      aArrayOfTopMostChildContents.RemoveElementAt(i);
      continue;
    }
    if (!EditorUtils::IsDescendantOf(content, *replaceElement)) {
      i++;
      continue;
    }
    // For saving number of calls of EditorUtils::IsDescendantOf(), we should
    // remove its siblings in the array.
    nsIContent* parent = content->GetParent();
    aArrayOfTopMostChildContents.RemoveElementAt(i);
    while (i < aArrayOfTopMostChildContents.Length() &&
           aArrayOfTopMostChildContents[i]->GetParent() == parent) {
      aArrayOfTopMostChildContents.RemoveElementAt(i);
    }
  }

  // Now replace the removed nodes with the structural parent
  if (aStartOrEnd == StartOrEnd::end) {
    aArrayOfTopMostChildContents.AppendElement(*replaceElement);
  } else {
    aArrayOfTopMostChildContents.InsertElementAt(0, *replaceElement);
  }
}

}  // namespace mozilla

Messung V0.5 in Prozent
C=90 H=96 G=93

¤ Dauer der Verarbeitung: 0.84 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© 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 und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge