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

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),
--> --------------------

--> maximum size reached

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

96%


¤ Dauer der Verarbeitung: 0.21 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.