tor-browser

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

HTMLEditorDataTransfer.cpp (183956B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=2 sw=2 et tw=78: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "HTMLEditor.h"
      8 
      9 #include <string.h>
     10 
     11 #include "AutoSelectionRestorer.h"
     12 #include "EditAction.h"
     13 #include "EditorBase.h"
     14 #include "EditorDOMPoint.h"
     15 #include "EditorUtils.h"
     16 #include "HTMLEditHelpers.h"
     17 #include "HTMLEditUtils.h"
     18 #include "InternetCiter.h"
     19 #include "PendingStyles.h"
     20 #include "SelectionState.h"
     21 #include "WhiteSpaceVisibilityKeeper.h"
     22 #include "WSRunScanner.h"
     23 
     24 #include "ErrorList.h"
     25 #include "mozilla/dom/Comment.h"
     26 #include "mozilla/dom/DataTransfer.h"
     27 #include "mozilla/dom/Document.h"
     28 #include "mozilla/dom/DocumentFragment.h"
     29 #include "mozilla/dom/DOMException.h"
     30 #include "mozilla/dom/DOMStringList.h"
     31 #include "mozilla/dom/DOMStringList.h"
     32 #include "mozilla/dom/Element.h"
     33 #include "mozilla/dom/ElementInlines.h"
     34 #include "mozilla/dom/Event.h"
     35 #include "mozilla/dom/FileBlobImpl.h"
     36 #include "mozilla/dom/FileReader.h"
     37 #include "mozilla/dom/Selection.h"
     38 #include "mozilla/dom/StaticRange.h"
     39 #include "mozilla/dom/WorkerRef.h"
     40 #include "mozilla/Attributes.h"
     41 #include "mozilla/Base64.h"
     42 #include "mozilla/BasicEvents.h"
     43 #include "mozilla/DebugOnly.h"
     44 #include "mozilla/Maybe.h"
     45 #include "mozilla/OwningNonNull.h"
     46 #include "mozilla/Preferences.h"
     47 #include "mozilla/Result.h"
     48 #include "mozilla/StaticPrefs_editor.h"
     49 #include "mozilla/TextComposition.h"
     50 #include "nsAString.h"
     51 #include "nsCOMPtr.h"
     52 #include "nsCRTGlue.h"  // for CRLF
     53 #include "nsComponentManagerUtils.h"
     54 #include "nsIScriptError.h"
     55 #include "nsContentUtils.h"
     56 #include "nsDebug.h"
     57 #include "nsDependentSubstring.h"
     58 #include "nsError.h"
     59 #include "nsFocusManager.h"
     60 #include "nsGkAtoms.h"
     61 #include "nsIClipboard.h"
     62 #include "nsIContent.h"
     63 #include "nsIDocumentEncoder.h"
     64 #include "nsIFile.h"
     65 #include "nsIInputStream.h"
     66 #include "nsIMIMEService.h"
     67 #include "nsINode.h"
     68 #include "nsIParserUtils.h"
     69 #include "nsIPrincipal.h"
     70 #include "nsISupportsImpl.h"
     71 #include "nsISupportsPrimitives.h"
     72 #include "nsISupportsUtils.h"
     73 #include "nsITransferable.h"
     74 #include "nsIVariant.h"
     75 #include "nsLinebreakConverter.h"
     76 #include "nsLiteralString.h"
     77 #include "nsNameSpaceManager.h"
     78 #include "nsNetUtil.h"
     79 #include "nsPrintfCString.h"
     80 #include "nsRange.h"
     81 #include "nsReadableUtils.h"
     82 #include "nsServiceManagerUtils.h"
     83 #include "nsStreamUtils.h"
     84 #include "nsString.h"
     85 #include "nsStringFwd.h"
     86 #include "nsStringIterator.h"
     87 #include "nsTreeSanitizer.h"
     88 #include "nsXPCOM.h"
     89 #include "nscore.h"
     90 #include "nsContentUtils.h"
     91 #include "nsQueryObject.h"
     92 
     93 class nsAtom;
     94 class nsILoadContext;
     95 
     96 namespace mozilla {
     97 
     98 using namespace dom;
     99 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
    100 using LeafNodeType = HTMLEditUtils::LeafNodeType;
    101 
    102 #define kInsertCookie "_moz_Insert Here_moz_"
    103 
    104 // some little helpers
    105 static bool FindIntegerAfterString(const char* aLeadingString,
    106                                   const nsCString& aCStr,
    107                                   int32_t& foundNumber);
    108 static void RemoveFragComments(nsCString& aStr);
    109 
    110 nsresult HTMLEditor::InsertDroppedDataTransferAsAction(
    111    AutoEditActionDataSetter& aEditActionData, DataTransfer& aDataTransfer,
    112    const EditorDOMPoint& aDroppedAt, nsIPrincipal* aSourcePrincipal) {
    113  MOZ_ASSERT(aEditActionData.GetEditAction() == EditAction::eDrop);
    114  MOZ_ASSERT(GetEditAction() == EditAction::eDrop);
    115  MOZ_ASSERT(aDroppedAt.IsSet());
    116  MOZ_ASSERT(aDataTransfer.MozItemCount() > 0);
    117 
    118  if (IsReadonly()) {
    119    return NS_OK;
    120  }
    121 
    122  aEditActionData.InitializeDataTransfer(&aDataTransfer);
    123  RefPtr<StaticRange> targetRange = StaticRange::Create(
    124      aDroppedAt.GetContainer(), aDroppedAt.Offset(), aDroppedAt.GetContainer(),
    125      aDroppedAt.Offset(), IgnoreErrors());
    126  NS_WARNING_ASSERTION(targetRange && targetRange->IsPositioned(),
    127                       "Why did we fail to create collapsed static range at "
    128                       "dropped position?");
    129  if (targetRange && targetRange->IsPositioned()) {
    130    aEditActionData.AppendTargetRange(*targetRange);
    131  }
    132  nsresult rv = aEditActionData.MaybeDispatchBeforeInputEvent();
    133  if (NS_FAILED(rv)) {
    134    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
    135                         "MaybeDispatchBeforeInputEvent() failed");
    136    return rv;
    137  }
    138 
    139  if (MOZ_UNLIKELY(!aDroppedAt.IsInContentNode())) {
    140    NS_WARNING("Dropped into non-content node");
    141    return NS_OK;
    142  }
    143 
    144  const RefPtr<Element> editingHost = ComputeEditingHost(
    145      *aDroppedAt.ContainerAs<nsIContent>(), LimitInBodyElement::No);
    146  if (MOZ_UNLIKELY(!editingHost)) {
    147    NS_WARNING("Dropped onto non-editable node");
    148    return NS_OK;
    149  }
    150 
    151  uint32_t numItems = aDataTransfer.MozItemCount();
    152  for (uint32_t i = 0; i < numItems; ++i) {
    153    DebugOnly<nsresult> rvIgnored =
    154        InsertFromDataTransfer(&aDataTransfer, i, aSourcePrincipal, aDroppedAt,
    155                               DeleteSelectedContent::No, *editingHost);
    156    if (NS_WARN_IF(Destroyed())) {
    157      return NS_OK;
    158    }
    159    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
    160                         "HTMLEditor::InsertFromDataTransfer("
    161                         "DeleteSelectedContent::No) failed, but ignored");
    162  }
    163  return NS_OK;
    164 }
    165 
    166 nsresult HTMLEditor::LoadHTML(const nsAString& aInputString) {
    167  MOZ_ASSERT(IsEditActionDataAvailable());
    168 
    169  if (NS_WARN_IF(!mInitSucceeded)) {
    170    return NS_ERROR_NOT_INITIALIZED;
    171  }
    172 
    173  // force IME commit; set up rules sniffing and batching
    174  DebugOnly<nsresult> rvIgnored = CommitComposition();
    175  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
    176                       "EditorBase::CommitComposition() failed, but ignored");
    177  if (NS_WARN_IF(Destroyed())) {
    178    return NS_ERROR_EDITOR_DESTROYED;
    179  }
    180 
    181  AutoPlaceholderBatch treatAsOneTransaction(
    182      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
    183  IgnoredErrorResult ignoredError;
    184  AutoEditSubActionNotifier startToHandleEditSubAction(
    185      *this, EditSubAction::eInsertHTMLSource, nsIEditor::eNext, ignoredError);
    186  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
    187    return ignoredError.StealNSResult();
    188  }
    189  NS_WARNING_ASSERTION(
    190      !ignoredError.Failed(),
    191      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
    192 
    193  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
    194  if (NS_FAILED(rv)) {
    195    NS_WARNING("EditorBase::EnsureNoPaddingBRElementForEmptyEditor() failed");
    196    return rv;
    197  }
    198 
    199  // Delete Selection, but only if it isn't collapsed, see bug #106269
    200  if (!SelectionRef().IsCollapsed()) {
    201    nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip);
    202    if (NS_FAILED(rv)) {
    203      NS_WARNING(
    204          "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
    205      return rv;
    206    }
    207  }
    208 
    209  // Get the first range in the selection, for context:
    210  RefPtr<const nsRange> range = SelectionRef().GetRangeAt(0);
    211  if (NS_WARN_IF(!range)) {
    212    return NS_ERROR_FAILURE;
    213  }
    214 
    215  // Create fragment for pasted HTML.
    216  ErrorResult error;
    217  RefPtr<DocumentFragment> documentFragment =
    218      range->CreateContextualFragment(aInputString, error);
    219  if (error.Failed()) {
    220    NS_WARNING("nsRange::CreateContextualFragment() failed");
    221    return error.StealNSResult();
    222  }
    223 
    224  // Put the fragment into the document at start of selection.
    225  EditorDOMPoint pointToInsert(range->StartRef());
    226  // XXX We need to make pointToInsert store offset for keeping traditional
    227  //     behavior since using only child node to pointing insertion point
    228  //     changes the behavior when inserted child is moved by mutation
    229  //     observer.  We need to investigate what we should do here.
    230  (void)pointToInsert.Offset();
    231  EditorDOMPoint pointToPutCaret;
    232  for (nsCOMPtr<nsIContent> contentToInsert = documentFragment->GetFirstChild();
    233       contentToInsert; contentToInsert = documentFragment->GetFirstChild()) {
    234    Result<CreateContentResult, nsresult> insertChildContentNodeResult =
    235        InsertNodeWithTransaction(*contentToInsert, pointToInsert);
    236    if (MOZ_UNLIKELY(insertChildContentNodeResult.isErr())) {
    237      NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
    238      return insertChildContentNodeResult.unwrapErr();
    239    }
    240    CreateContentResult unwrappedInsertChildContentNodeResult =
    241        insertChildContentNodeResult.unwrap();
    242    unwrappedInsertChildContentNodeResult.MoveCaretPointTo(
    243        pointToPutCaret, *this,
    244        {SuggestCaret::OnlyIfHasSuggestion,
    245         SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
    246    // XXX If the inserted node has been moved by mutation observer,
    247    //     incrementing offset will cause odd result.  Next new node
    248    //     will be inserted after existing node and the offset will be
    249    //     overflown from the container node.
    250    pointToInsert.Set(pointToInsert.GetContainer(), pointToInsert.Offset() + 1);
    251    if (NS_WARN_IF(!pointToInsert.Offset())) {
    252      // Append the remaining children to the container if offset is
    253      // overflown.
    254      pointToInsert.SetToEndOf(pointToInsert.GetContainer());
    255    }
    256  }
    257 
    258  if (pointToPutCaret.IsSet()) {
    259    nsresult rv = CollapseSelectionTo(pointToPutCaret);
    260    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
    261      NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored");
    262      return NS_ERROR_EDITOR_DESTROYED;
    263    }
    264    NS_WARNING_ASSERTION(
    265        NS_SUCCEEDED(rv),
    266        "EditorBase::CollapseSelectionTo() failed, but ignored");
    267  }
    268 
    269  return NS_OK;
    270 }
    271 
    272 NS_IMETHODIMP HTMLEditor::InsertHTML(const nsAString& aInString) {
    273  nsresult rv = InsertHTMLAsAction(aInString);
    274  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    275                       "HTMLEditor::InsertHTMLAsAction() failed");
    276  return rv;
    277 }
    278 
    279 nsresult HTMLEditor::InsertHTMLAsAction(const nsAString& aInString,
    280                                        nsIPrincipal* aPrincipal) {
    281  // FIXME: This should keep handling inserting HTML if the caller is
    282  // nsIHTMLEditor::InsertHTML.
    283  if (IsReadonly()) {
    284    return NS_OK;
    285  }
    286 
    287  AutoEditActionDataSetter editActionData(*this, EditAction::eInsertHTML,
    288                                          aPrincipal);
    289  nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
    290  if (NS_FAILED(rv)) {
    291    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
    292                         "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
    293    return EditorBase::ToGenericNSResult(rv);
    294  }
    295 
    296  const RefPtr<Element> editingHost =
    297      ComputeEditingHost(LimitInBodyElement::No);
    298  if (NS_WARN_IF(!editingHost)) {
    299    return NS_ERROR_FAILURE;
    300  }
    301 
    302  if (editingHost->IsContentEditablePlainTextOnly()) {
    303    nsAutoString plaintextString;
    304    nsresult rv = nsContentUtils::ConvertToPlainText(
    305        aInString, plaintextString, nsIDocumentEncoder::OutputLFLineBreak,
    306        0u /* never wrap lines*/);
    307    if (NS_FAILED(rv)) {
    308      NS_WARNING("nsContentUtils::ConvertToPlainText() failed");
    309      return EditorBase::ToGenericNSResult(rv);
    310    }
    311    Maybe<AutoPlaceholderBatch> treatAsOneTransaction;
    312    const auto EnsureAutoPlaceholderBatch = [&]() {
    313      if (treatAsOneTransaction.isNothing()) {
    314        treatAsOneTransaction.emplace(*this, ScrollSelectionIntoView::Yes,
    315                                      __FUNCTION__);
    316      }
    317    };
    318    if (mComposition &&
    319        mComposition->CanRequsetIMEToCommitOrCancelComposition()) {
    320      EnsureAutoPlaceholderBatch();
    321      CommitComposition();
    322      if (NS_WARN_IF(Destroyed())) {
    323        return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
    324      }
    325      if (NS_WARN_IF(editingHost !=
    326                     ComputeEditingHost(LimitInBodyElement::No))) {
    327        return EditorBase::ToGenericNSResult(
    328            NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
    329      }
    330    }
    331    if (MOZ_LIKELY(!plaintextString.IsEmpty())) {
    332      EnsureAutoPlaceholderBatch();
    333      rv = InsertTextAsSubAction(plaintextString, InsertTextFor::NormalText);
    334      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    335                           "EditorBase::InsertTextAsSubAction() failed");
    336    } else if (!SelectionRef().IsCollapsed()) {
    337      EnsureAutoPlaceholderBatch();
    338      rv = DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
    339      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    340                           "EditorBase::DeleteSelectionAsSubAction() failed");
    341    }
    342    return EditorBase::ToGenericNSResult(rv);
    343  }
    344  AutoPlaceholderBatch treatAsOneTransaction(
    345      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
    346  rv = InsertHTMLWithContextAsSubAction(
    347      aInString, u""_ns, u""_ns, u""_ns, SafeToInsertData::Yes,
    348      EditorDOMPoint(), DeleteSelectedContent::Yes,
    349      InlineStylesAtInsertionPoint::Clear, *editingHost);
    350  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    351                       "HTMLEditor::InsertHTMLWithContextAsSubAction("
    352                       "SafeToInsertData::Yes, DeleteSelectedContent::Yes, "
    353                       "InlineStylesAtInsertionPoint::Clear) failed");
    354  return EditorBase::ToGenericNSResult(rv);
    355 }
    356 
    357 class MOZ_STACK_CLASS HTMLEditor::HTMLWithContextInserter final {
    358 public:
    359  MOZ_CAN_RUN_SCRIPT HTMLWithContextInserter(HTMLEditor& aHTMLEditor,
    360                                             const Element& aEditingHost)
    361      : mHTMLEditor(aHTMLEditor), mEditingHost(aEditingHost) {}
    362 
    363  HTMLWithContextInserter() = delete;
    364  HTMLWithContextInserter(const HTMLWithContextInserter&) = delete;
    365  HTMLWithContextInserter(HTMLWithContextInserter&&) = delete;
    366 
    367  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run(
    368      const nsAString& aInputString, const nsAString& aContextStr,
    369      const nsAString& aInfoStr, SafeToInsertData aSafeToInsertData,
    370      InlineStylesAtInsertionPoint aInlineStylesAtInsertionPoint);
    371 
    372 private:
    373  class FragmentFromPasteCreator;
    374  class FragmentParser;
    375  /**
    376   * CollectTopMostChildContentsCompletelyInRange() collects topmost child
    377   * contents which are completely in the given range.
    378   * For example, if the range points a node with its container node, the
    379   * result is only the node (meaning does not include its descendants).
    380   * If the range starts start of a node and ends end of it, and if the node
    381   * does not have children, returns no nodes, otherwise, if the node has
    382   * some children, the result includes its all children (not including their
    383   * descendants).
    384   *
    385   * @param aStartPoint         Start point of the range.
    386   * @param aEndPoint           End point of the range.
    387   * @param aOutArrayOfContents [Out] Topmost children which are completely in
    388   *                            the range.
    389   */
    390  static void CollectTopMostChildContentsCompletelyInRange(
    391      const EditorRawDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint,
    392      nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents);
    393 
    394  EditorDOMPoint GetNewCaretPointAfterInsertingHTML(
    395      const EditorDOMPoint& aLastInsertedPoint) const;
    396 
    397  /**
    398   * Insert nodes in aArrayOfTopMostChildContents or their children to
    399   * aPointToInsert (if the container is not a proper parent of inserting node,
    400   * this splits the ancestors).
    401   */
    402  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateContentResult, nsresult>
    403  InsertContents(
    404      const EditorDOMPoint& aPointToInsert,
    405      nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents,
    406      const nsINode* aFragmentAsNode);
    407 
    408  /**
    409   * @param aContextStr as indicated by nsITransferable's kHTMLContext.
    410   * @param aInfoStr as indicated by nsITransferable's kHTMLInfo.
    411   */
    412  nsresult CreateDOMFragmentFromPaste(
    413      const nsAString& aInputString, const nsAString& aContextStr,
    414      const nsAString& aInfoStr, nsCOMPtr<nsINode>* aOutFragNode,
    415      nsCOMPtr<nsINode>* aOutStartNode, nsCOMPtr<nsINode>* aOutEndNode,
    416      uint32_t* aOutStartOffset, uint32_t* aOutEndOffset,
    417      SafeToInsertData aSafeToInsertData) const;
    418 
    419  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult MoveCaretOutsideOfLink(
    420      Element& aLinkElement, const EditorDOMPoint& aPointToPutCaret);
    421 
    422  // MOZ_KNOWN_LIVE because this is set only by the constructor which is
    423  // marked as MOZ_CAN_RUN_SCRIPT and this is allocated only in the stack.
    424  MOZ_KNOWN_LIVE HTMLEditor& mHTMLEditor;
    425  MOZ_KNOWN_LIVE const Element& mEditingHost;
    426 };
    427 
    428 class MOZ_STACK_CLASS
    429 HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator final {
    430 public:
    431  nsresult Run(const Document& aDocument, const nsAString& aInputString,
    432               const nsAString& aContextStr, const nsAString& aInfoStr,
    433               nsCOMPtr<nsINode>* aOutFragNode,
    434               nsCOMPtr<nsINode>* aOutStartNode, nsCOMPtr<nsINode>* aOutEndNode,
    435               SafeToInsertData aSafeToInsertData) const;
    436 
    437 private:
    438  nsresult CreateDocumentFragmentAndGetParentOfPastedHTMLInContext(
    439      const Document& aDocument, const nsAString& aInputString,
    440      const nsAString& aContextStr, SafeToInsertData aSafeToInsertData,
    441      nsCOMPtr<nsINode>& aParentNodeOfPastedHTMLInContext,
    442      RefPtr<DocumentFragment>& aDocumentFragmentToInsert) const;
    443 
    444  static nsAtom* DetermineContextLocalNameForParsingPastedHTML(
    445      const nsIContent* aParentContentOfPastedHTMLInContext);
    446 
    447  static bool FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie(
    448      nsINode& aStart, nsCOMPtr<nsINode>& aResult);
    449 
    450  static bool IsInsertionCookie(const nsIContent& aContent);
    451 
    452  /**
    453   * @param aDocumentFragmentForContext contains the merged result.
    454   */
    455  static nsresult MergeAndPostProcessFragmentsForPastedHTMLAndContext(
    456      DocumentFragment& aDocumentFragmentForPastedHTML,
    457      DocumentFragment& aDocumentFragmentForContext,
    458      nsIContent& aTargetContentOfContextForPastedHTML);
    459 
    460  /**
    461   * @param aInfoStr as indicated by nsITransferable's kHTMLInfo.
    462   */
    463  [[nodiscard]] static nsresult MoveStartAndEndAccordingToHTMLInfo(
    464      const nsAString& aInfoStr, nsCOMPtr<nsINode>* aOutStartNode,
    465      nsCOMPtr<nsINode>* aOutEndNode);
    466 
    467  static nsresult PostProcessFragmentForPastedHTMLWithoutContext(
    468      DocumentFragment& aDocumentFragmentForPastedHTML);
    469 
    470  static nsresult PreProcessContextDocumentFragmentForMerging(
    471      DocumentFragment& aDocumentFragmentForContext);
    472 
    473  static void RemoveHeadChildAndStealBodyChildsChildren(nsINode& aNode);
    474 
    475  /**
    476   * This is designed for a helper class to remove disturbing nodes at inserting
    477   * the HTML fragment into the DOM tree.  This walks the children and if some
    478   * elements do not have enough children, e.g., list elements not having
    479   * another visible list elements nor list item elements,
    480   * will be removed.
    481   *
    482   * @param aNode       Should not be a node whose mutation may be observed by
    483   *                    JS.
    484   */
    485  static void RemoveIncompleteDescendantsFromInsertingFragment(nsINode& aNode);
    486 
    487  enum class NodesToRemove {
    488    eAll,
    489    eOnlyListItems /*!< List items are always block-level elements, hence such
    490                     whitespace-only nodes are always invisible. */
    491  };
    492  static nsresult
    493  RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
    494      nsIContent& aNode, NodesToRemove aNodesToRemove);
    495 };
    496 
    497 EditorDOMPoint
    498 HTMLEditor::HTMLWithContextInserter::GetNewCaretPointAfterInsertingHTML(
    499    const EditorDOMPoint& aLastInsertedPoint) const {
    500  EditorDOMPoint pointToPutCaret;
    501 
    502  // but don't cross tables
    503  nsIContent* containerContent = nullptr;
    504  // FIXME: GetChild() might be nullptr, but it's referred as non-null in the
    505  // block!
    506  if (!aLastInsertedPoint.GetChild() ||
    507      !aLastInsertedPoint.GetChild()->IsHTMLElement(nsGkAtoms::table)) {
    508    containerContent = HTMLEditUtils::GetLastLeafContent(
    509        *aLastInsertedPoint.GetChild(), {LeafNodeType::OnlyEditableLeafNode},
    510        BlockInlineCheck::Unused,
    511        aLastInsertedPoint.GetChild()->GetAsElementOrParentElement());
    512    if (containerContent) {
    513      Element* mostDistantInclusiveAncestorTableElement = nullptr;
    514      for (Element* maybeTableElement =
    515               containerContent->GetAsElementOrParentElement();
    516           maybeTableElement &&
    517           maybeTableElement != aLastInsertedPoint.GetChild();
    518           maybeTableElement = maybeTableElement->GetParentElement()) {
    519        if (maybeTableElement->IsHTMLElement(nsGkAtoms::table)) {
    520          mostDistantInclusiveAncestorTableElement = maybeTableElement;
    521        }
    522      }
    523      // If we're in table elements, we should put caret into the most ancestor
    524      // table element.
    525      if (mostDistantInclusiveAncestorTableElement) {
    526        containerContent = mostDistantInclusiveAncestorTableElement;
    527      }
    528    }
    529  }
    530  // If we are not in table elements, we should put caret in the last inserted
    531  // node.
    532  if (!containerContent) {
    533    containerContent = aLastInsertedPoint.GetChild();
    534  }
    535 
    536  // If the container is a text node or a container element except `<table>`
    537  // element, put caret a end of it.
    538  if (containerContent->IsText() ||
    539      (HTMLEditUtils::IsContainerNode(*containerContent) &&
    540       !containerContent->IsHTMLElement(nsGkAtoms::table))) {
    541    pointToPutCaret.SetToEndOf(containerContent);
    542  }
    543  // Otherwise, i.e., it's an atomic element, `<table>` element or data node,
    544  // put caret after it.
    545  else {
    546    pointToPutCaret.SetAfter(containerContent);
    547  }
    548 
    549  // Make sure we don't end up with selection collapsed after an invisible
    550  // `<br>` element.
    551  const WSScanResult prevVisibleThing =
    552      WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
    553          // We want to put caret to an editable point so that we need to scan
    554          // only editable nodes.
    555          {WSRunScanner::Option::OnlyEditableNodes}, pointToPutCaret);
    556  if (prevVisibleThing.ReachedInvisibleBRElement()) {
    557    const WSScanResult prevVisibleThingOfBRElement =
    558        WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
    559            {WSRunScanner::Option::OnlyEditableNodes},
    560            EditorRawDOMPoint(prevVisibleThing.BRElementPtr()));
    561    if (prevVisibleThingOfBRElement.InVisibleOrCollapsibleCharacters()) {
    562      pointToPutCaret = prevVisibleThingOfBRElement
    563                            .PointAfterReachedContent<EditorDOMPoint>();
    564    } else if (prevVisibleThingOfBRElement.ReachedSpecialContent()) {
    565      pointToPutCaret = prevVisibleThingOfBRElement
    566                            .PointAfterReachedContentNode<EditorDOMPoint>();
    567    }
    568  }
    569 
    570  return pointToPutCaret;
    571 }
    572 
    573 nsresult HTMLEditor::InsertHTMLWithContextAsSubAction(
    574    const nsAString& aInputString, const nsAString& aContextStr,
    575    const nsAString& aInfoStr, const nsAString& aFlavor,
    576    SafeToInsertData aSafeToInsertData, const EditorDOMPoint& aPointToInsert,
    577    DeleteSelectedContent aDeleteSelectedContent,
    578    InlineStylesAtInsertionPoint aInlineStylesAtInsertionPoint,
    579    const Element& aEditingHost) {
    580  MOZ_ASSERT(IsEditActionDataAvailable());
    581 
    582  if (NS_WARN_IF(!mInitSucceeded)) {
    583    return NS_ERROR_NOT_INITIALIZED;
    584  }
    585 
    586  CommitComposition();
    587 
    588  IgnoredErrorResult ignoredError;
    589  AutoEditSubActionNotifier startToHandleEditSubAction(
    590      *this, EditSubAction::ePasteHTMLContent, nsIEditor::eNext, ignoredError);
    591  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
    592    return ignoredError.StealNSResult();
    593  }
    594  NS_WARNING_ASSERTION(
    595      !ignoredError.Failed(),
    596      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
    597  ignoredError.SuppressException();
    598 
    599  {
    600    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
    601    if (MOZ_UNLIKELY(result.isErr())) {
    602      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
    603      return result.unwrapErr();
    604    }
    605    if (result.inspect().Canceled()) {
    606      return NS_OK;
    607    }
    608  }
    609 
    610  // If we have a destination / target node, we want to insert there rather than
    611  // in place of the selection.  Ignore aDeleteSelectedContent here if
    612  // aPointToInsert is not set since deletion will also occur later in
    613  // HTMLWithContextInserter and will be collapsed around there; this block
    614  // is intended to cover the various scenarios where we are dropping in an
    615  // editor (and may want to delete the selection before collapsing the
    616  // selection in the new destination)
    617  if (aPointToInsert.IsSet()) {
    618    nsresult rv =
    619        PrepareToInsertContent(aPointToInsert, aDeleteSelectedContent);
    620    if (NS_FAILED(rv)) {
    621      NS_WARNING("EditorBase::PrepareToInsertContent() failed");
    622      return rv;
    623    }
    624    aDeleteSelectedContent = DeleteSelectedContent::No;
    625  }
    626 
    627  HTMLWithContextInserter htmlWithContextInserter(*this, aEditingHost);
    628 
    629  Result<EditActionResult, nsresult> result = htmlWithContextInserter.Run(
    630      aInputString, aContextStr, aInfoStr, aSafeToInsertData,
    631      aInlineStylesAtInsertionPoint);
    632  if (MOZ_UNLIKELY(result.isErr())) {
    633    return result.unwrapErr();
    634  }
    635 
    636  // If nothing is inserted and delete selection is required, we need to
    637  // delete selection right now.
    638  if (result.inspect().Ignored() &&
    639      aDeleteSelectedContent == DeleteSelectedContent::Yes) {
    640    nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip);
    641    if (NS_FAILED(rv)) {
    642      NS_WARNING(
    643          "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
    644      return rv;
    645    }
    646  }
    647  return NS_OK;
    648 }
    649 
    650 Result<EditActionResult, nsresult> HTMLEditor::HTMLWithContextInserter::Run(
    651    const nsAString& aInputString, const nsAString& aContextStr,
    652    const nsAString& aInfoStr, SafeToInsertData aSafeToInsertData,
    653    InlineStylesAtInsertionPoint aInlineStylesAtInsertionPoint) {
    654  MOZ_ASSERT(mHTMLEditor.IsEditActionDataAvailable());
    655 
    656  // create a dom document fragment that represents the structure to paste
    657  nsCOMPtr<nsINode> fragmentAsNode, streamStartParent, streamEndParent;
    658  uint32_t streamStartOffset = 0, streamEndOffset = 0;
    659 
    660  nsresult rv = CreateDOMFragmentFromPaste(
    661      aInputString, aContextStr, aInfoStr, address_of(fragmentAsNode),
    662      address_of(streamStartParent), address_of(streamEndParent),
    663      &streamStartOffset, &streamEndOffset, aSafeToInsertData);
    664  if (NS_FAILED(rv)) {
    665    NS_WARNING(
    666        "HTMLEditor::HTMLWithContextInserter::CreateDOMFragmentFromPaste() "
    667        "failed");
    668    return Err(rv);
    669  }
    670 
    671  // we need to recalculate various things based on potentially new offsets
    672  // this is work to be completed at a later date (probably by jfrancis)
    673 
    674  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfTopMostChildContents;
    675  // If we have stream start point information, lets use it and end point.
    676  // Otherwise, we should make a range all over the document fragment.
    677  EditorRawDOMPoint streamStartPoint =
    678      streamStartParent
    679          ? EditorRawDOMPoint(streamStartParent,
    680                              AssertedCast<uint32_t>(streamStartOffset))
    681          : EditorRawDOMPoint(fragmentAsNode, 0);
    682  EditorRawDOMPoint streamEndPoint =
    683      streamStartParent ? EditorRawDOMPoint(streamEndParent, streamEndOffset)
    684                        : EditorRawDOMPoint::AtEndOf(fragmentAsNode);
    685 
    686  (void)streamStartPoint;
    687  (void)streamEndPoint;
    688 
    689  HTMLWithContextInserter::CollectTopMostChildContentsCompletelyInRange(
    690      EditorRawDOMPoint(streamStartParent,
    691                        AssertedCast<uint32_t>(streamStartOffset)),
    692      EditorRawDOMPoint(streamEndParent,
    693                        AssertedCast<uint32_t>(streamEndOffset)),
    694      arrayOfTopMostChildContents);
    695 
    696  if (arrayOfTopMostChildContents.IsEmpty()) {
    697    return EditActionResult::IgnoredResult();  // Nothing to insert.
    698  }
    699 
    700  // Are there any table elements in the list?
    701  // check for table cell selection mode
    702  bool cellSelectionMode =
    703      HTMLEditUtils::IsInTableCellSelectionMode(mHTMLEditor.SelectionRef());
    704 
    705  if (cellSelectionMode) {
    706    // do we have table content to paste?  If so, we want to delete
    707    // the selected table cells and replace with new table elements;
    708    // but if not we want to delete _contents_ of cells and replace
    709    // with non-table elements.  Use cellSelectionMode bool to
    710    // indicate results.
    711    if (!HTMLEditUtils::IsAnyTableElementExceptColumnElement(
    712            *arrayOfTopMostChildContents[0])) {
    713      cellSelectionMode = false;
    714    }
    715  }
    716 
    717  if (!cellSelectionMode) {
    718    rv = mHTMLEditor.DeleteSelectionAndPrepareToCreateNode();
    719    if (NS_FAILED(rv)) {
    720      NS_WARNING("HTMLEditor::DeleteSelectionAndPrepareToCreateNode() failed");
    721      return Err(rv);
    722    }
    723 
    724    if (aInlineStylesAtInsertionPoint == InlineStylesAtInsertionPoint::Clear) {
    725      // pasting does not inherit local inline styles
    726      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
    727          mHTMLEditor.ClearStyleAt(
    728              EditorDOMPoint(mHTMLEditor.SelectionRef().AnchorRef()),
    729              EditorInlineStyle::RemoveAllStyles(), SpecifiedStyle::Preserve,
    730              mEditingHost);
    731      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
    732        NS_WARNING("HTMLEditor::ClearStyleAt() failed");
    733        return pointToPutCaretOrError.propagateErr();
    734      }
    735      if (pointToPutCaretOrError.inspect().IsSetAndValid()) {
    736        nsresult rv =
    737            mHTMLEditor.CollapseSelectionTo(pointToPutCaretOrError.unwrap());
    738        if (NS_FAILED(rv)) {
    739          NS_WARNING("EditorBase::CollapseSelectionTo() failed");
    740          return Err(rv);
    741        }
    742      }
    743    }
    744  } else {
    745    // Delete whole cells: we will replace with new table content.
    746 
    747    // Braces for artificial block to scope AutoSelectionRestorer.
    748    // Save current selection since DeleteTableCellWithTransaction() perturbs
    749    // it.
    750    {
    751      AutoSelectionRestorer restoreSelectionLater(&mHTMLEditor);
    752      rv = mHTMLEditor.DeleteTableCellWithTransaction(1);
    753      if (NS_FAILED(rv)) {
    754        NS_WARNING("HTMLEditor::DeleteTableCellWithTransaction(1) failed");
    755        return Err(rv);
    756      }
    757    }
    758    // collapse selection to beginning of deleted table content
    759    IgnoredErrorResult ignoredError;
    760    mHTMLEditor.SelectionRef().CollapseToStart(ignoredError);
    761    NS_WARNING_ASSERTION(!ignoredError.Failed(),
    762                         "Selection::Collapse() failed, but ignored");
    763  }
    764 
    765  {
    766    Result<EditActionResult, nsresult> result =
    767        mHTMLEditor.CanHandleHTMLEditSubAction();
    768    if (MOZ_UNLIKELY(result.isErr())) {
    769      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
    770      return result;
    771    }
    772    if (result.inspect().Canceled()) {
    773      return result;
    774    }
    775  }
    776 
    777  mHTMLEditor.UndefineCaretBidiLevel();
    778 
    779  rv = mHTMLEditor.EnsureNoPaddingBRElementForEmptyEditor();
    780  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    781    return Err(NS_ERROR_EDITOR_DESTROYED);
    782  }
    783  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    784                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
    785                       "failed, but ignored");
    786 
    787  if (NS_SUCCEEDED(rv) && mHTMLEditor.SelectionRef().IsCollapsed()) {
    788    nsresult rv =
    789        mHTMLEditor.EnsureCaretNotAfterInvisibleBRElement(mEditingHost);
    790    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    791      return Err(NS_ERROR_EDITOR_DESTROYED);
    792    }
    793    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    794                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
    795                         "failed, but ignored");
    796    if (NS_SUCCEEDED(rv)) {
    797      nsresult rv = mHTMLEditor.PrepareInlineStylesForCaret();
    798      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    799        return Err(NS_ERROR_EDITOR_DESTROYED);
    800      }
    801      NS_WARNING_ASSERTION(
    802          NS_SUCCEEDED(rv),
    803          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
    804    }
    805  }
    806 
    807  // Adjust position based on the first node we are going to insert.
    808  const auto candidatePointToInsert =
    809      mHTMLEditor.GetFirstSelectionStartPoint<EditorRawDOMPoint>();
    810  if (NS_WARN_IF(!candidatePointToInsert.IsSet()) ||
    811      NS_WARN_IF(
    812          !candidatePointToInsert.GetContainer()->IsInclusiveDescendantOf(
    813              &mEditingHost))) {
    814    return Err(NS_ERROR_FAILURE);
    815  }
    816  EditorDOMPoint pointToInsert =
    817      HTMLEditUtils::GetBetterInsertionPointFor<EditorDOMPoint>(
    818          arrayOfTopMostChildContents[0],
    819          mHTMLEditor.GetFirstSelectionStartPoint<EditorRawDOMPoint>());
    820  if (!pointToInsert.IsSet()) {
    821    NS_WARNING("HTMLEditor::GetBetterInsertionPointFor() failed");
    822    return Err(NS_ERROR_FAILURE);
    823  }
    824  Result<EditorDOMPoint, nsresult> pointToInsertOrError =
    825      WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt(
    826          mHTMLEditor, pointToInsert,
    827          {WhiteSpaceVisibilityKeeper::NormalizeOption::
    828               StopIfFollowingWhiteSpacesStartsWithNBSP});
    829  if (MOZ_UNLIKELY(pointToInsertOrError.isErr())) {
    830    NS_WARNING(
    831        "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt() failed");
    832    return pointToInsertOrError.propagateErr();
    833  }
    834  pointToInsert = pointToInsertOrError.unwrap();
    835  if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) {
    836    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
    837  }
    838 
    839  const bool insertionPointWasInLink =
    840      !!HTMLEditor::GetLinkElement(pointToInsert.GetContainer());
    841 
    842  if (pointToInsert.IsInTextNode()) {
    843    Result<SplitNodeResult, nsresult> splitNodeResult =
    844        mHTMLEditor.SplitNodeDeepWithTransaction(
    845            MOZ_KnownLive(*pointToInsert.ContainerAs<nsIContent>()),
    846            pointToInsert, SplitAtEdges::eAllowToCreateEmptyContainer);
    847    if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
    848      NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
    849      return splitNodeResult.propagateErr();
    850    }
    851    nsresult rv = splitNodeResult.inspect().SuggestCaretPointTo(
    852        mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
    853                      SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
    854    if (NS_FAILED(rv)) {
    855      NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
    856      return Err(rv);
    857    }
    858    pointToInsert = splitNodeResult.inspect().AtSplitPoint<EditorDOMPoint>();
    859    if (MOZ_UNLIKELY(!pointToInsert.IsSet())) {
    860      NS_WARNING(
    861          "HTMLEditor::SplitNodeDeepWithTransaction() didn't return split "
    862          "point");
    863      return Err(NS_ERROR_FAILURE);
    864    }
    865  }
    866 
    867  {  // Block only for AutoHTMLFragmentBoundariesFixer to hide it from the
    868     // following code. Note that it may modify arrayOfTopMostChildContents.
    869    AutoHTMLFragmentBoundariesFixer fixPiecesOfTablesAndLists(
    870        arrayOfTopMostChildContents);
    871  }
    872 
    873  MOZ_ASSERT(pointToInsert.GetContainer()->GetChildAt_Deprecated(
    874                 pointToInsert.Offset()) == pointToInsert.GetChild());
    875 
    876  Result<CreateContentResult, nsresult> insertNodeResultOrError =
    877      InsertContents(pointToInsert, arrayOfTopMostChildContents,
    878                     fragmentAsNode);
    879  if (MOZ_UNLIKELY(insertNodeResultOrError.isErr())) {
    880    NS_WARNING("HTMLWithContextInserter::InsertContents() failed.");
    881    return insertNodeResultOrError.propagateErr();
    882  }
    883 
    884  // The inserting content may contain empty container elements.  However, it's
    885  // intended.  Therefore, we should not clean up them in the post-processing.
    886  mHTMLEditor.TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements =
    887      false;
    888 
    889  CreateContentResult insertNodeResult = insertNodeResultOrError.unwrap();
    890  if (MOZ_UNLIKELY(!insertNodeResult.Handled())) {
    891    // Even if we haven't inserted new content nodes, we "handled" to insert
    892    // them so that return "handled" state.
    893    return EditActionResult::HandledResult();
    894  }
    895 
    896  if (MOZ_LIKELY(insertNodeResult.GetNewNode()->IsInComposedDoc())) {
    897    const auto afterLastInsertedContent =
    898        EditorRawDOMPoint(insertNodeResult.GetNewNode())
    899            .NextPointOrAfterContainer<EditorDOMPoint>();
    900    if (MOZ_LIKELY(afterLastInsertedContent.IsInContentNode())) {
    901      nsresult rv = mHTMLEditor.EnsureNoFollowingUnnecessaryLineBreak(
    902          afterLastInsertedContent);
    903      if (NS_FAILED(rv)) {
    904        NS_WARNING(
    905            "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
    906        return Err(rv);
    907      }
    908    }
    909  }
    910 
    911  MOZ_ASSERT(insertNodeResult.HasCaretPointSuggestion());
    912  rv = insertNodeResult.SuggestCaretPointTo(
    913      mHTMLEditor, {SuggestCaret::AndIgnoreTrivialError});
    914  if (NS_FAILED(rv)) {
    915    NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
    916    return Err(rv);
    917  }
    918  NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
    919                       "CaretPoint::SuggestCaretPointTo() failed, but ignored");
    920 
    921  // If we didn't start from an `<a href>` element, we should not keep
    922  // caret in the link to make users type something outside the link.
    923  if (insertionPointWasInLink) {
    924    return EditActionResult::HandledResult();
    925  }
    926  if (Element* const parentElement =
    927          insertNodeResult.GetNewNode()->GetParentElement()) {
    928    const RefPtr<Element> linkElement = GetLinkElement(parentElement);
    929    if (MOZ_LIKELY(!linkElement)) {
    930      return EditActionResult::HandledResult();
    931    }
    932 
    933    nsresult rv =
    934        MoveCaretOutsideOfLink(*linkElement, insertNodeResult.CaretPointRef());
    935    if (NS_FAILED(rv)) {
    936      NS_WARNING(
    937          "HTMLEditor::HTMLWithContextInserter::MoveCaretOutsideOfLink() "
    938          "failed");
    939      return Err(rv);
    940    }
    941  }
    942 
    943  return EditActionResult::HandledResult();
    944 }
    945 
    946 Result<CreateContentResult, nsresult>
    947 HTMLEditor::HTMLWithContextInserter::InsertContents(
    948    const EditorDOMPoint& aPointToInsert,
    949    nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents,
    950    const nsINode* aFragmentAsNode) {
    951  MOZ_ASSERT(aPointToInsert.IsSetAndValidInComposedDoc());
    952 
    953  // Loop over the node list and paste the nodes:
    954  const RefPtr<const Element> maybeNonEditableBlockElement =
    955      aPointToInsert.IsInContentNode()
    956          ? HTMLEditUtils::GetInclusiveAncestorElement(
    957                *aPointToInsert.ContainerAs<nsIContent>(),
    958                HTMLEditUtils::ClosestBlockElement,
    959                BlockInlineCheck::UseComputedDisplayOutsideStyle)
    960          : nullptr;
    961 
    962  nsCOMPtr<nsIContent> insertedContextParentContent;
    963  RefPtr<nsIContent> lastInsertedContent;
    964  for (const OwningNonNull<nsIContent>& content :
    965       aArrayOfTopMostChildContents) {
    966    if (NS_WARN_IF(content == aFragmentAsNode) ||
    967        NS_WARN_IF(content->IsHTMLElement(nsGkAtoms::body))) {
    968      return Err(NS_ERROR_FAILURE);
    969    }
    970 
    971    if (insertedContextParentContent) {
    972      // If we had to insert something higher up in the paste hierarchy,
    973      // we want to skip any further paste nodes that descend from that.
    974      // Else we will paste twice.
    975      // XXX This check may be really expensive.  Cannot we check whether
    976      //     the node's `ownerDocument` is the `aFragmentAsNode` or not?
    977      if (EditorUtils::IsDescendantOf(*content,
    978                                      *insertedContextParentContent)) {
    979        continue;
    980      }
    981      // Okay, now, we finished moving nodes in insertedContextParentContent.
    982      // We can forget it now to skip the expensive check.
    983      insertedContextParentContent = nullptr;
    984    }
    985 
    986    // In the most cases, we want to move `content` into the DOM as-is. However,
    987    // in some cases, we don't want to insert content but do want to insert its
    988    // children into the existing proper container.  Therefore, we will check
    989    // the `content` type and insertion point's container below.  However, even
    990    // in such case, we may not be able to move only its children.  Then, we
    991    // need to fall it back to the default behavior.  Therefore, let's wrap the
    992    // default behavior into this lambda.
    993    const auto InsertCurrentContentToNextInsertionPoint =
    994        [&](const EditorDOMPoint& aPointToInsertContent)
    995            MOZ_CAN_RUN_SCRIPT MOZ_NEVER_INLINE_DEBUG -> Result<Ok, nsresult> {
    996      // MOZ_KnownLive(content) because 'aArrayOfTopMostChildContents'
    997      // guarantees its lifetime.
    998      Result<CreateContentResult, nsresult> moveContentResult =
    999          mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction<nsIContent>(
   1000              MOZ_KnownLive(content), aPointToInsertContent,
   1001              SplitAtEdges::eDoNotCreateEmptyContainer);
   1002      if (MOZ_LIKELY(moveContentResult.isOk())) {
   1003        moveContentResult.inspect().IgnoreCaretPointSuggestion();
   1004        if (MOZ_UNLIKELY(!moveContentResult.inspect().Handled())) {
   1005          MOZ_ASSERT(aPointToInsertContent.IsSetAndValidInComposedDoc());
   1006          MOZ_ASSERT_IF(lastInsertedContent,
   1007                        lastInsertedContent->IsInComposedDoc());
   1008          return Ok{};
   1009        }
   1010        lastInsertedContent = content;
   1011        MOZ_ASSERT(lastInsertedContent->IsInComposedDoc());
   1012        return Ok{};
   1013      }
   1014      // If we got unexpected DOM tree, let's abort.
   1015      if (NS_WARN_IF(moveContentResult.inspectErr() ==
   1016                     NS_ERROR_EDITOR_DESTROYED) ||
   1017          NS_WARN_IF(moveContentResult.inspectErr() ==
   1018                     NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE)) {
   1019        return moveContentResult.propagateErr();
   1020      }
   1021      // If we the next insertion point becomes invalid, it means that we
   1022      // got unexpected DOM tree which couldn't be detected by
   1023      // InsertNodeIntoProperAncestorWithTransaction().  Let's abort to
   1024      // avoid to move the node into unexpected position/documents.
   1025      if (NS_WARN_IF(!aPointToInsertContent.IsSetAndValidInComposedDoc())) {
   1026        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1027      }
   1028      // Assume failure means no legal parent in the document hierarchy,
   1029      // try again with the parent of content in the paste hierarchy.
   1030      // FYI: We cannot use `InclusiveAncestorOfType` here because of
   1031      //      calling `InsertNodeIntoProperAncestorWithTransaction()`.
   1032      EditorDOMPoint pointToInsert = aPointToInsertContent;
   1033      for (nsCOMPtr<nsIContent> parent = content->GetParent(); parent;
   1034           parent = parent->GetParent()) {
   1035        if (NS_WARN_IF(parent->IsHTMLElement(nsGkAtoms::body))) {
   1036          break;  // for the inner `for` loop
   1037        }
   1038        Result<CreateContentResult, nsresult> moveParentResult =
   1039            mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction<nsIContent>(
   1040                *parent, pointToInsert,
   1041                SplitAtEdges::eDoNotCreateEmptyContainer);
   1042        if (MOZ_UNLIKELY(moveParentResult.isErr())) {
   1043          // If we got unexpected DOM tree, let's abort.
   1044          if (NS_WARN_IF(moveParentResult.inspectErr() ==
   1045                         NS_ERROR_EDITOR_DESTROYED) ||
   1046              NS_WARN_IF(moveParentResult.inspectErr() ==
   1047                         NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE)) {
   1048            return moveParentResult.propagateErr();
   1049          }
   1050          // If we the next insertion point becomes invalid, it means that we
   1051          // got unexpected DOM tree which couldn't be detected by
   1052          // InsertNodeIntoProperAncestorWithTransaction().  Let's abort to
   1053          // avoid to move the node into unexpected position/documents.
   1054          if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) {
   1055            return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1056          }
   1057          // If the parent cannot be inserted into the DOM tree, the node may be
   1058          // an element to make a specific structure like a table. Then, we can
   1059          // insert one of its ancestors to the inserting position.  So, let's
   1060          // retry with its parent.
   1061          continue;  // the inner `for` loop
   1062        }
   1063        moveParentResult.inspect().IgnoreCaretPointSuggestion();
   1064        if (MOZ_UNLIKELY(!moveParentResult.inspect().Handled())) {
   1065          MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
   1066          MOZ_ASSERT_IF(lastInsertedContent,
   1067                        lastInsertedContent->IsInComposedDoc());
   1068          continue;
   1069        }
   1070        pointToInsert = EditorDOMPoint::After(*parent);
   1071        lastInsertedContent = parent;
   1072        MOZ_ASSERT(lastInsertedContent->IsInComposedDoc());
   1073        insertedContextParentContent = std::move(parent);
   1074        break;  // from the inner `for` loop
   1075      }  // end of the inner `for` loop iterating ancestors of content
   1076      return Ok{};
   1077    };
   1078 
   1079    // If a `<table>` or `<tr>` element on the clipboard, and pasting it into
   1080    // a `<table>` or `<tr>` element, insert only the appropriate children
   1081    // instead.
   1082    if (HTMLEditUtils::IsTableRowElement(*content)) {
   1083      EditorDOMPoint pointToInsert =
   1084          lastInsertedContent ? EditorDOMPoint::After(*lastInsertedContent)
   1085                              : aPointToInsert;
   1086      if (HTMLEditUtils::IsTableRowElement(
   1087              pointToInsert.GetContainerAs<nsIContent>()) &&
   1088          (content->IsHTMLElement(nsGkAtoms::table) ||
   1089           pointToInsert.IsContainerHTMLElement(nsGkAtoms::table))) {
   1090        MOZ_ASSERT(!content->IsInComposedDoc());
   1091        bool inserted = false;
   1092        for (RefPtr<nsIContent> child = content->GetFirstChild(); child;
   1093             child = content->GetFirstChild()) {
   1094          Result<CreateContentResult, nsresult> moveChildResult =
   1095              mHTMLEditor
   1096                  .InsertNodeIntoProperAncestorWithTransaction<nsIContent>(
   1097                      *child, pointToInsert,
   1098                      SplitAtEdges::eDoNotCreateEmptyContainer);
   1099          if (MOZ_UNLIKELY(moveChildResult.isErr())) {
   1100            // If we got unexpected DOM tree, let's abort.
   1101            if (NS_WARN_IF(moveChildResult.inspectErr() ==
   1102                           NS_ERROR_EDITOR_DESTROYED) ||
   1103                NS_WARN_IF(moveChildResult.inspectErr() ==
   1104                           NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE)) {
   1105              return moveChildResult.propagateErr();
   1106            }
   1107            // If we the next insertion point becomes invalid, it means that
   1108            // we got unexpected DOM tree which couldn't be detected by
   1109            // InsertNodeIntoProperAncestorWithTransaction().  Let's abort to
   1110            // avoid to move the node into unexpected position/documents.
   1111            if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) {
   1112              return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1113            }
   1114            NS_WARNING(
   1115                "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
   1116                "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but maybe "
   1117                "ignored");
   1118            break;  // from the inner `for` loop
   1119          }
   1120          moveChildResult.inspect().IgnoreCaretPointSuggestion();
   1121          if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) {
   1122            MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
   1123            MOZ_ASSERT_IF(lastInsertedContent,
   1124                          lastInsertedContent->IsInComposedDoc());
   1125            continue;
   1126          }
   1127          inserted = true;
   1128          pointToInsert = EditorDOMPoint::After(*child);
   1129          MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
   1130          lastInsertedContent = std::move(child);
   1131          MOZ_ASSERT(lastInsertedContent->IsInComposedDoc());
   1132        }  // end of the inner `for` loop iterating children of `content`
   1133        if (!inserted) {
   1134          Result<Ok, nsresult> moveContentOrParentResultOrError =
   1135              InsertCurrentContentToNextInsertionPoint(pointToInsert);
   1136          if (MOZ_UNLIKELY(moveContentOrParentResultOrError.isErr())) {
   1137            NS_WARNING("InsertCurrentContentToNextInsertionPoint() failed");
   1138            return moveContentOrParentResultOrError.propagateErr();
   1139          }
   1140        }
   1141        continue;
   1142      }
   1143    }  // if <tr>
   1144 
   1145    // If a list element on the clipboard, and pasting it into a list or
   1146    // list item element, insert the appropriate children instead.  I.e.,
   1147    // merge the list elements instead of pasting as a sublist.
   1148    if (HTMLEditUtils::IsListElement(*content)) {
   1149      EditorDOMPoint pointToInsert =
   1150          lastInsertedContent ? EditorDOMPoint::After(*lastInsertedContent)
   1151                              : aPointToInsert;
   1152      if (HTMLEditUtils::IsListElement(
   1153              pointToInsert.GetContainerAs<nsIContent>()) ||
   1154          HTMLEditUtils::IsListItemElement(
   1155              pointToInsert.GetContainerAs<nsIContent>())) {
   1156        MOZ_ASSERT(!content->IsInComposedDoc());
   1157        bool inserted = false;
   1158        for (RefPtr<nsIContent> child = content->GetFirstChild(); child;
   1159             child = content->GetFirstChild()) {
   1160          // Ignore invisible nodes like `Comment` or white-space only `Text`
   1161          // and invalid children of the list element.
   1162          // XXX Although we should not construct invalid structure, but
   1163          // shouldn't we preserve invalid children for avoiding dataloss?
   1164          if (!HTMLEditUtils::IsListItemElement(*child) &&
   1165              !HTMLEditUtils::IsListElement(*child)) {
   1166            continue;
   1167          }
   1168          // If we're pasting into empty list item, we should remove it
   1169          // and past current node into the parent list directly.
   1170          // XXX This creates invalid structure if current list item element
   1171          //     is not proper child of the parent element, or current node
   1172          //     is a list element.
   1173          if (HTMLEditUtils::IsListItemElement(
   1174                  pointToInsert.GetContainerAs<nsIContent>()) &&
   1175              HTMLEditUtils::IsRemovableNode(
   1176                  *pointToInsert.ContainerAs<Element>()) &&
   1177              HTMLEditUtils::IsEmptyNode(
   1178                  *pointToInsert.ContainerAs<Element>(),
   1179                  {EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
   1180            const OwningNonNull<Element> emptyListItemElement =
   1181                *pointToInsert.ContainerAs<Element>();
   1182            nsCOMPtr<nsINode> parentNode =
   1183                emptyListItemElement->GetParentNode();
   1184            MOZ_ASSERT(parentNode);
   1185            nsCOMPtr<nsIContent> nextSibling =
   1186                emptyListItemElement->GetNextSibling();
   1187            nsresult rv =
   1188                mHTMLEditor.DeleteNodeWithTransaction(*emptyListItemElement);
   1189            if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
   1190              NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   1191              return Err(NS_ERROR_EDITOR_DESTROYED);
   1192            }
   1193            if (NS_WARN_IF(!parentNode->IsInComposedDoc()) ||
   1194                NS_WARN_IF(nextSibling &&
   1195                           nextSibling->GetParentNode() != parentNode)) {
   1196              return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1197            }
   1198            NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   1199                                 "EditorBase::DeleteNodeWithTransaction() "
   1200                                 "failed, but ignored");
   1201            pointToInsert =
   1202                nextSibling ? EditorDOMPoint(std::move(nextSibling))
   1203                            : EditorDOMPoint::AtEndOf(std::move(parentNode));
   1204            MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
   1205          }
   1206          NS_WARNING(nsPrintfCString("%s into %s", ToString(*child).c_str(),
   1207                                     ToString(pointToInsert).c_str())
   1208                         .get());
   1209          Result<CreateContentResult, nsresult> moveChildResult =
   1210              mHTMLEditor
   1211                  .InsertNodeIntoProperAncestorWithTransaction<nsIContent>(
   1212                      *child, pointToInsert,
   1213                      SplitAtEdges::eDoNotCreateEmptyContainer);
   1214          if (MOZ_UNLIKELY(moveChildResult.isErr())) {
   1215            // If we got unexpected DOM tree, let's abort.
   1216            if (NS_WARN_IF(moveChildResult.inspectErr() ==
   1217                           NS_ERROR_EDITOR_DESTROYED) ||
   1218                NS_WARN_IF(moveChildResult.inspectErr() ==
   1219                           NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE)) {
   1220              return moveChildResult.propagateErr();
   1221            }
   1222            // If we the next insertion point becomes invalid, it means that
   1223            // we got unexpected DOM tree which couldn't be detected by
   1224            // InsertNodeIntoProperAncestorWithTransaction().  Let's abort to
   1225            // avoid to move the node into unexpected position/documents.
   1226            if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) {
   1227              return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1228            }
   1229            NS_WARNING(
   1230                "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
   1231                "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but maybe "
   1232                "ignored");
   1233            break;  // from the inner `for` loop
   1234          }
   1235          moveChildResult.inspect().IgnoreCaretPointSuggestion();
   1236          if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) {
   1237            MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
   1238            MOZ_ASSERT_IF(lastInsertedContent,
   1239                          lastInsertedContent->IsInComposedDoc());
   1240            continue;
   1241          }
   1242          inserted = true;
   1243          pointToInsert = EditorDOMPoint::After(*child);
   1244          MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
   1245          lastInsertedContent = std::move(child);
   1246          MOZ_ASSERT(lastInsertedContent->IsInComposedDoc());
   1247        }  // end of the inner `for` loop iterating children of `content`
   1248        if (!inserted) {
   1249          Result<Ok, nsresult> moveContentOrParentResultOrError =
   1250              InsertCurrentContentToNextInsertionPoint(pointToInsert);
   1251          if (MOZ_UNLIKELY(moveContentOrParentResultOrError.isErr())) {
   1252            NS_WARNING("InsertCurrentContentToNextInsertionPoint() failed");
   1253            return moveContentOrParentResultOrError.propagateErr();
   1254          }
   1255        }
   1256        continue;
   1257      }
   1258    }  // if <ul>, <ol> or <dl>
   1259 
   1260    // If pasting into a `<pre>` element and current node is a `<pre>` element,
   1261    // move only its children.
   1262    if (maybeNonEditableBlockElement &&
   1263        maybeNonEditableBlockElement->IsHTMLElement(nsGkAtoms::pre) &&
   1264        content->IsHTMLElement(nsGkAtoms::pre)) {
   1265      MOZ_ASSERT(!content->IsInComposedDoc());
   1266      EditorDOMPoint pointToInsert =
   1267          lastInsertedContent ? EditorDOMPoint::After(*lastInsertedContent)
   1268                              : aPointToInsert;
   1269      bool inserted = false;
   1270      for (RefPtr<nsIContent> child = content->GetFirstChild(); child;
   1271           child = content->GetFirstChild()) {
   1272        Result<CreateContentResult, nsresult> moveChildResult =
   1273            mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction<nsIContent>(
   1274                *child, pointToInsert,
   1275                SplitAtEdges::eDoNotCreateEmptyContainer);
   1276        if (MOZ_UNLIKELY(moveChildResult.isErr())) {
   1277          // If we got unexpected DOM tree, let's abort.
   1278          if (NS_WARN_IF(moveChildResult.inspectErr() ==
   1279                         NS_ERROR_EDITOR_DESTROYED) ||
   1280              NS_WARN_IF(moveChildResult.inspectErr() ==
   1281                         NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE)) {
   1282            return moveChildResult.propagateErr();
   1283          }
   1284          // If we the next insertion point becomes invalid, it means that we
   1285          // got unexpected DOM tree which couldn't be detected by
   1286          // InsertNodeIntoProperAncestorWithTransaction().  Let's abort to
   1287          // avoid to move the node into unexpected position/documents.
   1288          if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) {
   1289            return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1290          }
   1291          NS_WARNING(
   1292              "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
   1293              "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but maybe "
   1294              "ignored");
   1295          break;  // from the inner `for` loop
   1296        }
   1297        moveChildResult.inspect().IgnoreCaretPointSuggestion();
   1298        if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) {
   1299          MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
   1300          MOZ_ASSERT_IF(lastInsertedContent,
   1301                        lastInsertedContent->IsInComposedDoc());
   1302          continue;
   1303        }
   1304        inserted = true;
   1305        pointToInsert = EditorDOMPoint::After(*child);
   1306        MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
   1307        lastInsertedContent = std::move(child);
   1308        MOZ_ASSERT(lastInsertedContent->IsInComposedDoc());
   1309      }  // end of the inner `for` loop iterating children of `content`
   1310      if (!inserted) {
   1311        Result<Ok, nsresult> moveContentOrParentResultOrError =
   1312            InsertCurrentContentToNextInsertionPoint(pointToInsert);
   1313        if (MOZ_UNLIKELY(moveContentOrParentResultOrError.isErr())) {
   1314          NS_WARNING("InsertCurrentContentToNextInsertionPoint() failed");
   1315          return moveContentOrParentResultOrError.propagateErr();
   1316        }
   1317      }
   1318      continue;
   1319    }  // if <pre> and inserting into a connected <pre>
   1320 
   1321    // By default, we should move `content` into the DOM.
   1322    Result<Ok, nsresult> moveContentOrParentResultOrError =
   1323        InsertCurrentContentToNextInsertionPoint(
   1324            lastInsertedContent ? EditorDOMPoint::After(*lastInsertedContent)
   1325                                : aPointToInsert);
   1326    if (MOZ_UNLIKELY(moveContentOrParentResultOrError.isErr())) {
   1327      NS_WARNING("InsertCurrentContentToNextInsertionPoint() failed");
   1328      return moveContentOrParentResultOrError.propagateErr();
   1329    }
   1330  }  // end of the `for` loop iterating aArrayOfTopMostChildContents
   1331 
   1332  if (!lastInsertedContent) {
   1333    return CreateContentResult::NotHandled();
   1334  }
   1335  EditorDOMPoint pointToPutCaret =
   1336      GetNewCaretPointAfterInsertingHTML(EditorDOMPoint(lastInsertedContent));
   1337  return CreateContentResult(std::move(lastInsertedContent),
   1338                             std::move(pointToPutCaret));
   1339 }
   1340 
   1341 nsresult HTMLEditor::HTMLWithContextInserter::MoveCaretOutsideOfLink(
   1342    Element& aLinkElement, const EditorDOMPoint& aPointToPutCaret) {
   1343  MOZ_ASSERT(HTMLEditUtils::IsHyperlinkElement(aLinkElement));
   1344 
   1345  // The reason why do that instead of just moving caret after it is, the
   1346  // link might have ended in an invisible `<br>` element.  If so, the code
   1347  // above just placed selection inside that.  So we need to split it instead.
   1348  // XXX Sounds like that it's not really expensive comparing with the reason
   1349  //     to use SplitNodeDeepWithTransaction() here.
   1350  Result<SplitNodeResult, nsresult> splitLinkResult =
   1351      mHTMLEditor.SplitNodeDeepWithTransaction(
   1352          aLinkElement, aPointToPutCaret,
   1353          SplitAtEdges::eDoNotCreateEmptyContainer);
   1354  if (MOZ_UNLIKELY(splitLinkResult.isErr())) {
   1355    if (splitLinkResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
   1356      NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
   1357      return NS_ERROR_EDITOR_DESTROYED;
   1358    }
   1359    NS_WARNING(
   1360        "HTMLEditor::SplitNodeDeepWithTransaction() failed, but ignored");
   1361  }
   1362 
   1363  if (nsIContent* previousContentOfSplitPoint =
   1364          splitLinkResult.inspect().GetPreviousContent()) {
   1365    splitLinkResult.inspect().IgnoreCaretPointSuggestion();
   1366    nsresult rv = mHTMLEditor.CollapseSelectionTo(
   1367        EditorRawDOMPoint::After(*previousContentOfSplitPoint));
   1368    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   1369      return NS_ERROR_EDITOR_DESTROYED;
   1370    }
   1371    NS_WARNING_ASSERTION(
   1372        NS_SUCCEEDED(rv),
   1373        "EditorBase::CollapseSelectionTo() failed, but ignored");
   1374    return NS_OK;
   1375  }
   1376 
   1377  nsresult rv = splitLinkResult.inspect().SuggestCaretPointTo(
   1378      mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
   1379                    SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
   1380  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   1381                       "SplitNodeResult::SuggestCaretPointTo() failed");
   1382  return rv;
   1383 }
   1384 
   1385 // static
   1386 Element* HTMLEditor::GetLinkElement(nsINode* aNode) {
   1387  if (NS_WARN_IF(!aNode)) {
   1388    return nullptr;
   1389  }
   1390  nsINode* node = aNode;
   1391  while (node) {
   1392    if (node->IsElement() &&
   1393        HTMLEditUtils::IsHyperlinkElement(*node->AsElement())) {
   1394      return node->AsElement();
   1395    }
   1396    node = node->GetParentNode();
   1397  }
   1398  return nullptr;
   1399 }
   1400 
   1401 // static
   1402 nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
   1403    RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
   1404        nsIContent& aNode, NodesToRemove aNodesToRemove) {
   1405  if (aNode.TextIsOnlyWhitespace()) {
   1406    nsCOMPtr<nsINode> parent = aNode.GetParentNode();
   1407    // TODO: presumably, if the parent is a `<pre>` element, the node
   1408    // shouldn't be removed.
   1409    if (parent) {
   1410      if (aNodesToRemove == NodesToRemove::eAll ||
   1411          HTMLEditUtils::IsListElement(nsIContent::FromNode(parent))) {
   1412        ErrorResult error;
   1413        parent->RemoveChild(aNode, error);
   1414        NS_WARNING_ASSERTION(!error.Failed(), "nsINode::RemoveChild() failed");
   1415        return error.StealNSResult();
   1416      }
   1417      return NS_OK;
   1418    }
   1419  }
   1420 
   1421  if (!aNode.IsHTMLElement(nsGkAtoms::pre)) {
   1422    nsCOMPtr<nsIContent> child = aNode.GetLastChild();
   1423    while (child) {
   1424      nsCOMPtr<nsIContent> previous = child->GetPreviousSibling();
   1425      nsresult rv = FragmentFromPasteCreator::
   1426          RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
   1427              *child, aNodesToRemove);
   1428      if (NS_FAILED(rv)) {
   1429        NS_WARNING(
   1430            "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
   1431            "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces"
   1432            "() "
   1433            "failed");
   1434        return rv;
   1435      }
   1436      child = std::move(previous);
   1437    }
   1438  }
   1439  return NS_OK;
   1440 }
   1441 
   1442 class MOZ_STACK_CLASS HTMLEditor::HTMLTransferablePreparer {
   1443 public:
   1444  HTMLTransferablePreparer(const HTMLEditor& aHTMLEditor,
   1445                           nsITransferable** aTransferable,
   1446                           const Element* aEditingHost);
   1447 
   1448  nsresult Run();
   1449 
   1450 private:
   1451  void AddDataFlavorsInBestOrder(nsITransferable& aTransferable) const;
   1452 
   1453  const HTMLEditor& mHTMLEditor;
   1454  const Element* const mEditingHost;
   1455  nsITransferable** mTransferable;
   1456 };
   1457 
   1458 HTMLEditor::HTMLTransferablePreparer::HTMLTransferablePreparer(
   1459    const HTMLEditor& aHTMLEditor, nsITransferable** aTransferable,
   1460    const Element* aEditingHost)
   1461    : mHTMLEditor{aHTMLEditor},
   1462      mEditingHost(aEditingHost),
   1463      mTransferable{aTransferable} {
   1464  MOZ_ASSERT(mTransferable);
   1465  MOZ_ASSERT(!*mTransferable);
   1466 }
   1467 
   1468 nsresult HTMLEditor::PrepareHTMLTransferable(
   1469    nsITransferable** aTransferable, const Element* aEditingHost) const {
   1470  HTMLTransferablePreparer htmlTransferablePreparer{*this, aTransferable,
   1471                                                    aEditingHost};
   1472  return htmlTransferablePreparer.Run();
   1473 }
   1474 
   1475 nsresult HTMLEditor::HTMLTransferablePreparer::Run() {
   1476  // Create generic Transferable for getting the data
   1477  nsresult rv;
   1478  RefPtr<nsITransferable> transferable =
   1479      do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
   1480  if (NS_FAILED(rv)) {
   1481    NS_WARNING("do_CreateInstance() failed to create nsITransferable instance");
   1482    return rv;
   1483  }
   1484 
   1485  if (!transferable) {
   1486    NS_WARNING("do_CreateInstance() returned nullptr, but ignored");
   1487    return NS_OK;
   1488  }
   1489 
   1490  // Get the nsITransferable interface for getting the data from the clipboard
   1491  RefPtr<Document> destdoc = mHTMLEditor.GetDocument();
   1492  nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
   1493  DebugOnly<nsresult> rvIgnored = transferable->Init(loadContext);
   1494  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1495                       "nsITransferable::Init() failed, but ignored");
   1496 
   1497  // See `HTMLEditor::InsertFromTransferableAtSelection`.
   1498  AddDataFlavorsInBestOrder(*transferable);
   1499 
   1500  transferable.forget(mTransferable);
   1501 
   1502  return NS_OK;
   1503 }
   1504 
   1505 void HTMLEditor::HTMLTransferablePreparer::AddDataFlavorsInBestOrder(
   1506    nsITransferable& aTransferable) const {
   1507  // Create the desired DataFlavor for the type of data
   1508  // we want to get out of the transferable
   1509  // This should only happen in html editors, not plaintext
   1510  // Note that if you add more flavors here you will need to add them
   1511  // to DataTransfer::GetExternalClipboardFormats as well.
   1512  if (!mHTMLEditor.IsPlaintextMailComposer() &&
   1513      !(mEditingHost && mEditingHost->IsContentEditablePlainTextOnly())) {
   1514    DebugOnly<nsresult> rvIgnored =
   1515        aTransferable.AddDataFlavor(kNativeHTMLMime);
   1516    NS_WARNING_ASSERTION(
   1517        NS_SUCCEEDED(rvIgnored),
   1518        "nsITransferable::AddDataFlavor(kNativeHTMLMime) failed, but ignored");
   1519    rvIgnored = aTransferable.AddDataFlavor(kHTMLMime);
   1520    NS_WARNING_ASSERTION(
   1521        NS_SUCCEEDED(rvIgnored),
   1522        "nsITransferable::AddDataFlavor(kHTMLMime) failed, but ignored");
   1523    rvIgnored = aTransferable.AddDataFlavor(kFileMime);
   1524    NS_WARNING_ASSERTION(
   1525        NS_SUCCEEDED(rvIgnored),
   1526        "nsITransferable::AddDataFlavor(kFileMime) failed, but ignored");
   1527 
   1528    switch (Preferences::GetInt("clipboard.paste_image_type", 1)) {
   1529      case 0:  // prefer JPEG over PNG over GIF encoding
   1530        rvIgnored = aTransferable.AddDataFlavor(kJPEGImageMime);
   1531        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1532                             "nsITransferable::AddDataFlavor(kJPEGImageMime) "
   1533                             "failed, but ignored");
   1534        rvIgnored = aTransferable.AddDataFlavor(kJPGImageMime);
   1535        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1536                             "nsITransferable::AddDataFlavor(kJPGImageMime) "
   1537                             "failed, but ignored");
   1538        rvIgnored = aTransferable.AddDataFlavor(kPNGImageMime);
   1539        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1540                             "nsITransferable::AddDataFlavor(kPNGImageMime) "
   1541                             "failed, but ignored");
   1542        rvIgnored = aTransferable.AddDataFlavor(kGIFImageMime);
   1543        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1544                             "nsITransferable::AddDataFlavor(kGIFImageMime) "
   1545                             "failed, but ignored");
   1546        break;
   1547      case 1:  // prefer PNG over JPEG over GIF encoding (default)
   1548      default:
   1549        rvIgnored = aTransferable.AddDataFlavor(kPNGImageMime);
   1550        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1551                             "nsITransferable::AddDataFlavor(kPNGImageMime) "
   1552                             "failed, but ignored");
   1553        rvIgnored = aTransferable.AddDataFlavor(kJPEGImageMime);
   1554        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1555                             "nsITransferable::AddDataFlavor(kJPEGImageMime) "
   1556                             "failed, but ignored");
   1557        rvIgnored = aTransferable.AddDataFlavor(kJPGImageMime);
   1558        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1559                             "nsITransferable::AddDataFlavor(kJPGImageMime) "
   1560                             "failed, but ignored");
   1561        rvIgnored = aTransferable.AddDataFlavor(kGIFImageMime);
   1562        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1563                             "nsITransferable::AddDataFlavor(kGIFImageMime) "
   1564                             "failed, but ignored");
   1565        break;
   1566      case 2:  // prefer GIF over JPEG over PNG encoding
   1567        rvIgnored = aTransferable.AddDataFlavor(kGIFImageMime);
   1568        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1569                             "nsITransferable::AddDataFlavor(kGIFImageMime) "
   1570                             "failed, but ignored");
   1571        rvIgnored = aTransferable.AddDataFlavor(kJPEGImageMime);
   1572        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1573                             "nsITransferable::AddDataFlavor(kJPEGImageMime) "
   1574                             "failed, but ignored");
   1575        rvIgnored = aTransferable.AddDataFlavor(kJPGImageMime);
   1576        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1577                             "nsITransferable::AddDataFlavor(kJPGImageMime) "
   1578                             "failed, but ignored");
   1579        rvIgnored = aTransferable.AddDataFlavor(kPNGImageMime);
   1580        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1581                             "nsITransferable::AddDataFlavor(kPNGImageMime) "
   1582                             "failed, but ignored");
   1583        break;
   1584    }
   1585  }
   1586  DebugOnly<nsresult> rvIgnored = aTransferable.AddDataFlavor(kTextMime);
   1587  NS_WARNING_ASSERTION(
   1588      NS_SUCCEEDED(rvIgnored),
   1589      "nsITransferable::AddDataFlavor(kTextMime) failed, but ignored");
   1590  rvIgnored = aTransferable.AddDataFlavor(kMozTextInternal);
   1591  NS_WARNING_ASSERTION(
   1592      NS_SUCCEEDED(rvIgnored),
   1593      "nsITransferable::AddDataFlavor(kMozTextInternal) failed, but ignored");
   1594 }
   1595 
   1596 bool FindIntegerAfterString(const char* aLeadingString, const nsCString& aCStr,
   1597                            int32_t& foundNumber) {
   1598  // first obtain offsets from cfhtml str
   1599  int32_t numFront = aCStr.Find(aLeadingString);
   1600  if (numFront == -1) {
   1601    return false;
   1602  }
   1603  numFront += strlen(aLeadingString);
   1604 
   1605  int32_t numBack = aCStr.FindCharInSet(CRLF, numFront);
   1606  if (numBack == -1) {
   1607    return false;
   1608  }
   1609 
   1610  nsAutoCString numStr(Substring(aCStr, numFront, numBack - numFront));
   1611  nsresult errorCode;
   1612  foundNumber = numStr.ToInteger(&errorCode);
   1613  return true;
   1614 }
   1615 
   1616 void RemoveFragComments(nsCString& aStr) {
   1617  // remove the StartFragment/EndFragment comments from the str, if present
   1618  int32_t startCommentIndx = aStr.Find("<!--StartFragment");
   1619  if (startCommentIndx >= 0) {
   1620    int32_t startCommentEnd = aStr.Find("-->", startCommentIndx);
   1621    if (startCommentEnd > startCommentIndx) {
   1622      aStr.Cut(startCommentIndx, (startCommentEnd + 3) - startCommentIndx);
   1623    }
   1624  }
   1625  int32_t endCommentIndx = aStr.Find("<!--EndFragment");
   1626  if (endCommentIndx >= 0) {
   1627    int32_t endCommentEnd = aStr.Find("-->", endCommentIndx);
   1628    if (endCommentEnd > endCommentIndx) {
   1629      aStr.Cut(endCommentIndx, (endCommentEnd + 3) - endCommentIndx);
   1630    }
   1631  }
   1632 }
   1633 
   1634 nsresult HTMLEditor::ParseCFHTML(const nsCString& aCfhtml,
   1635                                 char16_t** aStuffToPaste,
   1636                                 char16_t** aCfcontext) {
   1637  // First obtain offsets from cfhtml str.
   1638  int32_t startHTML, endHTML, startFragment, endFragment;
   1639  if (!FindIntegerAfterString("StartHTML:", aCfhtml, startHTML) ||
   1640      startHTML < -1) {
   1641    return NS_ERROR_FAILURE;
   1642  }
   1643  if (!FindIntegerAfterString("EndHTML:", aCfhtml, endHTML) || endHTML < -1) {
   1644    return NS_ERROR_FAILURE;
   1645  }
   1646  if (!FindIntegerAfterString("StartFragment:", aCfhtml, startFragment) ||
   1647      startFragment < 0) {
   1648    return NS_ERROR_FAILURE;
   1649  }
   1650  if (!FindIntegerAfterString("EndFragment:", aCfhtml, endFragment) ||
   1651      startFragment < 0) {
   1652    return NS_ERROR_FAILURE;
   1653  }
   1654 
   1655  // The StartHTML and EndHTML markers are allowed to be -1 to include
   1656  // everything.
   1657  //   See Reference: MSDN doc entitled "HTML Clipboard Format"
   1658  //   http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
   1659  if (startHTML == -1) {
   1660    startHTML = aCfhtml.Find("<!--StartFragment-->");
   1661    if (startHTML == -1) {
   1662      return NS_OK;
   1663    }
   1664  }
   1665  if (endHTML == -1) {
   1666    const char endFragmentMarker[] = "<!--EndFragment-->";
   1667    endHTML = aCfhtml.Find(endFragmentMarker);
   1668    if (endHTML == -1) {
   1669      return NS_OK;
   1670    }
   1671    endHTML += std::size(endFragmentMarker) - 1;
   1672  }
   1673 
   1674  // create context string
   1675  nsAutoCString contextUTF8(
   1676      Substring(aCfhtml, startHTML, startFragment - startHTML) +
   1677      "<!--" kInsertCookie "-->"_ns +
   1678      Substring(aCfhtml, endFragment, endHTML - endFragment));
   1679 
   1680  // validate startFragment
   1681  // make sure it's not in the middle of a HTML tag
   1682  // see bug #228879 for more details
   1683  int32_t curPos = startFragment;
   1684  while (curPos > startHTML) {
   1685    if (aCfhtml[curPos] == '>') {
   1686      // working backwards, the first thing we see is the end of a tag
   1687      // so StartFragment is good, so do nothing.
   1688      break;
   1689    }
   1690    if (aCfhtml[curPos] == '<') {
   1691      // if we are at the start, then we want to see the '<'
   1692      if (curPos != startFragment) {
   1693        // working backwards, the first thing we see is the start of a tag
   1694        // so StartFragment is bad, so we need to update it.
   1695        NS_ERROR(
   1696            "StartFragment byte count in the clipboard looks bad, see bug "
   1697            "#228879");
   1698        startFragment = curPos - 1;
   1699      }
   1700      break;
   1701    }
   1702    curPos--;
   1703  }
   1704 
   1705  // create fragment string
   1706  nsAutoCString fragmentUTF8(
   1707      Substring(aCfhtml, startFragment, endFragment - startFragment));
   1708 
   1709  // remove the StartFragment/EndFragment comments from the fragment, if present
   1710  RemoveFragComments(fragmentUTF8);
   1711 
   1712  // remove the StartFragment/EndFragment comments from the context, if present
   1713  RemoveFragComments(contextUTF8);
   1714 
   1715  // convert both strings to usc2
   1716  const nsString& fragUcs2Str = NS_ConvertUTF8toUTF16(fragmentUTF8);
   1717  const nsString& cntxtUcs2Str = NS_ConvertUTF8toUTF16(contextUTF8);
   1718 
   1719  // translate platform linebreaks for fragment
   1720  int32_t oldLengthInChars =
   1721      fragUcs2Str.Length() + 1;  // +1 to include null terminator
   1722  int32_t newLengthInChars = 0;
   1723  *aStuffToPaste = nsLinebreakConverter::ConvertUnicharLineBreaks(
   1724      fragUcs2Str.get(), nsLinebreakConverter::eLinebreakAny,
   1725      nsLinebreakConverter::eLinebreakContent, oldLengthInChars,
   1726      &newLengthInChars);
   1727  if (!*aStuffToPaste) {
   1728    NS_WARNING("nsLinebreakConverter::ConvertUnicharLineBreaks() failed");
   1729    return NS_ERROR_FAILURE;
   1730  }
   1731 
   1732  // translate platform linebreaks for context
   1733  oldLengthInChars =
   1734      cntxtUcs2Str.Length() + 1;  // +1 to include null terminator
   1735  newLengthInChars = 0;
   1736  *aCfcontext = nsLinebreakConverter::ConvertUnicharLineBreaks(
   1737      cntxtUcs2Str.get(), nsLinebreakConverter::eLinebreakAny,
   1738      nsLinebreakConverter::eLinebreakContent, oldLengthInChars,
   1739      &newLengthInChars);
   1740  // it's ok for context to be empty.  frag might be whole doc and contain all
   1741  // its context.
   1742 
   1743  // we're done!
   1744  return NS_OK;
   1745 }
   1746 
   1747 static nsresult ImgFromData(const nsACString& aType, const nsACString& aData,
   1748                            nsString& aOutput) {
   1749  aOutput.AssignLiteral("<IMG src=\"data:");
   1750  AppendUTF8toUTF16(aType, aOutput);
   1751  aOutput.AppendLiteral(";base64,");
   1752  nsresult rv = Base64EncodeAppend(aData, aOutput);
   1753  if (NS_FAILED(rv)) {
   1754    NS_WARNING("Base64Encode() failed");
   1755    return rv;
   1756  }
   1757  aOutput.AppendLiteral("\" alt=\"\" >");
   1758  return NS_OK;
   1759 }
   1760 
   1761 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLEditor::BlobReader)
   1762 
   1763 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HTMLEditor::BlobReader)
   1764  NS_IMPL_CYCLE_COLLECTION_UNLINK(mBlob)
   1765  NS_IMPL_CYCLE_COLLECTION_UNLINK(mHTMLEditor)
   1766  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPointToInsert)
   1767 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
   1768 
   1769 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HTMLEditor::BlobReader)
   1770  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBlob)
   1771  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHTMLEditor)
   1772  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPointToInsert)
   1773 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
   1774 
   1775 HTMLEditor::BlobReader::BlobReader(BlobImpl* aBlob, HTMLEditor* aHTMLEditor,
   1776                                   SafeToInsertData aSafeToInsertData,
   1777                                   const EditorDOMPoint& aPointToInsert,
   1778                                   DeleteSelectedContent aDeleteSelectedContent,
   1779                                   const Element& aEditingHost)
   1780    : mBlob(aBlob),
   1781      mHTMLEditor(aHTMLEditor),
   1782      mEditingHost(&aEditingHost),
   1783      // "beforeinput" event should've been dispatched before we read blob,
   1784      // but anyway, we need to clone dataTransfer for "input" event.
   1785      mDataTransfer(mHTMLEditor->GetInputEventDataTransfer()),
   1786      mPointToInsert(aPointToInsert),
   1787      mEditAction(aHTMLEditor->GetEditAction()),
   1788      mSafeToInsertData(aSafeToInsertData),
   1789      mDeleteSelectedContent(aDeleteSelectedContent),
   1790      mNeedsToDispatchBeforeInputEvent(
   1791          !mHTMLEditor->HasTriedToDispatchBeforeInputEvent()) {
   1792  MOZ_ASSERT(mBlob);
   1793  MOZ_ASSERT(mHTMLEditor);
   1794  MOZ_ASSERT(mHTMLEditor->IsEditActionDataAvailable());
   1795  MOZ_ASSERT(mDataTransfer);
   1796 
   1797  // Take only offset here since it's our traditional behavior.
   1798  if (mPointToInsert.IsSet()) {
   1799    AutoEditorDOMPointChildInvalidator storeOnlyWithOffset(mPointToInsert);
   1800  }
   1801 }
   1802 
   1803 nsresult HTMLEditor::BlobReader::OnResult(const nsACString& aResult) {
   1804  if (NS_WARN_IF(!mEditingHost)) {
   1805    return NS_ERROR_FAILURE;
   1806  }
   1807  AutoEditActionDataSetter editActionData(*mHTMLEditor, mEditAction);
   1808  editActionData.InitializeDataTransfer(mDataTransfer);
   1809  if (NS_WARN_IF(!editActionData.CanHandle())) {
   1810    return NS_ERROR_FAILURE;
   1811  }
   1812 
   1813  if (NS_WARN_IF(mNeedsToDispatchBeforeInputEvent)) {
   1814    nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
   1815    if (NS_FAILED(rv)) {
   1816      NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   1817                           "MaybeDispatchBeforeInputEvent(), failed");
   1818      return EditorBase::ToGenericNSResult(rv);
   1819    }
   1820  } else {
   1821    editActionData.MarkAsBeforeInputHasBeenDispatched();
   1822  }
   1823 
   1824  nsString blobType;
   1825  mBlob->GetType(blobType);
   1826 
   1827  // TODO: This does not work well.
   1828  // * If the data is not an image file, this inserts <img> element with odd
   1829  //   data URI (bug 1610220).
   1830  // * If the data is valid image file data, an <img> file is inserted with
   1831  //   data URI, but it's not loaded (bug 1610219).
   1832  NS_ConvertUTF16toUTF8 type(blobType);
   1833  nsAutoString stuffToPaste;
   1834  nsresult rv = ImgFromData(type, aResult, stuffToPaste);
   1835  if (NS_FAILED(rv)) {
   1836    NS_WARNING("ImgFormData() failed");
   1837    return EditorBase::ToGenericNSResult(rv);
   1838  }
   1839 
   1840  RefPtr<HTMLEditor> htmlEditor = std::move(mHTMLEditor);
   1841  AutoPlaceholderBatch treatAsOneTransaction(
   1842      *htmlEditor, ScrollSelectionIntoView::Yes, __FUNCTION__);
   1843  EditorDOMPoint pointToInsert = std::move(mPointToInsert);
   1844  const RefPtr<const Element> editingHost = std::move(mEditingHost);
   1845  rv = htmlEditor->InsertHTMLWithContextAsSubAction(
   1846      stuffToPaste, u""_ns, u""_ns, NS_LITERAL_STRING_FROM_CSTRING(kFileMime),
   1847      mSafeToInsertData, pointToInsert, mDeleteSelectedContent,
   1848      InlineStylesAtInsertionPoint::Preserve, *editingHost);
   1849  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   1850                       "HTMLEditor::InsertHTMLWithContextAsSubAction("
   1851                       "InlineStylesAtInsertionPoint::Preserve) failed");
   1852  return EditorBase::ToGenericNSResult(rv);
   1853 }
   1854 
   1855 nsresult HTMLEditor::BlobReader::OnError(const nsAString& aError) {
   1856  AutoTArray<nsString, 1> error;
   1857  error.AppendElement(aError);
   1858  nsContentUtils::ReportToConsole(
   1859      nsIScriptError::warningFlag, "Editor"_ns, mHTMLEditor->GetDocument(),
   1860      nsContentUtils::eDOM_PROPERTIES, "EditorFileDropFailed", error);
   1861  return NS_OK;
   1862 }
   1863 
   1864 class SlurpBlobEventListener final : public nsIDOMEventListener {
   1865 public:
   1866  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   1867  NS_DECL_CYCLE_COLLECTION_CLASS(SlurpBlobEventListener)
   1868 
   1869  explicit SlurpBlobEventListener(HTMLEditor::BlobReader* aListener)
   1870      : mListener(aListener) {}
   1871 
   1872  MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override;
   1873 
   1874 private:
   1875  ~SlurpBlobEventListener() = default;
   1876 
   1877  RefPtr<HTMLEditor::BlobReader> mListener;
   1878 };
   1879 
   1880 NS_IMPL_CYCLE_COLLECTION(SlurpBlobEventListener, mListener)
   1881 
   1882 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SlurpBlobEventListener)
   1883  NS_INTERFACE_MAP_ENTRY(nsISupports)
   1884  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
   1885 NS_INTERFACE_MAP_END
   1886 
   1887 NS_IMPL_CYCLE_COLLECTING_ADDREF(SlurpBlobEventListener)
   1888 NS_IMPL_CYCLE_COLLECTING_RELEASE(SlurpBlobEventListener)
   1889 
   1890 NS_IMETHODIMP SlurpBlobEventListener::HandleEvent(Event* aEvent) {
   1891  EventTarget* target = aEvent->GetTarget();
   1892  if (!target || !mListener) {
   1893    return NS_OK;
   1894  }
   1895 
   1896  RefPtr<FileReader> reader = do_QueryObject(target);
   1897  if (!reader) {
   1898    return NS_OK;
   1899  }
   1900 
   1901  EventMessage message = aEvent->WidgetEventPtr()->mMessage;
   1902 
   1903  RefPtr<HTMLEditor::BlobReader> listener(mListener);
   1904  if (message == eLoad) {
   1905    MOZ_ASSERT(reader->DataFormat() == FileReader::FILE_AS_BINARY);
   1906 
   1907    // The original data has been converted from Latin1 to UTF-16, this just
   1908    // undoes that conversion.
   1909    DebugOnly<nsresult> rvIgnored =
   1910        listener->OnResult(NS_LossyConvertUTF16toASCII(reader->Result()));
   1911    NS_WARNING_ASSERTION(
   1912        NS_SUCCEEDED(rvIgnored),
   1913        "HTMLEditor::BlobReader::OnResult() failed, but ignored");
   1914    return NS_OK;
   1915  }
   1916 
   1917  if (message == eLoadError) {
   1918    nsAutoString errorMessage;
   1919    reader->GetError()->GetErrorMessage(errorMessage);
   1920    DebugOnly<nsresult> rvIgnored = listener->OnError(errorMessage);
   1921    NS_WARNING_ASSERTION(
   1922        NS_SUCCEEDED(rvIgnored),
   1923        "HTMLEditor::BlobReader::OnError() failed, but ignored");
   1924    return NS_OK;
   1925  }
   1926 
   1927  return NS_OK;
   1928 }
   1929 
   1930 // static
   1931 nsresult HTMLEditor::SlurpBlob(Blob* aBlob, nsIGlobalObject* aGlobal,
   1932                               BlobReader* aBlobReader) {
   1933  MOZ_ASSERT(aBlob);
   1934  MOZ_ASSERT(aGlobal);
   1935  MOZ_ASSERT(aBlobReader);
   1936 
   1937  RefPtr<WeakWorkerRef> workerRef;
   1938  RefPtr<FileReader> reader = new FileReader(aGlobal, workerRef);
   1939 
   1940  RefPtr<SlurpBlobEventListener> eventListener =
   1941      new SlurpBlobEventListener(aBlobReader);
   1942 
   1943  nsresult rv = reader->AddEventListener(u"load"_ns, eventListener, false);
   1944  if (NS_FAILED(rv)) {
   1945    NS_WARNING("FileReader::AddEventListener(load) failed");
   1946    return rv;
   1947  }
   1948 
   1949  rv = reader->AddEventListener(u"error"_ns, eventListener, false);
   1950  if (NS_FAILED(rv)) {
   1951    NS_WARNING("FileReader::AddEventListener(error) failed");
   1952    return rv;
   1953  }
   1954 
   1955  ErrorResult error;
   1956  reader->ReadAsBinaryString(*aBlob, error);
   1957  NS_WARNING_ASSERTION(!error.Failed(),
   1958                       "FileReader::ReadAsBinaryString() failed");
   1959  return error.StealNSResult();
   1960 }
   1961 
   1962 nsresult HTMLEditor::InsertObject(const nsACString& aType, nsISupports* aObject,
   1963                                  SafeToInsertData aSafeToInsertData,
   1964                                  const EditorDOMPoint& aPointToInsert,
   1965                                  DeleteSelectedContent aDeleteSelectedContent,
   1966                                  const Element& aEditingHost) {
   1967  MOZ_ASSERT(IsEditActionDataAvailable());
   1968 
   1969  // Check to see if we the file is actually an image.
   1970  nsAutoCString type(aType);
   1971  if (type.EqualsLiteral(kFileMime)) {
   1972    if (nsCOMPtr<nsIFile> file = do_QueryInterface(aObject)) {
   1973      nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1");
   1974      if (NS_WARN_IF(!mime)) {
   1975        return NS_ERROR_FAILURE;
   1976      }
   1977 
   1978      nsresult rv = mime->GetTypeFromFile(file, type);
   1979      if (NS_FAILED(rv)) {
   1980        NS_WARNING("nsIMIMEService::GetTypeFromFile() failed");
   1981        return rv;
   1982      }
   1983    }
   1984  }
   1985 
   1986  nsCOMPtr<nsISupports> object = aObject;
   1987  if (type.EqualsLiteral(kJPEGImageMime) || type.EqualsLiteral(kJPGImageMime) ||
   1988      type.EqualsLiteral(kPNGImageMime) || type.EqualsLiteral(kGIFImageMime)) {
   1989    if (nsCOMPtr<nsIFile> file = do_QueryInterface(object)) {
   1990      object = new FileBlobImpl(file);
   1991      // Fallthrough to BlobImpl code below.
   1992    } else if (RefPtr<Blob> blob = do_QueryObject(object)) {
   1993      object = blob->Impl();
   1994      // Fallthrough.
   1995    } else if (nsCOMPtr<nsIInputStream> imageStream =
   1996                   do_QueryInterface(object)) {
   1997      nsCString imageData;
   1998      nsresult rv = NS_ConsumeStream(imageStream, UINT32_MAX, imageData);
   1999      if (NS_FAILED(rv)) {
   2000        NS_WARNING("NS_ConsumeStream() failed");
   2001        return rv;
   2002      }
   2003 
   2004      rv = imageStream->Close();
   2005      if (NS_FAILED(rv)) {
   2006        NS_WARNING("nsIInputStream::Close() failed");
   2007        return rv;
   2008      }
   2009 
   2010      nsAutoString stuffToPaste;
   2011      rv = ImgFromData(type, imageData, stuffToPaste);
   2012      if (NS_FAILED(rv)) {
   2013        NS_WARNING("ImgFromData() failed");
   2014        return rv;
   2015      }
   2016 
   2017      AutoPlaceholderBatch treatAsOneTransaction(
   2018          *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   2019      rv = InsertHTMLWithContextAsSubAction(
   2020          stuffToPaste, u""_ns, u""_ns,
   2021          NS_LITERAL_STRING_FROM_CSTRING(kFileMime), aSafeToInsertData,
   2022          aPointToInsert, aDeleteSelectedContent,
   2023          InlineStylesAtInsertionPoint::Preserve, aEditingHost);
   2024      NS_WARNING_ASSERTION(
   2025          NS_SUCCEEDED(rv),
   2026          "HTMLEditor::InsertHTMLWithContextAsSubAction("
   2027          "InlineStylesAtInsertionPoint::Preserve) failed, but ignored");
   2028      return NS_OK;
   2029    } else {
   2030      NS_WARNING("HTMLEditor::InsertObject: Unexpected type for image mime");
   2031      return NS_OK;
   2032    }
   2033  }
   2034 
   2035  // We always try to insert BlobImpl even without a known image mime.
   2036  nsCOMPtr<BlobImpl> blob = do_QueryInterface(object);
   2037  if (!blob) {
   2038    return NS_OK;
   2039  }
   2040 
   2041  RefPtr<BlobReader> br =
   2042      new BlobReader(blob, this, aSafeToInsertData, aPointToInsert,
   2043                     aDeleteSelectedContent, aEditingHost);
   2044 
   2045  nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
   2046  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(inner);
   2047  if (!global) {
   2048    NS_WARNING("Could not get global");
   2049    return NS_ERROR_FAILURE;
   2050  }
   2051 
   2052  RefPtr<Blob> domBlob = Blob::Create(global, blob);
   2053  if (!domBlob) {
   2054    NS_WARNING("Blob::Create() failed");
   2055    return NS_ERROR_FAILURE;
   2056  }
   2057 
   2058  nsresult rv = SlurpBlob(domBlob, global, br);
   2059  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::::SlurpBlob() failed");
   2060  return rv;
   2061 }
   2062 
   2063 static bool GetString(nsISupports* aData, nsAString& aText) {
   2064  if (nsCOMPtr<nsISupportsString> str = do_QueryInterface(aData)) {
   2065    DebugOnly<nsresult> rvIgnored = str->GetData(aText);
   2066    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   2067                         "nsISupportsString::GetData() failed, but ignored");
   2068    return !aText.IsEmpty();
   2069  }
   2070 
   2071  return false;
   2072 }
   2073 
   2074 static bool GetCString(nsISupports* aData, nsACString& aText) {
   2075  if (nsCOMPtr<nsISupportsCString> str = do_QueryInterface(aData)) {
   2076    DebugOnly<nsresult> rvIgnored = str->GetData(aText);
   2077    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   2078                         "nsISupportsString::GetData() failed, but ignored");
   2079    return !aText.IsEmpty();
   2080  }
   2081 
   2082  return false;
   2083 }
   2084 
   2085 nsresult HTMLEditor::InsertFromTransferableAtSelection(
   2086    nsITransferable* aTransferable, const nsAString& aContextStr,
   2087    const nsAString& aInfoStr, HavePrivateHTMLFlavor aHavePrivateHTMLFlavor,
   2088    const Element& aEditingHost) {
   2089  nsAutoCString bestFlavor;
   2090  nsCOMPtr<nsISupports> genericDataObj;
   2091 
   2092  // See `HTMLTransferablePreparer::AddDataFlavorsInBestOrder`.
   2093  nsresult rv = aTransferable->GetAnyTransferData(
   2094      bestFlavor, getter_AddRefs(genericDataObj));
   2095  NS_WARNING_ASSERTION(
   2096      NS_SUCCEEDED(rv),
   2097      "nsITransferable::GetAnyTransferData() failed, but ignored");
   2098  if (NS_SUCCEEDED(rv)) {
   2099    AutoTransactionsConserveSelection dontChangeMySelection(*this);
   2100    nsAutoString flavor;
   2101    CopyASCIItoUTF16(bestFlavor, flavor);
   2102    const SafeToInsertData safeToInsertData = IsSafeToInsertData(nullptr);
   2103 
   2104    if (bestFlavor.EqualsLiteral(kFileMime) ||
   2105        bestFlavor.EqualsLiteral(kJPEGImageMime) ||
   2106        bestFlavor.EqualsLiteral(kJPGImageMime) ||
   2107        bestFlavor.EqualsLiteral(kPNGImageMime) ||
   2108        bestFlavor.EqualsLiteral(kGIFImageMime)) {
   2109      nsresult rv = InsertObject(bestFlavor, genericDataObj, safeToInsertData,
   2110                                 EditorDOMPoint(), DeleteSelectedContent::Yes,
   2111                                 aEditingHost);
   2112      if (NS_FAILED(rv)) {
   2113        NS_WARNING("HTMLEditor::InsertObject() failed");
   2114        return rv;
   2115      }
   2116    } else if (bestFlavor.EqualsLiteral(kNativeHTMLMime)) {
   2117      // note cf_html uses utf8
   2118      nsAutoCString cfhtml;
   2119      if (GetCString(genericDataObj, cfhtml)) {
   2120        // cfselection left emtpy for now.
   2121        nsString cfcontext, cffragment, cfselection;
   2122        nsresult rv = ParseCFHTML(cfhtml, getter_Copies(cffragment),
   2123                                  getter_Copies(cfcontext));
   2124        if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty()) {
   2125          // If we have our private HTML flavor, we will only use the fragment
   2126          // from the CF_HTML. The rest comes from the clipboard.
   2127          if (aHavePrivateHTMLFlavor == HavePrivateHTMLFlavor::Yes) {
   2128            AutoPlaceholderBatch treatAsOneTransaction(
   2129                *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   2130            rv = InsertHTMLWithContextAsSubAction(
   2131                cffragment, aContextStr, aInfoStr, flavor, safeToInsertData,
   2132                EditorDOMPoint(), DeleteSelectedContent::Yes,
   2133                InlineStylesAtInsertionPoint::Clear, aEditingHost);
   2134            if (NS_FAILED(rv)) {
   2135              NS_WARNING(
   2136                  "HTMLEditor::InsertHTMLWithContextAsSubAction("
   2137                  "DeleteSelectedContent::Yes, "
   2138                  "InlineStylesAtInsertionPoint::Clear) failed");
   2139              return rv;
   2140            }
   2141          } else {
   2142            AutoPlaceholderBatch treatAsOneTransaction(
   2143                *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   2144            rv = InsertHTMLWithContextAsSubAction(
   2145                cffragment, cfcontext, cfselection, flavor, safeToInsertData,
   2146                EditorDOMPoint(), DeleteSelectedContent::Yes,
   2147                InlineStylesAtInsertionPoint::Clear, aEditingHost);
   2148            if (NS_FAILED(rv)) {
   2149              NS_WARNING(
   2150                  "HTMLEditor::InsertHTMLWithContextAsSubAction("
   2151                  "DeleteSelectedContent::Yes, "
   2152                  "InlineStylesAtInsertionPoint::Clear) failed");
   2153              return rv;
   2154            }
   2155          }
   2156        } else {
   2157          // In some platforms (like Linux), the clipboard might return data
   2158          // requested for unknown flavors (for example:
   2159          // application/x-moz-nativehtml).  In this case, treat the data
   2160          // to be pasted as mere HTML to get the best chance of pasting it
   2161          // correctly.
   2162          bestFlavor.AssignLiteral(kHTMLMime);
   2163          // Fall through the next case
   2164        }
   2165      }
   2166    }
   2167    if (bestFlavor.EqualsLiteral(kHTMLMime) ||
   2168        bestFlavor.EqualsLiteral(kTextMime) ||
   2169        bestFlavor.EqualsLiteral(kMozTextInternal)) {
   2170      nsAutoString stuffToPaste;
   2171      if (!GetString(genericDataObj, stuffToPaste)) {
   2172        nsAutoCString text;
   2173        if (GetCString(genericDataObj, text)) {
   2174          CopyUTF8toUTF16(text, stuffToPaste);
   2175        }
   2176      }
   2177 
   2178      if (!stuffToPaste.IsEmpty()) {
   2179        if (bestFlavor.EqualsLiteral(kHTMLMime)) {
   2180          AutoPlaceholderBatch treatAsOneTransaction(
   2181              *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   2182          nsresult rv = InsertHTMLWithContextAsSubAction(
   2183              stuffToPaste, aContextStr, aInfoStr, flavor, safeToInsertData,
   2184              EditorDOMPoint(), DeleteSelectedContent::Yes,
   2185              InlineStylesAtInsertionPoint::Clear, aEditingHost);
   2186          if (NS_FAILED(rv)) {
   2187            NS_WARNING(
   2188                "HTMLEditor::InsertHTMLWithContextAsSubAction("
   2189                "DeleteSelectedContent::Yes, "
   2190                "InlineStylesAtInsertionPoint::Clear) failed");
   2191            return rv;
   2192          }
   2193        } else {
   2194          AutoPlaceholderBatch treatAsOneTransaction(
   2195              *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   2196          nsresult rv =
   2197              InsertTextAsSubAction(stuffToPaste, InsertTextFor::NormalText);
   2198          if (NS_FAILED(rv)) {
   2199            NS_WARNING("EditorBase::InsertTextAsSubAction() failed");
   2200            return rv;
   2201          }
   2202        }
   2203      }
   2204    }
   2205  }
   2206 
   2207  // Try to scroll the selection into view if the paste succeeded
   2208  DebugOnly<nsresult> rvIgnored = ScrollSelectionFocusIntoView();
   2209  NS_WARNING_ASSERTION(
   2210      NS_SUCCEEDED(rvIgnored),
   2211      "EditorBase::ScrollSelectionFocusIntoView() failed, but ignored");
   2212  return NS_OK;
   2213 }
   2214 
   2215 static void GetStringFromDataTransfer(const DataTransfer* aDataTransfer,
   2216                                      const nsAString& aType, uint32_t aIndex,
   2217                                      nsString& aOutputString) {
   2218  nsCOMPtr<nsIVariant> variant;
   2219  DebugOnly<nsresult> rvIgnored = aDataTransfer->GetDataAtNoSecurityCheck(
   2220      aType, aIndex, getter_AddRefs(variant));
   2221  if (!variant) {
   2222    MOZ_ASSERT(aOutputString.IsEmpty());
   2223    return;
   2224  }
   2225  NS_WARNING_ASSERTION(
   2226      NS_SUCCEEDED(rvIgnored),
   2227      "DataTransfer::GetDataAtNoSecurityCheck() failed, but ignored");
   2228  variant->GetAsAString(aOutputString);
   2229  nsContentUtils::PlatformToDOMLineBreaks(aOutputString);
   2230 }
   2231 
   2232 nsresult HTMLEditor::InsertFromDataTransfer(
   2233    const DataTransfer* aDataTransfer, uint32_t aIndex,
   2234    nsIPrincipal* aSourcePrincipal, const EditorDOMPoint& aDroppedAt,
   2235    DeleteSelectedContent aDeleteSelectedContent, const Element& aEditingHost) {
   2236  MOZ_ASSERT(GetEditAction() == EditAction::eDrop ||
   2237             GetEditAction() == EditAction::ePaste);
   2238  MOZ_ASSERT(mPlaceholderBatch,
   2239             "HTMLEditor::InsertFromDataTransfer() should be called by "
   2240             "HandleDropEvent() or paste action and there should've already "
   2241             "been placeholder transaction");
   2242  MOZ_ASSERT_IF(GetEditAction() == EditAction::eDrop, aDroppedAt.IsSet());
   2243 
   2244  ErrorResult error;
   2245  RefPtr<DOMStringList> types = aDataTransfer->MozTypesAt(aIndex, error);
   2246  if (error.Failed()) {
   2247    NS_WARNING("DataTransfer::MozTypesAt() failed");
   2248    return error.StealNSResult();
   2249  }
   2250 
   2251  const bool hasPrivateHTMLFlavor =
   2252      types->Contains(NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext));
   2253 
   2254  const bool isPlaintextEditor = IsPlaintextMailComposer() ||
   2255                                 aEditingHost.IsContentEditablePlainTextOnly();
   2256  const SafeToInsertData safeToInsertData =
   2257      IsSafeToInsertData(aSourcePrincipal);
   2258 
   2259  uint32_t length = types->Length();
   2260  for (uint32_t i = 0; i < length; i++) {
   2261    nsAutoString type;
   2262    types->Item(i, type);
   2263 
   2264    if (!isPlaintextEditor) {
   2265      if (type.EqualsLiteral(kFileMime) || type.EqualsLiteral(kJPEGImageMime) ||
   2266          type.EqualsLiteral(kJPGImageMime) ||
   2267          type.EqualsLiteral(kPNGImageMime) ||
   2268          type.EqualsLiteral(kGIFImageMime)) {
   2269        nsCOMPtr<nsIVariant> variant;
   2270        DebugOnly<nsresult> rvIgnored = aDataTransfer->GetDataAtNoSecurityCheck(
   2271            type, aIndex, getter_AddRefs(variant));
   2272        if (variant) {
   2273          NS_WARNING_ASSERTION(
   2274              NS_SUCCEEDED(rvIgnored),
   2275              "DataTransfer::GetDataAtNoSecurityCheck() failed, but ignored");
   2276          nsCOMPtr<nsISupports> object;
   2277          rvIgnored = variant->GetAsISupports(getter_AddRefs(object));
   2278          NS_WARNING_ASSERTION(
   2279              NS_SUCCEEDED(rvIgnored),
   2280              "nsIVariant::GetAsISupports() failed, but ignored");
   2281          nsresult rv = InsertObject(NS_ConvertUTF16toUTF8(type), object,
   2282                                     safeToInsertData, aDroppedAt,
   2283                                     aDeleteSelectedContent, aEditingHost);
   2284          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2285                               "HTMLEditor::InsertObject() failed");
   2286          return rv;
   2287        }
   2288      } else if (type.EqualsLiteral(kNativeHTMLMime)) {
   2289        // Windows only clipboard parsing.
   2290        nsAutoString text;
   2291        GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
   2292        NS_ConvertUTF16toUTF8 cfhtml(text);
   2293 
   2294        nsString cfcontext, cffragment,
   2295            cfselection;  // cfselection left emtpy for now
   2296 
   2297        nsresult rv = ParseCFHTML(cfhtml, getter_Copies(cffragment),
   2298                                  getter_Copies(cfcontext));
   2299        if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty()) {
   2300          if (hasPrivateHTMLFlavor) {
   2301            // If we have our private HTML flavor, we will only use the fragment
   2302            // from the CF_HTML. The rest comes from the clipboard.
   2303            nsAutoString contextString, infoString;
   2304            GetStringFromDataTransfer(
   2305                aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext),
   2306                aIndex, contextString);
   2307            GetStringFromDataTransfer(aDataTransfer,
   2308                                      NS_LITERAL_STRING_FROM_CSTRING(kHTMLInfo),
   2309                                      aIndex, infoString);
   2310            AutoPlaceholderBatch treatAsOneTransaction(
   2311                *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   2312            nsresult rv = InsertHTMLWithContextAsSubAction(
   2313                cffragment, contextString, infoString, type, safeToInsertData,
   2314                aDroppedAt, aDeleteSelectedContent,
   2315                InlineStylesAtInsertionPoint::Clear, aEditingHost);
   2316            NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2317                                 "HTMLEditor::InsertHTMLWithContextAsSubAction("
   2318                                 "InlineStylesAtInsertionPoint::Clear) failed");
   2319            return rv;
   2320          }
   2321          AutoPlaceholderBatch treatAsOneTransaction(
   2322              *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   2323          nsresult rv = InsertHTMLWithContextAsSubAction(
   2324              cffragment, cfcontext, cfselection, type, safeToInsertData,
   2325              aDroppedAt, aDeleteSelectedContent,
   2326              InlineStylesAtInsertionPoint::Clear, aEditingHost);
   2327          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2328                               "HTMLEditor::InsertHTMLWithContextAsSubAction("
   2329                               "InlineStylesAtInsertionPoint::Clear) failed");
   2330          return rv;
   2331        }
   2332      } else if (type.EqualsLiteral(kHTMLMime)) {
   2333        nsAutoString text, contextString, infoString;
   2334        GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
   2335        GetStringFromDataTransfer(aDataTransfer,
   2336                                  NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext),
   2337                                  aIndex, contextString);
   2338        GetStringFromDataTransfer(aDataTransfer,
   2339                                  NS_LITERAL_STRING_FROM_CSTRING(kHTMLInfo),
   2340                                  aIndex, infoString);
   2341        if (type.EqualsLiteral(kHTMLMime)) {
   2342          AutoPlaceholderBatch treatAsOneTransaction(
   2343              *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   2344          nsresult rv = InsertHTMLWithContextAsSubAction(
   2345              text, contextString, infoString, type, safeToInsertData,
   2346              aDroppedAt, aDeleteSelectedContent,
   2347              InlineStylesAtInsertionPoint::Clear, aEditingHost);
   2348          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2349                               "HTMLEditor::InsertHTMLWithContextAsSubAction("
   2350                               "InlineStylesAtInsertionPoint::Clear) failed");
   2351          return rv;
   2352        }
   2353      }
   2354    }
   2355 
   2356    if (type.EqualsLiteral(kTextMime) || type.EqualsLiteral(kMozTextInternal)) {
   2357      nsAutoString text;
   2358      GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
   2359      AutoPlaceholderBatch treatAsOneTransaction(
   2360          *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   2361      nsresult rv = InsertTextAt(text, aDroppedAt, aDeleteSelectedContent);
   2362      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2363                           "EditorBase::InsertTextAt() failed");
   2364      return rv;
   2365    }
   2366  }
   2367 
   2368  return NS_OK;
   2369 }
   2370 
   2371 // static
   2372 HTMLEditor::HavePrivateHTMLFlavor
   2373 HTMLEditor::DataTransferOrClipboardHasPrivateHTMLFlavor(
   2374    DataTransfer* aDataTransfer, nsIClipboard* aClipboard) {
   2375  nsresult rv;
   2376  if (aDataTransfer) {
   2377    return aDataTransfer->HasPrivateHTMLFlavor() ? HavePrivateHTMLFlavor::Yes
   2378                                                 : HavePrivateHTMLFlavor::No;
   2379  }
   2380  // otherwise, fall back to clipboard
   2381  if (NS_WARN_IF(!aClipboard)) {
   2382    return HavePrivateHTMLFlavor::No;
   2383  }
   2384 
   2385  // check the clipboard for our special kHTMLContext flavor.  If that is there,
   2386  // we know we have our own internal html format on clipboard.
   2387  bool hasPrivateHTMLFlavor = false;
   2388  AutoTArray<nsCString, 1> flavArray = {nsDependentCString(kHTMLContext)};
   2389  rv = aClipboard->HasDataMatchingFlavors(
   2390      flavArray, nsIClipboard::kGlobalClipboard, &hasPrivateHTMLFlavor);
   2391  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2392                       "nsIClipboard::HasDataMatchingFlavors(nsIClipboard::"
   2393                       "kGlobalClipboard) failed");
   2394  return NS_SUCCEEDED(rv) && hasPrivateHTMLFlavor ? HavePrivateHTMLFlavor::Yes
   2395                                                  : HavePrivateHTMLFlavor::No;
   2396 }
   2397 
   2398 nsresult HTMLEditor::HandlePaste(AutoEditActionDataSetter& aEditActionData,
   2399                                 nsIClipboard::ClipboardType aClipboardType,
   2400                                 DataTransfer* aDataTransfer) {
   2401  aEditActionData.InitializeDataTransferWithClipboard(
   2402      SettingDataTransfer::eWithFormat, aDataTransfer, aClipboardType);
   2403  nsresult rv = aEditActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
   2404  if (NS_FAILED(rv)) {
   2405    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   2406                         "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
   2407    return rv;
   2408  }
   2409  const RefPtr<Element> editingHost =
   2410      ComputeEditingHost(LimitInBodyElement::No);
   2411  if (NS_WARN_IF(!editingHost)) {
   2412    return NS_ERROR_FAILURE;
   2413  }
   2414  rv = PasteInternal(aClipboardType, aDataTransfer, *editingHost);
   2415  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::PasteInternal() failed");
   2416  return rv;
   2417 }
   2418 
   2419 nsresult HTMLEditor::PasteInternal(nsIClipboard::ClipboardType aClipboardType,
   2420                                   DataTransfer* aDataTransfer,
   2421                                   const Element& aEditingHost) {
   2422  MOZ_ASSERT(IsEditActionDataAvailable());
   2423 
   2424  if (MOZ_UNLIKELY(!IsModifiable())) {
   2425    return NS_OK;
   2426  }
   2427 
   2428  // Get Clipboard Service
   2429  nsresult rv = NS_OK;
   2430  nsCOMPtr<nsIClipboard> clipboard =
   2431      do_GetService("@mozilla.org/widget/clipboard;1", &rv);
   2432  if (NS_FAILED(rv)) {
   2433    NS_WARNING("Failed to get nsIClipboard service");
   2434    return rv;
   2435  }
   2436 
   2437  // Get the nsITransferable interface for getting the data from the clipboard
   2438  nsCOMPtr<nsITransferable> transferable;
   2439  rv = PrepareHTMLTransferable(getter_AddRefs(transferable), &aEditingHost);
   2440  if (NS_FAILED(rv)) {
   2441    NS_WARNING("HTMLEditor::PrepareHTMLTransferable() failed");
   2442    return rv;
   2443  }
   2444  if (!transferable) {
   2445    NS_WARNING("HTMLEditor::PrepareHTMLTransferable() returned nullptr");
   2446    return NS_ERROR_FAILURE;
   2447  }
   2448  // Get the Data from the clipboard
   2449  rv = GetDataFromDataTransferOrClipboard(aDataTransfer, transferable,
   2450                                          aClipboardType);
   2451  if (NS_FAILED(rv)) {
   2452    NS_WARNING("EditorBase::GetDataFromDataTransferOrClipboard() failed");
   2453    return rv;
   2454  }
   2455 
   2456  // also get additional html copy hints, if present
   2457  nsAutoString contextStr, infoStr;
   2458 
   2459  // If we have our internal html flavor on the clipboard, there is special
   2460  // context to use instead of cfhtml context.
   2461  const HavePrivateHTMLFlavor clipboardHasPrivateHTMLFlavor =
   2462      DataTransferOrClipboardHasPrivateHTMLFlavor(aDataTransfer, clipboard);
   2463  if (clipboardHasPrivateHTMLFlavor == HavePrivateHTMLFlavor::Yes) {
   2464    nsCOMPtr<nsITransferable> contextTransferable =
   2465        do_CreateInstance("@mozilla.org/widget/transferable;1");
   2466    if (!contextTransferable) {
   2467      NS_WARNING(
   2468          "do_CreateInstance() failed to create nsITransferable instance");
   2469      return NS_ERROR_FAILURE;
   2470    }
   2471    DebugOnly<nsresult> rvIgnored = contextTransferable->Init(nullptr);
   2472    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   2473                         "nsITransferable::Init() failed, but ignored");
   2474    contextTransferable->SetIsPrivateData(transferable->GetIsPrivateData());
   2475    rvIgnored = contextTransferable->AddDataFlavor(kHTMLContext);
   2476    NS_WARNING_ASSERTION(
   2477        NS_SUCCEEDED(rvIgnored),
   2478        "nsITransferable::AddDataFlavor(kHTMLContext) failed, but ignored");
   2479    GetDataFromDataTransferOrClipboard(aDataTransfer, contextTransferable,
   2480                                       aClipboardType);
   2481    nsCOMPtr<nsISupports> contextDataObj;
   2482    rv = contextTransferable->GetTransferData(kHTMLContext,
   2483                                              getter_AddRefs(contextDataObj));
   2484    if (NS_SUCCEEDED(rv) && contextDataObj) {
   2485      if (nsCOMPtr<nsISupportsString> str = do_QueryInterface(contextDataObj)) {
   2486        DebugOnly<nsresult> rvIgnored = str->GetData(contextStr);
   2487        NS_WARNING_ASSERTION(
   2488            NS_SUCCEEDED(rvIgnored),
   2489            "nsISupportsString::GetData() failed, but ignored");
   2490      }
   2491    }
   2492 
   2493    nsCOMPtr<nsITransferable> infoTransferable =
   2494        do_CreateInstance("@mozilla.org/widget/transferable;1");
   2495    if (!infoTransferable) {
   2496      NS_WARNING(
   2497          "do_CreateInstance() failed to create nsITransferable instance");
   2498      return NS_ERROR_FAILURE;
   2499    }
   2500    rvIgnored = infoTransferable->Init(nullptr);
   2501    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2502                         "nsITransferable::Init() failed, but ignored");
   2503    contextTransferable->SetIsPrivateData(transferable->GetIsPrivateData());
   2504    rvIgnored = infoTransferable->AddDataFlavor(kHTMLInfo);
   2505    NS_WARNING_ASSERTION(
   2506        NS_SUCCEEDED(rvIgnored),
   2507        "nsITransferable::AddDataFlavor(kHTMLInfo) failed, but ignored");
   2508 
   2509    GetDataFromDataTransferOrClipboard(aDataTransfer, infoTransferable,
   2510                                       aClipboardType);
   2511    nsCOMPtr<nsISupports> infoDataObj;
   2512    rv = infoTransferable->GetTransferData(kHTMLInfo,
   2513                                           getter_AddRefs(infoDataObj));
   2514    if (NS_SUCCEEDED(rv) && infoDataObj) {
   2515      if (nsCOMPtr<nsISupportsString> str = do_QueryInterface(infoDataObj)) {
   2516        DebugOnly<nsresult> rvIgnored = str->GetData(infoStr);
   2517        NS_WARNING_ASSERTION(
   2518            NS_SUCCEEDED(rvIgnored),
   2519            "nsISupportsString::GetData() failed, but ignored");
   2520      }
   2521    }
   2522  }
   2523 
   2524  rv = InsertFromTransferableAtSelection(transferable, contextStr, infoStr,
   2525                                         clipboardHasPrivateHTMLFlavor,
   2526                                         aEditingHost);
   2527  NS_WARNING_ASSERTION(
   2528      NS_SUCCEEDED(rv),
   2529      "HTMLEditor::InsertFromTransferableAtSelection() failed");
   2530  return rv;
   2531 }
   2532 
   2533 nsresult HTMLEditor::HandlePasteTransferable(
   2534    AutoEditActionDataSetter& aEditActionData, nsITransferable& aTransferable) {
   2535  // InitializeDataTransfer may fetch input stream in aTransferable, so it
   2536  // may be invalid after calling this.
   2537  aEditActionData.InitializeDataTransfer(&aTransferable);
   2538 
   2539  nsresult rv = aEditActionData.MaybeDispatchBeforeInputEvent();
   2540  if (NS_FAILED(rv)) {
   2541    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   2542                         "MaybeDispatchBeforeInputEvent(), failed");
   2543    return rv;
   2544  }
   2545 
   2546  const RefPtr<Element> editingHost =
   2547      ComputeEditingHost(LimitInBodyElement::No);
   2548  if (NS_WARN_IF(!editingHost)) {
   2549    return NS_ERROR_FAILURE;
   2550  }
   2551 
   2552  RefPtr<DataTransfer> dataTransfer = GetInputEventDataTransfer();
   2553  if (dataTransfer->HasFile() && dataTransfer->MozItemCount() > 0) {
   2554    // Now aTransferable has moved to DataTransfer. Use DataTransfer.
   2555    AutoPlaceholderBatch treatAsOneTransaction(
   2556        *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   2557 
   2558    rv = InsertFromDataTransfer(dataTransfer, 0, nullptr, EditorDOMPoint(),
   2559                                DeleteSelectedContent::Yes, *editingHost);
   2560    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2561                         "HTMLEditor::InsertFromDataTransfer("
   2562                         "DeleteSelectedContent::Yes) failed");
   2563    return rv;
   2564  }
   2565 
   2566  nsAutoString contextStr, infoStr;
   2567  rv = InsertFromTransferableAtSelection(&aTransferable, contextStr, infoStr,
   2568                                         HavePrivateHTMLFlavor::No,
   2569                                         *editingHost);
   2570  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2571                       "HTMLEditor::InsertFromTransferableAtSelection("
   2572                       "HavePrivateHTMLFlavor::No) failed");
   2573  return rv;
   2574 }
   2575 
   2576 nsresult HTMLEditor::PasteNoFormattingAsAction(
   2577    nsIClipboard::ClipboardType aClipboardType,
   2578    DispatchPasteEvent aDispatchPasteEvent,
   2579    DataTransfer* aDataTransfer /* = nullptr */,
   2580    nsIPrincipal* aPrincipal /* = nullptr */) {
   2581  if (IsReadonly()) {
   2582    return NS_OK;
   2583  }
   2584  // Create the same DataTransfer object here so we can share it between
   2585  // the clipboard event and its data with the call to
   2586  // InsertFromTransferableWithSelection below. This prevents
   2587  // race conditions with Content Analysis on like we see in bug 1918027.
   2588  RefPtr<DataTransfer> dataTransfer =
   2589      aDataTransfer ? RefPtr<DataTransfer>(aDataTransfer)
   2590                    : RefPtr<DataTransfer>(CreateDataTransferForPaste(
   2591                          ePasteNoFormatting, aClipboardType));
   2592 
   2593  auto clearDataTransfer = MakeScopeExit([&] {
   2594    // If the caller passed in aDataTransfer, they are responsible for clearing
   2595    // this.
   2596    if (!aDataTransfer && dataTransfer) {
   2597      dataTransfer->ClearForPaste();
   2598    }
   2599  });
   2600 
   2601  AutoEditActionDataSetter editActionData(*this, EditAction::ePaste,
   2602                                          aPrincipal);
   2603  if (NS_WARN_IF(!editActionData.CanHandle())) {
   2604    return NS_ERROR_NOT_INITIALIZED;
   2605  }
   2606  editActionData.InitializeDataTransferWithClipboard(
   2607      SettingDataTransfer::eWithoutFormat, dataTransfer, aClipboardType);
   2608 
   2609  if (aDispatchPasteEvent == DispatchPasteEvent::Yes) {
   2610    RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager();
   2611    if (NS_WARN_IF(!focusManager)) {
   2612      return NS_ERROR_UNEXPECTED;
   2613    }
   2614    const RefPtr<Element> focusedElement = focusManager->GetFocusedElement();
   2615 
   2616    Result<ClipboardEventResult, nsresult> ret = Err(NS_ERROR_FAILURE);
   2617    {
   2618      // This method is not set up to pass back the new aDataTransfer
   2619      // if it changes. If we need this in the future, we can change
   2620      // aDataTransfer to be a RefPtr<DataTransfer>*.
   2621      MOZ_ASSERT(!aDataTransfer);
   2622      AutoTrackDataTransferForPaste trackDataTransfer(*this, dataTransfer);
   2623 
   2624      ret = DispatchClipboardEventAndUpdateClipboard(
   2625          ePasteNoFormatting, Some(aClipboardType), dataTransfer);
   2626      if (MOZ_UNLIKELY(ret.isErr())) {
   2627        NS_WARNING(
   2628            "EditorBase::DispatchClipboardEventAndUpdateClipboard("
   2629            "ePasteNoFormatting) failed");
   2630        return EditorBase::ToGenericNSResult(ret.unwrapErr());
   2631      }
   2632    }
   2633    switch (ret.inspect()) {
   2634      case ClipboardEventResult::DoDefault:
   2635        break;
   2636      case ClipboardEventResult::DefaultPreventedOfPaste:
   2637      case ClipboardEventResult::IgnoredOrError:
   2638        return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
   2639      case ClipboardEventResult::CopyOrCutHandled:
   2640        MOZ_ASSERT_UNREACHABLE("Invalid result for ePaste");
   2641    }
   2642 
   2643    // If focus is changed by a "paste" event listener, we should keep handling
   2644    // the "pasting" in new focused editor because Chrome works as so.
   2645    const RefPtr<Element> newFocusedElement = focusManager->GetFocusedElement();
   2646    if (MOZ_UNLIKELY(focusedElement != newFocusedElement)) {
   2647      // For the privacy reason, let's top handling it if new focused element is
   2648      // in different document.
   2649      if (focusManager->GetFocusedWindow() != GetWindow()) {
   2650        return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
   2651      }
   2652      RefPtr<EditorBase> editorBase =
   2653          nsContentUtils::GetActiveEditor(GetPresContext());
   2654      if (!editorBase || (editorBase->IsHTMLEditor() &&
   2655                          !editorBase->AsHTMLEditor()->IsActiveInDOMWindow())) {
   2656        return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
   2657      }
   2658      if (editorBase != this) {
   2659        if (editorBase->IsHTMLEditor()) {
   2660          nsresult rv = MOZ_KnownLive(editorBase->AsHTMLEditor())
   2661                            ->PasteNoFormattingAsAction(
   2662                                aClipboardType, DispatchPasteEvent::No,
   2663                                dataTransfer, aPrincipal);
   2664          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2665                               "HTMLEditor::PasteNoFormattingAsAction("
   2666                               "DispatchPasteEvent::No) failed");
   2667          return EditorBase::ToGenericNSResult(rv);
   2668        }
   2669        nsresult rv = editorBase->PasteAsAction(
   2670            aClipboardType, DispatchPasteEvent::No, dataTransfer, aPrincipal);
   2671        NS_WARNING_ASSERTION(
   2672            NS_SUCCEEDED(rv),
   2673            "EditorBase::PasteAsAction(DispatchPasteEvent::No) failed");
   2674        return EditorBase::ToGenericNSResult(rv);
   2675      }
   2676    }
   2677  }
   2678 
   2679  const RefPtr<Element> editingHost =
   2680      ComputeEditingHost(LimitInBodyElement::No);
   2681  if (NS_WARN_IF(!editingHost)) {
   2682    return NS_ERROR_FAILURE;
   2683  }
   2684 
   2685  // Dispatch "beforeinput" event after "paste" event.  And perhaps, before
   2686  // committing composition because if pasting is canceled, we don't need to
   2687  // commit the active composition.
   2688  nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
   2689  if (NS_FAILED(rv)) {
   2690    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   2691                         "MaybeDispatchBeforeInputEvent(), failed");
   2692    return EditorBase::ToGenericNSResult(rv);
   2693  }
   2694 
   2695  DebugOnly<nsresult> rvIgnored = CommitComposition();
   2696  if (NS_WARN_IF(Destroyed())) {
   2697    return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
   2698  }
   2699  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2700                       "EditorBase::CommitComposition() failed, but ignored");
   2701  if (MOZ_UNLIKELY(!IsModifiable())) {
   2702    return NS_OK;
   2703  }
   2704 
   2705  Result<nsCOMPtr<nsITransferable>, nsresult> maybeTransferable =
   2706      EditorUtils::CreateTransferableForPlainText(*GetDocument());
   2707  if (maybeTransferable.isErr()) {
   2708    NS_WARNING("EditorUtils::CreateTransferableForPlainText() failed");
   2709    return EditorBase::ToGenericNSResult(maybeTransferable.unwrapErr());
   2710  }
   2711  nsCOMPtr<nsITransferable> transferable(maybeTransferable.unwrap());
   2712  if (!transferable) {
   2713    NS_WARNING(
   2714        "EditorUtils::CreateTransferableForPlainText() returned nullptr, but "
   2715        "ignored");
   2716    return NS_OK;
   2717  }
   2718  rv = GetDataFromDataTransferOrClipboard(dataTransfer, transferable,
   2719                                          aClipboardType);
   2720  if (NS_FAILED(rv)) {
   2721    NS_WARNING("EditorBase::GetDataFromDataTransferOrClipboard() failed");
   2722    return rv;
   2723  }
   2724 
   2725  rv = InsertFromTransferableAtSelection(
   2726      transferable, u""_ns, u""_ns, HavePrivateHTMLFlavor::No, *editingHost);
   2727  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2728                       "HTMLEditor::InsertFromTransferableAtSelection("
   2729                       "HavePrivateHTMLFlavor::No) failed");
   2730  return EditorBase::ToGenericNSResult(rv);
   2731 }
   2732 
   2733 // The following arrays contain the MIME types that we can paste. The arrays
   2734 // are used by CanPaste() and CanPasteTransferable() below.
   2735 
   2736 static const char* textEditorFlavors[] = {kTextMime};
   2737 static const char* textHtmlEditorFlavors[] = {kTextMime,      kHTMLMime,
   2738                                              kJPEGImageMime, kJPGImageMime,
   2739                                              kPNGImageMime,  kGIFImageMime};
   2740 
   2741 bool HTMLEditor::CanPaste(nsIClipboard::ClipboardType aClipboardType) const {
   2742  if (AreClipboardCommandsUnconditionallyEnabled()) {
   2743    return true;
   2744  }
   2745 
   2746  // can't paste if readonly
   2747  if (!IsModifiable()) {
   2748    return false;
   2749  }
   2750 
   2751  const RefPtr<Element> editingHost =
   2752      ComputeEditingHost(LimitInBodyElement::No);
   2753  if (!editingHost) {
   2754    return false;
   2755  }
   2756 
   2757  nsresult rv;
   2758  nsCOMPtr<nsIClipboard> clipboard(
   2759      do_GetService("@mozilla.org/widget/clipboard;1", &rv));
   2760  if (NS_FAILED(rv)) {
   2761    NS_WARNING("Failed to get nsIClipboard service");
   2762    return false;
   2763  }
   2764 
   2765  // Use the flavors depending on the current editor mask
   2766  if (IsPlaintextMailComposer() ||
   2767      editingHost->IsContentEditablePlainTextOnly()) {
   2768    AutoTArray<nsCString, std::size(textEditorFlavors)> flavors;
   2769    flavors.AppendElements<const char*>(Span<const char*>(textEditorFlavors));
   2770    bool haveFlavors;
   2771    nsresult rv = clipboard->HasDataMatchingFlavors(flavors, aClipboardType,
   2772                                                    &haveFlavors);
   2773    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2774                         "nsIClipboard::HasDataMatchingFlavors() failed");
   2775    return NS_SUCCEEDED(rv) && haveFlavors;
   2776  }
   2777 
   2778  AutoTArray<nsCString, std::size(textHtmlEditorFlavors)> flavors;
   2779  flavors.AppendElements<const char*>(Span<const char*>(textHtmlEditorFlavors));
   2780  bool haveFlavors;
   2781  rv = clipboard->HasDataMatchingFlavors(flavors, aClipboardType, &haveFlavors);
   2782  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2783                       "nsIClipboard::HasDataMatchingFlavors() failed");
   2784  return NS_SUCCEEDED(rv) && haveFlavors;
   2785 }
   2786 
   2787 bool HTMLEditor::CanPasteTransferable(nsITransferable* aTransferable) {
   2788  // can't paste if readonly
   2789  if (!IsModifiable()) {
   2790    return false;
   2791  }
   2792 
   2793  const RefPtr<Element> editingHost =
   2794      ComputeEditingHost(LimitInBodyElement::No);
   2795  if (!editingHost) {
   2796    return false;
   2797  }
   2798 
   2799  // If |aTransferable| is null, assume that a paste will succeed.
   2800  if (!aTransferable) {
   2801    return true;
   2802  }
   2803 
   2804  // Peek in |aTransferable| to see if it contains a supported MIME type.
   2805 
   2806  // Use the flavors depending on the current editor mask
   2807  const char** flavors;
   2808  size_t length;
   2809  if (IsPlaintextMailComposer() ||
   2810      editingHost->IsContentEditablePlainTextOnly()) {
   2811    flavors = textEditorFlavors;
   2812    length = std::size(textEditorFlavors);
   2813  } else {
   2814    flavors = textHtmlEditorFlavors;
   2815    length = std::size(textHtmlEditorFlavors);
   2816  }
   2817 
   2818  for (size_t i = 0; i < length; i++, flavors++) {
   2819    nsCOMPtr<nsISupports> data;
   2820    nsresult rv =
   2821        aTransferable->GetTransferData(*flavors, getter_AddRefs(data));
   2822    if (NS_SUCCEEDED(rv) && data) {
   2823      return true;
   2824    }
   2825  }
   2826 
   2827  return false;
   2828 }
   2829 
   2830 nsresult HTMLEditor::HandlePasteAsQuotation(
   2831    AutoEditActionDataSetter& aEditActionData,
   2832    nsIClipboard::ClipboardType aClipboardType, DataTransfer* aDataTransfer) {
   2833  MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
   2834             aClipboardType == nsIClipboard::kSelectionClipboard);
   2835  aEditActionData.InitializeDataTransferWithClipboard(
   2836      SettingDataTransfer::eWithFormat, aDataTransfer, aClipboardType);
   2837  if (NS_WARN_IF(!aEditActionData.CanHandle())) {
   2838    return NS_ERROR_NOT_INITIALIZED;
   2839  }
   2840 
   2841  nsresult rv = aEditActionData.MaybeDispatchBeforeInputEvent();
   2842  if (NS_FAILED(rv)) {
   2843    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   2844                         "MaybeDispatchBeforeInputEvent(), failed");
   2845    return rv;
   2846  }
   2847 
   2848  const RefPtr<Element> editingHost =
   2849      ComputeEditingHost(LimitInBodyElement::No);
   2850  if (NS_WARN_IF(!editingHost)) {
   2851    return NS_ERROR_FAILURE;
   2852  }
   2853 
   2854  if (IsPlaintextMailComposer() ||
   2855      editingHost->IsContentEditablePlainTextOnly()) {
   2856    nsresult rv =
   2857        PasteAsPlaintextQuotation(aClipboardType, aDataTransfer, *editingHost);
   2858    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2859                         "HTMLEditor::PasteAsPlaintextQuotation() failed");
   2860    return rv;
   2861  }
   2862 
   2863  // If it's not in plain text edit mode, paste text into new
   2864  // <blockquote type="cite"> element after removing selection.
   2865 
   2866  {
   2867    // XXX Why don't we test these first?
   2868    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
   2869    if (MOZ_UNLIKELY(result.isErr())) {
   2870      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
   2871      return result.unwrapErr();
   2872    }
   2873    if (result.inspect().Canceled()) {
   2874      return NS_OK;
   2875    }
   2876  }
   2877 
   2878  UndefineCaretBidiLevel();
   2879 
   2880  AutoPlaceholderBatch treatAsOneTransaction(
   2881      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   2882  IgnoredErrorResult ignoredError;
   2883  AutoEditSubActionNotifier startToHandleEditSubAction(
   2884      *this, EditSubAction::eInsertQuotation, nsIEditor::eNext, ignoredError);
   2885  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   2886    return ignoredError.StealNSResult();
   2887  }
   2888  NS_WARNING_ASSERTION(
   2889      !ignoredError.Failed(),
   2890      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   2891 
   2892  rv = EnsureNoPaddingBRElementForEmptyEditor();
   2893  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   2894    return NS_ERROR_EDITOR_DESTROYED;
   2895  }
   2896  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2897                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
   2898                       "failed, but ignored");
   2899 
   2900  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
   2901    nsresult rv = EnsureCaretNotAfterInvisibleBRElement(*editingHost);
   2902    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   2903      return NS_ERROR_EDITOR_DESTROYED;
   2904    }
   2905    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2906                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
   2907                         "failed, but ignored");
   2908    if (NS_SUCCEEDED(rv)) {
   2909      nsresult rv = PrepareInlineStylesForCaret();
   2910      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   2911        return NS_ERROR_EDITOR_DESTROYED;
   2912      }
   2913      NS_WARNING_ASSERTION(
   2914          NS_SUCCEEDED(rv),
   2915          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
   2916    }
   2917  }
   2918 
   2919  // Remove Selection and create `<blockquote type="cite">` now.
   2920  // XXX Why don't we insert the `<blockquote>` into the DOM tree after
   2921  //     pasting the content in clipboard into it?
   2922  Result<RefPtr<Element>, nsresult> blockquoteElementOrError =
   2923      DeleteSelectionAndCreateElement(
   2924          *nsGkAtoms::blockquote,
   2925          // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
   2926          [](HTMLEditor&, Element& aBlockquoteElement, const EditorDOMPoint&)
   2927              MOZ_CAN_RUN_SCRIPT_BOUNDARY {
   2928                DebugOnly<nsresult> rvIgnored = aBlockquoteElement.SetAttr(
   2929                    kNameSpaceID_None, nsGkAtoms::type, u"cite"_ns,
   2930                    aBlockquoteElement.IsInComposedDoc());
   2931                NS_WARNING_ASSERTION(
   2932                    NS_SUCCEEDED(rvIgnored),
   2933                    nsPrintfCString(
   2934                        "Element::SetAttr(nsGkAtoms::type, \"cite\", %s) "
   2935                        "failed, but ignored",
   2936                        aBlockquoteElement.IsInComposedDoc() ? "true" : "false")
   2937                        .get());
   2938                return NS_OK;
   2939              });
   2940  if (MOZ_UNLIKELY(blockquoteElementOrError.isErr()) ||
   2941      NS_WARN_IF(Destroyed())) {
   2942    NS_WARNING(
   2943        "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::blockquote) "
   2944        "failed");
   2945    return Destroyed() ? NS_ERROR_EDITOR_DESTROYED
   2946                       : blockquoteElementOrError.unwrapErr();
   2947  }
   2948  MOZ_ASSERT(blockquoteElementOrError.inspect());
   2949 
   2950  // Collapse Selection in the new `<blockquote>` element.
   2951  rv = CollapseSelectionToStartOf(
   2952      MOZ_KnownLive(*blockquoteElementOrError.inspect()));
   2953  if (NS_FAILED(rv)) {
   2954    NS_WARNING("EditorBase::CollapseSelectionToStartOf() failed");
   2955    return rv;
   2956  }
   2957 
   2958  rv = PasteInternal(aClipboardType, aDataTransfer, *editingHost);
   2959  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::PasteInternal() failed");
   2960  return rv;
   2961 }
   2962 
   2963 nsresult HTMLEditor::PasteAsPlaintextQuotation(
   2964    nsIClipboard::ClipboardType aSelectionType, DataTransfer* aDataTransfer,
   2965    const Element& aEditingHost) {
   2966  nsresult rv;
   2967  // Create generic Transferable for getting the data
   2968  nsCOMPtr<nsITransferable> transferable =
   2969      do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
   2970  if (NS_FAILED(rv)) {
   2971    NS_WARNING("do_CreateInstance() failed to create nsITransferable instance");
   2972    return rv;
   2973  }
   2974  if (!transferable) {
   2975    NS_WARNING("do_CreateInstance() returned nullptr");
   2976    return NS_ERROR_FAILURE;
   2977  }
   2978 
   2979  RefPtr<Document> destdoc = GetDocument();
   2980  auto* windowContext = GetDocument()->GetWindowContext();
   2981  if (!windowContext) {
   2982    NS_WARNING("Editor didn't have document window context");
   2983    return NS_ERROR_FAILURE;
   2984  }
   2985 
   2986  nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
   2987  DebugOnly<nsresult> rvIgnored = transferable->Init(loadContext);
   2988  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   2989                       "nsITransferable::Init() failed, but ignored");
   2990 
   2991  // We only handle plaintext pastes here
   2992  rvIgnored = transferable->AddDataFlavor(kTextMime);
   2993  NS_WARNING_ASSERTION(
   2994      NS_SUCCEEDED(rvIgnored),
   2995      "nsITransferable::AddDataFlavor(kTextMime) failed, but ignored");
   2996 
   2997  // Get the Data from the clipboard
   2998  GetDataFromDataTransferOrClipboard(aDataTransfer, transferable,
   2999                                     aSelectionType);
   3000 
   3001  // Now we ask the transferable for the data
   3002  // it still owns the data, we just have a pointer to it.
   3003  // If it can't support a "text" output of the data the call will fail
   3004  nsCOMPtr<nsISupports> genericDataObj;
   3005  nsAutoCString flavor;
   3006  rv = transferable->GetAnyTransferData(flavor, getter_AddRefs(genericDataObj));
   3007  if (NS_FAILED(rv)) {
   3008    NS_WARNING("nsITransferable::GetAnyTransferData() failed");
   3009    return rv;
   3010  }
   3011 
   3012  if (!flavor.EqualsLiteral(kTextMime)) {
   3013    return NS_OK;
   3014  }
   3015 
   3016  nsAutoString stuffToPaste;
   3017  if (!GetString(genericDataObj, stuffToPaste)) {
   3018    return NS_OK;
   3019  }
   3020 
   3021  AutoPlaceholderBatch treatAsOneTransaction(
   3022      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   3023  rv = InsertAsPlaintextQuotation(stuffToPaste, AddCites::Yes, aEditingHost);
   3024  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3025                       "HTMLEditor::InsertAsPlaintextQuotation() failed");
   3026  return rv;
   3027 }
   3028 
   3029 nsresult HTMLEditor::InsertWithQuotationsAsSubAction(
   3030    const nsAString& aQuotedText) {
   3031  MOZ_ASSERT(IsEditActionDataAvailable());
   3032 
   3033  const RefPtr<Element> editingHost =
   3034      ComputeEditingHost(LimitInBodyElement::No);
   3035  if (NS_WARN_IF(!editingHost)) {
   3036    return NS_ERROR_FAILURE;
   3037  }
   3038 
   3039  {
   3040    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
   3041    if (MOZ_UNLIKELY(result.isErr())) {
   3042      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
   3043      return result.unwrapErr();
   3044    }
   3045    if (result.inspect().Canceled()) {
   3046      return NS_OK;
   3047    }
   3048  }
   3049 
   3050  UndefineCaretBidiLevel();
   3051 
   3052  // Let the citer quote it for us:
   3053  nsString quotedStuff;
   3054  InternetCiter::GetCiteString(aQuotedText, quotedStuff);
   3055 
   3056  // It's best to put a blank line after the quoted text so that mails
   3057  // written without thinking won't be so ugly.
   3058  if (!aQuotedText.IsEmpty() &&
   3059      (aQuotedText.Last() != HTMLEditUtils::kNewLine)) {
   3060    quotedStuff.Append(HTMLEditUtils::kNewLine);
   3061  }
   3062 
   3063  IgnoredErrorResult ignoredError;
   3064  AutoEditSubActionNotifier startToHandleEditSubAction(
   3065      *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError);
   3066  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   3067    return ignoredError.StealNSResult();
   3068  }
   3069  NS_WARNING_ASSERTION(
   3070      !ignoredError.Failed(),
   3071      "OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   3072 
   3073  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
   3074  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   3075    return NS_ERROR_EDITOR_DESTROYED;
   3076  }
   3077  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3078                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
   3079                       "failed, but ignored");
   3080 
   3081  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
   3082    nsresult rv = EnsureCaretNotAfterInvisibleBRElement(*editingHost);
   3083    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   3084      return NS_ERROR_EDITOR_DESTROYED;
   3085    }
   3086    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3087                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
   3088                         "failed, but ignored");
   3089    if (NS_SUCCEEDED(rv)) {
   3090      nsresult rv = PrepareInlineStylesForCaret();
   3091      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   3092        return NS_ERROR_EDITOR_DESTROYED;
   3093      }
   3094      NS_WARNING_ASSERTION(
   3095          NS_SUCCEEDED(rv),
   3096          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
   3097    }
   3098  }
   3099 
   3100  rv = InsertTextAsSubAction(quotedStuff, InsertTextFor::NormalText);
   3101  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3102                       "EditorBase::InsertTextAsSubAction() failed");
   3103  return rv;
   3104 }
   3105 
   3106 NS_IMETHODIMP HTMLEditor::InsertTextWithQuotations(
   3107    const nsAString& aStringToInsert) {
   3108  AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText);
   3109  MOZ_ASSERT(!aStringToInsert.IsVoid());
   3110  editActionData.SetData(aStringToInsert);
   3111  nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
   3112  if (NS_FAILED(rv)) {
   3113    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   3114                         "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
   3115    return EditorBase::ToGenericNSResult(rv);
   3116  }
   3117  if (aStringToInsert.IsEmpty()) {
   3118    return NS_OK;
   3119  }
   3120 
   3121  const RefPtr<Element> editingHost =
   3122      ComputeEditingHost(LimitInBodyElement::No);
   3123  if (NS_WARN_IF(!editingHost)) {
   3124    return NS_ERROR_FAILURE;
   3125  }
   3126 
   3127  // The whole operation should be undoable in one transaction:
   3128  // XXX Why isn't enough to use only AutoPlaceholderBatch here?
   3129  AutoTransactionBatch bundleAllTransactions(*this, __FUNCTION__);
   3130  AutoPlaceholderBatch treatAsOneTransaction(
   3131      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   3132 
   3133  rv = InsertTextWithQuotationsInternal(aStringToInsert, *editingHost);
   3134  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3135                       "HTMLEditor::InsertTextWithQuotationsInternal() failed");
   3136  return EditorBase::ToGenericNSResult(rv);
   3137 }
   3138 
   3139 nsresult HTMLEditor::InsertTextWithQuotationsInternal(
   3140    const nsAString& aStringToInsert, const Element& aEditingHost) {
   3141  MOZ_ASSERT(!aStringToInsert.IsEmpty());
   3142  // We're going to loop over the string, collecting up a "hunk"
   3143  // that's all the same type (quoted or not),
   3144  // Whenever the quotedness changes (or we reach the string's end)
   3145  // we will insert the hunk all at once, quoted or non.
   3146  static const char16_t cite('>');
   3147  bool curHunkIsQuoted = (aStringToInsert.First() == cite);
   3148 
   3149  nsAString::const_iterator hunkStart, strEnd;
   3150  aStringToInsert.BeginReading(hunkStart);
   3151  aStringToInsert.EndReading(strEnd);
   3152 
   3153  // In the loop below, we only look for DOM newlines (\n),
   3154  // because we don't have a FindChars method that can look
   3155  // for both \r and \n.  \r is illegal in the dom anyway,
   3156  // but in debug builds, let's take the time to verify that
   3157  // there aren't any there:
   3158 #ifdef DEBUG
   3159  nsAString::const_iterator dbgStart(hunkStart);
   3160  if (FindCharInReadable(HTMLEditUtils::kCarriageReturn, dbgStart, strEnd)) {
   3161    NS_ASSERTION(
   3162        false,
   3163        "Return characters in DOM! InsertTextWithQuotations may be wrong");
   3164  }
   3165 #endif /* DEBUG */
   3166 
   3167  // Loop over lines:
   3168  nsresult rv = NS_OK;
   3169  nsAString::const_iterator lineStart(hunkStart);
   3170  // We will break from inside when we run out of newlines.
   3171  for (;;) {
   3172    // Search for the end of this line (dom newlines, see above):
   3173    bool found = FindCharInReadable(HTMLEditUtils::kNewLine, lineStart, strEnd);
   3174    bool quoted = false;
   3175    if (found) {
   3176      // if there's another newline, lineStart now points there.
   3177      // Loop over any consecutive newline chars:
   3178      nsAString::const_iterator firstNewline(lineStart);
   3179      while (*lineStart == HTMLEditUtils::kNewLine) {
   3180        ++lineStart;
   3181      }
   3182      quoted = (*lineStart == cite);
   3183      if (quoted == curHunkIsQuoted) {
   3184        continue;
   3185      }
   3186      // else we're changing state, so we need to insert
   3187      // from curHunk to lineStart then loop around.
   3188 
   3189      // But if the current hunk is quoted, then we want to make sure
   3190      // that any extra newlines on the end do not get included in
   3191      // the quoted section: blank lines flaking a quoted section
   3192      // should be considered unquoted, so that if the user clicks
   3193      // there and starts typing, the new text will be outside of
   3194      // the quoted block.
   3195      if (curHunkIsQuoted) {
   3196        lineStart = firstNewline;
   3197 
   3198        // 'firstNewline' points to the first '\n'. We want to
   3199        // ensure that this first newline goes into the hunk
   3200        // since quoted hunks can be displayed as blocks
   3201        // (and the newline should become invisible in this case).
   3202        // So the next line needs to start at the next character.
   3203        lineStart++;
   3204      }
   3205    }
   3206 
   3207    // If no newline found, lineStart is now strEnd and we can finish up,
   3208    // inserting from curHunk to lineStart then returning.
   3209    const nsAString& curHunk = Substring(hunkStart, lineStart);
   3210    if (curHunkIsQuoted) {
   3211      rv = InsertAsPlaintextQuotation(curHunk, AddCites::No, aEditingHost);
   3212      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   3213        return NS_ERROR_EDITOR_DESTROYED;
   3214      }
   3215      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3216                           "HTMLEditor::InsertAsPlaintextQuotation() failed, "
   3217                           "but might be ignored");
   3218    } else {
   3219      rv = InsertTextAsSubAction(curHunk, InsertTextFor::NormalText);
   3220      NS_WARNING_ASSERTION(
   3221          NS_SUCCEEDED(rv),
   3222          "EditorBase::InsertTextAsSubAction() failed, but might be ignored");
   3223    }
   3224    if (!found) {
   3225      break;
   3226    }
   3227    curHunkIsQuoted = quoted;
   3228    hunkStart = lineStart;
   3229  }
   3230 
   3231  // XXX This returns the last result of InsertAsPlaintextQuotation() or
   3232  //     InsertTextAsSubAction() in the loop.  This must be a bug.
   3233  return rv;
   3234 }
   3235 
   3236 nsresult HTMLEditor::InsertAsQuotation(const nsAString& aQuotedText,
   3237                                       nsINode** aNodeInserted) {
   3238  const RefPtr<Element> editingHost =
   3239      ComputeEditingHost(LimitInBodyElement::No);
   3240  if (NS_WARN_IF(!editingHost)) {
   3241    return NS_ERROR_FAILURE;
   3242  }
   3243 
   3244  if (IsPlaintextMailComposer() ||
   3245      editingHost->IsContentEditablePlainTextOnly()) {
   3246    AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText);
   3247    MOZ_ASSERT(!aQuotedText.IsVoid());
   3248    editActionData.SetData(aQuotedText);
   3249    nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
   3250    if (NS_FAILED(rv)) {
   3251      NS_WARNING_ASSERTION(
   3252          rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   3253          "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
   3254      return EditorBase::ToGenericNSResult(rv);
   3255    }
   3256    AutoPlaceholderBatch treatAsOneTransaction(
   3257        *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   3258    rv = InsertAsPlaintextQuotation(aQuotedText, AddCites::Yes, *editingHost,
   3259                                    aNodeInserted);
   3260    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3261                         "HTMLEditor::InsertAsPlaintextQuotation() failed");
   3262    return EditorBase::ToGenericNSResult(rv);
   3263  }
   3264 
   3265  AutoEditActionDataSetter editActionData(*this,
   3266                                          EditAction::eInsertBlockquoteElement);
   3267  nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
   3268  if (NS_FAILED(rv)) {
   3269    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   3270                         "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
   3271    return EditorBase::ToGenericNSResult(rv);
   3272  }
   3273 
   3274  AutoPlaceholderBatch treatAsOneTransaction(
   3275      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   3276  nsAutoString citation;
   3277  rv = InsertAsCitedQuotationInternal(aQuotedText, citation, false,
   3278                                      *editingHost, aNodeInserted);
   3279  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3280                       "HTMLEditor::InsertAsCitedQuotationInternal() failed");
   3281  return EditorBase::ToGenericNSResult(rv);
   3282 }
   3283 
   3284 // Insert plaintext as a quotation, with cite marks (e.g. "> ").
   3285 // This differs from its corresponding method in TextEditor
   3286 // in that here, quoted material is enclosed in a <pre> tag
   3287 // in order to preserve the original line wrapping.
   3288 nsresult HTMLEditor::InsertAsPlaintextQuotation(const nsAString& aQuotedText,
   3289                                                AddCites aAddCites,
   3290                                                const Element& aEditingHost,
   3291                                                nsINode** aNodeInserted) {
   3292  MOZ_ASSERT(IsEditActionDataAvailable());
   3293 
   3294  if (aNodeInserted) {
   3295    *aNodeInserted = nullptr;
   3296  }
   3297 
   3298  {
   3299    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
   3300    if (MOZ_UNLIKELY(result.isErr())) {
   3301      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
   3302      return result.unwrapErr();
   3303    }
   3304    if (result.inspect().Canceled()) {
   3305      return NS_OK;
   3306    }
   3307  }
   3308 
   3309  UndefineCaretBidiLevel();
   3310 
   3311  IgnoredErrorResult ignoredError;
   3312  AutoEditSubActionNotifier startToHandleEditSubAction(
   3313      *this, EditSubAction::eInsertQuotation, nsIEditor::eNext, ignoredError);
   3314  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   3315    return ignoredError.StealNSResult();
   3316  }
   3317  NS_WARNING_ASSERTION(
   3318      !ignoredError.Failed(),
   3319      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   3320 
   3321  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
   3322  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   3323    return NS_ERROR_EDITOR_DESTROYED;
   3324  }
   3325  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3326                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
   3327                       "failed, but ignored");
   3328 
   3329  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
   3330    nsresult rv = EnsureCaretNotAfterInvisibleBRElement(aEditingHost);
   3331    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   3332      return NS_ERROR_EDITOR_DESTROYED;
   3333    }
   3334    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3335                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
   3336                         "failed, but ignored");
   3337    if (NS_SUCCEEDED(rv)) {
   3338      nsresult rv = PrepareInlineStylesForCaret();
   3339      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   3340        return NS_ERROR_EDITOR_DESTROYED;
   3341      }
   3342      NS_WARNING_ASSERTION(
   3343          NS_SUCCEEDED(rv),
   3344          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
   3345    }
   3346  }
   3347 
   3348  RefPtr<Element> containerSpanElement;
   3349  if (!aEditingHost.IsContentEditablePlainTextOnly()) {
   3350    // Wrap the inserted quote in a <span> so we can distinguish it. If we're
   3351    // inserting into the <body>, we use a <span> which is displayed as a block
   3352    // and sized to the screen using 98 viewport width units.
   3353    // We could use 100vw, but 98vw avoids a horizontal scroll bar where
   3354    // possible. All this is done to wrap overlong lines to the screen and not
   3355    // to the container element, the width-restricted body.
   3356    // XXX I think that we don't need to do this in the web.  This should be
   3357    // done only for Thunderbird.
   3358    Result<RefPtr<Element>, nsresult> spanElementOrError =
   3359        DeleteSelectionAndCreateElement(
   3360            *nsGkAtoms::span, [](HTMLEditor&, Element& aSpanElement,
   3361                                 const EditorDOMPoint& aPointToInsert) {
   3362              // Add an attribute on the pre node so we'll know it's a
   3363              // quotation.
   3364              DebugOnly<nsresult> rvIgnored = aSpanElement.SetAttr(
   3365                  kNameSpaceID_None, nsGkAtoms::mozquote, u"true"_ns,
   3366                  aSpanElement.IsInComposedDoc());
   3367              NS_WARNING_ASSERTION(
   3368                  NS_SUCCEEDED(rvIgnored),
   3369                  nsPrintfCString(
   3370                      "Element::SetAttr(nsGkAtoms::mozquote, \"true\", %s) "
   3371                      "failed",
   3372                      aSpanElement.IsInComposedDoc() ? "true" : "false")
   3373                      .get());
   3374              // Allow wrapping on spans so long lines get wrapped to the
   3375              // screen.
   3376              if (aPointToInsert.IsContainerHTMLElement(nsGkAtoms::body)) {
   3377                DebugOnly<nsresult> rvIgnored = aSpanElement.SetAttr(
   3378                    kNameSpaceID_None, nsGkAtoms::style,
   3379                    nsLiteralString(u"white-space: pre-wrap; display: block; "
   3380                                    u"width: 98vw;"),
   3381                    false);
   3382                NS_WARNING_ASSERTION(
   3383                    NS_SUCCEEDED(rvIgnored),
   3384                    "Element::SetAttr(nsGkAtoms::style, \"pre-wrap, block\", "
   3385                    "false) failed, but ignored");
   3386              } else {
   3387                DebugOnly<nsresult> rvIgnored =
   3388                    aSpanElement.SetAttr(kNameSpaceID_None, nsGkAtoms::style,
   3389                                         u"white-space: pre-wrap;"_ns, false);
   3390                NS_WARNING_ASSERTION(
   3391                    NS_SUCCEEDED(rvIgnored),
   3392                    "Element::SetAttr(nsGkAtoms::style, "
   3393                    "\"pre-wrap\", false) failed, but ignored");
   3394              }
   3395              return NS_OK;
   3396            });
   3397    if (MOZ_UNLIKELY(spanElementOrError.isErr())) {
   3398      NS_WARNING(
   3399          "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::span) "
   3400          "failed");
   3401      return NS_OK;
   3402    }
   3403    // If this succeeded, then set selection inside the pre
   3404    // so the inserted text will end up there.
   3405    // If it failed, we don't care what the return value was,
   3406    // but we'll fall through and try to insert the text anyway.
   3407    MOZ_ASSERT(spanElementOrError.inspect());
   3408    nsresult rv = CollapseSelectionToStartOf(
   3409        MOZ_KnownLive(*spanElementOrError.inspect()));
   3410    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
   3411      NS_WARNING(
   3412          "EditorBase::CollapseSelectionToStartOf() caused destroying the "
   3413          "editor");
   3414      return NS_ERROR_EDITOR_DESTROYED;
   3415    }
   3416    NS_WARNING_ASSERTION(
   3417        NS_SUCCEEDED(rv),
   3418        "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
   3419    containerSpanElement = spanElementOrError.unwrap();
   3420  }
   3421 
   3422  // TODO: We should insert text at specific point rather than at selection.
   3423  //       Then, we can do this before inserting the <span> element.
   3424  if (aAddCites == AddCites::Yes) {
   3425    nsresult rv = InsertWithQuotationsAsSubAction(aQuotedText);
   3426    if (NS_FAILED(rv)) {
   3427      NS_WARNING("HTMLEditor::InsertWithQuotationsAsSubAction() failed");
   3428      return rv;
   3429    }
   3430  } else {
   3431    nsresult rv = InsertTextAsSubAction(aQuotedText, InsertTextFor::NormalText);
   3432    if (NS_FAILED(rv)) {
   3433      NS_WARNING("EditorBase::InsertTextAsSubAction() failed");
   3434      return rv;
   3435    }
   3436  }
   3437 
   3438  // Set the selection to after the <span> if and only if we wrap the text into
   3439  // it.
   3440  if (containerSpanElement) {
   3441    EditorRawDOMPoint afterNewSpanElement(
   3442        EditorRawDOMPoint::After(*containerSpanElement));
   3443    NS_WARNING_ASSERTION(
   3444        afterNewSpanElement.IsSet(),
   3445        "Failed to set after the new <span> element, but ignored");
   3446    if (afterNewSpanElement.IsSet()) {
   3447      nsresult rv = CollapseSelectionTo(afterNewSpanElement);
   3448      if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
   3449        NS_WARNING(
   3450            "EditorBase::CollapseSelectionTo() caused destroying the editor");
   3451        return NS_ERROR_EDITOR_DESTROYED;
   3452      }
   3453      NS_WARNING_ASSERTION(
   3454          NS_SUCCEEDED(rv),
   3455          "EditorBase::CollapseSelectionTo() failed, but ignored");
   3456    }
   3457 
   3458    // Note that if !aAddCites, aNodeInserted isn't set.
   3459    // That's okay because the routines that use aAddCites
   3460    // don't need to know the inserted node.
   3461    if (aNodeInserted) {
   3462      containerSpanElement.forget(aNodeInserted);
   3463    }
   3464  }
   3465 
   3466  return NS_OK;
   3467 }
   3468 
   3469 NS_IMETHODIMP HTMLEditor::Rewrap(bool aRespectNewlines) {
   3470  AutoEditActionDataSetter editActionData(*this, EditAction::eRewrap);
   3471  nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
   3472  if (NS_FAILED(rv)) {
   3473    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   3474                         "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
   3475    return EditorBase::ToGenericNSResult(rv);
   3476  }
   3477 
   3478  const RefPtr<Element> editingHost =
   3479      ComputeEditingHost(LimitInBodyElement::No);
   3480  if (NS_WARN_IF(!editingHost)) {
   3481    return NS_ERROR_FAILURE;
   3482  }
   3483 
   3484  // Rewrap makes no sense if there's no wrap column; default to 72.
   3485  int32_t wrapWidth = WrapWidth();
   3486  if (wrapWidth <= 0) {
   3487    wrapWidth = 72;
   3488  }
   3489 
   3490  nsAutoString current;
   3491  const bool isCollapsed = SelectionRef().IsCollapsed();
   3492  uint32_t flags = nsIDocumentEncoder::OutputFormatted |
   3493                   nsIDocumentEncoder::OutputLFLineBreak;
   3494  if (!isCollapsed) {
   3495    flags |= nsIDocumentEncoder::OutputSelectionOnly;
   3496  }
   3497  rv = ComputeValueInternal(u"text/plain"_ns, flags, current);
   3498  if (NS_FAILED(rv)) {
   3499    NS_WARNING("EditorBase::ComputeValueInternal(text/plain) failed");
   3500    return EditorBase::ToGenericNSResult(rv);
   3501  }
   3502 
   3503  if (current.IsEmpty()) {
   3504    return NS_OK;
   3505  }
   3506 
   3507  nsString wrapped;
   3508  uint32_t firstLineOffset = 0;  // XXX need to reset this if there is a
   3509                                 //     selection
   3510  InternetCiter::Rewrap(current, wrapWidth, firstLineOffset, aRespectNewlines,
   3511                        wrapped);
   3512 
   3513  if (wrapped.IsEmpty()) {
   3514    return NS_OK;
   3515  }
   3516 
   3517  if (isCollapsed) {
   3518    DebugOnly<nsresult> rvIgnored = SelectAllInternal();
   3519    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   3520                         "HTMLEditor::SelectAllInternal() failed");
   3521  }
   3522 
   3523  // The whole operation in InsertTextWithQuotationsInternal() should be
   3524  // undoable in one transaction.
   3525  // XXX Why isn't enough to use only AutoPlaceholderBatch here?
   3526  AutoTransactionBatch bundleAllTransactions(*this, __FUNCTION__);
   3527  AutoPlaceholderBatch treatAsOneTransaction(
   3528      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   3529  rv = InsertTextWithQuotationsInternal(wrapped, *editingHost);
   3530  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3531                       "HTMLEditor::InsertTextWithQuotationsInternal() failed");
   3532  return EditorBase::ToGenericNSResult(rv);
   3533 }
   3534 
   3535 NS_IMETHODIMP HTMLEditor::InsertAsCitedQuotation(const nsAString& aQuotedText,
   3536                                                 const nsAString& aCitation,
   3537                                                 bool aInsertHTML,
   3538                                                 nsINode** aNodeInserted) {
   3539  const RefPtr<Element> editingHost =
   3540      ComputeEditingHost(LimitInBodyElement::No);
   3541  if (NS_WARN_IF(!editingHost)) {
   3542    return NS_ERROR_FAILURE;
   3543  }
   3544 
   3545  // Don't let anyone insert HTML when we're in plaintext mode.
   3546  if (IsPlaintextMailComposer() ||
   3547      editingHost->IsContentEditablePlainTextOnly()) {
   3548    NS_ASSERTION(
   3549        !aInsertHTML,
   3550        "InsertAsCitedQuotation: trying to insert html into plaintext editor");
   3551 
   3552    AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText);
   3553    MOZ_ASSERT(!aQuotedText.IsVoid());
   3554    editActionData.SetData(aQuotedText);
   3555    nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
   3556    if (NS_FAILED(rv)) {
   3557      NS_WARNING_ASSERTION(
   3558          rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   3559          "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
   3560      return EditorBase::ToGenericNSResult(rv);
   3561    }
   3562 
   3563    AutoPlaceholderBatch treatAsOneTransaction(
   3564        *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   3565    rv = InsertAsPlaintextQuotation(aQuotedText, AddCites::Yes, *editingHost,
   3566                                    aNodeInserted);
   3567    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3568                         "HTMLEditor::InsertAsPlaintextQuotation() failed");
   3569    return EditorBase::ToGenericNSResult(rv);
   3570  }
   3571 
   3572  AutoEditActionDataSetter editActionData(*this,
   3573                                          EditAction::eInsertBlockquoteElement);
   3574  nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
   3575  if (NS_FAILED(rv)) {
   3576    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
   3577                         "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
   3578    return EditorBase::ToGenericNSResult(rv);
   3579  }
   3580 
   3581  AutoPlaceholderBatch treatAsOneTransaction(
   3582      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   3583  rv = InsertAsCitedQuotationInternal(aQuotedText, aCitation, aInsertHTML,
   3584                                      *editingHost, aNodeInserted);
   3585  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3586                       "HTMLEditor::InsertAsCitedQuotationInternal() failed");
   3587  return EditorBase::ToGenericNSResult(rv);
   3588 }
   3589 
   3590 nsresult HTMLEditor::InsertAsCitedQuotationInternal(
   3591    const nsAString& aQuotedText, const nsAString& aCitation, bool aInsertHTML,
   3592    const Element& aEditingHost, nsINode** aNodeInserted) {
   3593  MOZ_ASSERT(IsEditActionDataAvailable());
   3594  MOZ_ASSERT(!IsPlaintextMailComposer());
   3595 
   3596  {
   3597    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
   3598    if (MOZ_UNLIKELY(result.isErr())) {
   3599      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
   3600      return result.unwrapErr();
   3601    }
   3602    if (result.inspect().Canceled()) {
   3603      return NS_OK;
   3604    }
   3605  }
   3606 
   3607  UndefineCaretBidiLevel();
   3608 
   3609  IgnoredErrorResult ignoredError;
   3610  AutoEditSubActionNotifier startToHandleEditSubAction(
   3611      *this, EditSubAction::eInsertQuotation, nsIEditor::eNext, ignoredError);
   3612  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   3613    return ignoredError.StealNSResult();
   3614  }
   3615  NS_WARNING_ASSERTION(
   3616      !ignoredError.Failed(),
   3617      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   3618 
   3619  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
   3620  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   3621    return NS_ERROR_EDITOR_DESTROYED;
   3622  }
   3623  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3624                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
   3625                       "failed, but ignored");
   3626 
   3627  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
   3628    nsresult rv = EnsureCaretNotAfterInvisibleBRElement(aEditingHost);
   3629    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   3630      return NS_ERROR_EDITOR_DESTROYED;
   3631    }
   3632    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3633                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
   3634                         "failed, but ignored");
   3635    if (NS_SUCCEEDED(rv)) {
   3636      nsresult rv = PrepareInlineStylesForCaret();
   3637      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   3638        return NS_ERROR_EDITOR_DESTROYED;
   3639      }
   3640      NS_WARNING_ASSERTION(
   3641          NS_SUCCEEDED(rv),
   3642          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
   3643    }
   3644  }
   3645 
   3646  Result<RefPtr<Element>, nsresult> blockquoteElementOrError =
   3647      DeleteSelectionAndCreateElement(
   3648          *nsGkAtoms::blockquote,
   3649          // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
   3650          [&aCitation](HTMLEditor&, Element& aBlockquoteElement,
   3651                       const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
   3652            // Try to set type=cite.  Ignore it if this fails.
   3653            DebugOnly<nsresult> rvIgnored = aBlockquoteElement.SetAttr(
   3654                kNameSpaceID_None, nsGkAtoms::type, u"cite"_ns,
   3655                aBlockquoteElement.IsInComposedDoc());
   3656            NS_WARNING_ASSERTION(
   3657                NS_SUCCEEDED(rvIgnored),
   3658                nsPrintfCString(
   3659                    "Element::SetAttr(nsGkAtoms::type, \"cite\", %s) failed, "
   3660                    "but ignored",
   3661                    aBlockquoteElement.IsInComposedDoc() ? "true" : "false")
   3662                    .get());
   3663            if (!aCitation.IsEmpty()) {
   3664              DebugOnly<nsresult> rvIgnored = aBlockquoteElement.SetAttr(
   3665                  kNameSpaceID_None, nsGkAtoms::cite, aCitation,
   3666                  aBlockquoteElement.IsInComposedDoc());
   3667              NS_WARNING_ASSERTION(
   3668                  NS_SUCCEEDED(rvIgnored),
   3669                  nsPrintfCString(
   3670                      "Element::SetAttr(nsGkAtoms::cite, \"...\", %s) failed, "
   3671                      "but ignored",
   3672                      aBlockquoteElement.IsInComposedDoc() ? "true" : "false")
   3673                      .get());
   3674            }
   3675            return NS_OK;
   3676          });
   3677  if (MOZ_UNLIKELY(blockquoteElementOrError.isErr() ||
   3678                   NS_WARN_IF(Destroyed()))) {
   3679    NS_WARNING(
   3680        "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::blockquote) "
   3681        "failed");
   3682    return Destroyed() ? NS_ERROR_EDITOR_DESTROYED
   3683                       : blockquoteElementOrError.unwrapErr();
   3684  }
   3685  MOZ_ASSERT(blockquoteElementOrError.inspect());
   3686 
   3687  // Set the selection inside the blockquote so aQuotedText will go there:
   3688  rv = CollapseSelectionTo(
   3689      EditorRawDOMPoint(blockquoteElementOrError.inspect(), 0u));
   3690  if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
   3691    NS_WARNING(
   3692        "EditorBase::CollapseSelectionTo() caused destroying the editor");
   3693    return NS_ERROR_EDITOR_DESTROYED;
   3694  }
   3695  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3696                       "EditorBase::CollapseSelectionTo() failed, but ignored");
   3697 
   3698  // TODO: We should insert text at specific point rather than at selection.
   3699  //       Then, we can do this before inserting the <blockquote> element.
   3700  if (aInsertHTML) {
   3701    rv = LoadHTML(aQuotedText);
   3702    if (NS_WARN_IF(Destroyed())) {
   3703      return NS_ERROR_EDITOR_DESTROYED;
   3704    }
   3705    if (NS_FAILED(rv)) {
   3706      NS_WARNING("HTMLEditor::LoadHTML() failed");
   3707      return rv;
   3708    }
   3709  } else {
   3710    rv = InsertTextAsSubAction(
   3711        aQuotedText, InsertTextFor::NormalText);  // XXX ignore charset
   3712    if (NS_WARN_IF(Destroyed())) {
   3713      return NS_ERROR_EDITOR_DESTROYED;
   3714    }
   3715    if (NS_FAILED(rv)) {
   3716      NS_WARNING("HTMLEditor::LoadHTML() failed");
   3717      return rv;
   3718    }
   3719  }
   3720 
   3721  // Set the selection to just after the inserted node:
   3722  EditorRawDOMPoint afterNewBlockquoteElement(
   3723      EditorRawDOMPoint::After(blockquoteElementOrError.inspect()));
   3724  NS_WARNING_ASSERTION(
   3725      afterNewBlockquoteElement.IsSet(),
   3726      "Failed to set after new <blockquote> element, but ignored");
   3727  if (afterNewBlockquoteElement.IsSet()) {
   3728    nsresult rv = CollapseSelectionTo(afterNewBlockquoteElement);
   3729    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
   3730      NS_WARNING(
   3731          "EditorBase::CollapseSelectionTo() caused destroying the editor");
   3732      return NS_ERROR_EDITOR_DESTROYED;
   3733    }
   3734    NS_WARNING_ASSERTION(
   3735        NS_SUCCEEDED(rv),
   3736        "EditorBase::CollapseSelectionTo() failed, but ignored");
   3737  }
   3738 
   3739  if (aNodeInserted) {
   3740    blockquoteElementOrError.unwrap().forget(aNodeInserted);
   3741  }
   3742 
   3743  return NS_OK;
   3744 }
   3745 
   3746 void HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
   3747    RemoveHeadChildAndStealBodyChildsChildren(nsINode& aNode) {
   3748  nsCOMPtr<nsIContent> body, head;
   3749  // find the body and head nodes if any.
   3750  // look only at immediate children of aNode.
   3751  for (nsCOMPtr<nsIContent> child = aNode.GetFirstChild(); child;
   3752       child = child->GetNextSibling()) {
   3753    if (child->IsHTMLElement(nsGkAtoms::body)) {
   3754      body = child;
   3755    } else if (child->IsHTMLElement(nsGkAtoms::head)) {
   3756      head = child;
   3757    }
   3758  }
   3759  if (head) {
   3760    ErrorResult ignored;
   3761    aNode.RemoveChild(*head, ignored);
   3762  }
   3763  if (body) {
   3764    nsCOMPtr<nsIContent> child = body->GetFirstChild();
   3765    while (child) {
   3766      ErrorResult ignored;
   3767      aNode.InsertBefore(*child, body, ignored);
   3768      child = body->GetFirstChild();
   3769    }
   3770 
   3771    ErrorResult ignored;
   3772    aNode.RemoveChild(*body, ignored);
   3773  }
   3774 }
   3775 
   3776 // static
   3777 void HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
   3778    RemoveIncompleteDescendantsFromInsertingFragment(nsINode& aNode) {
   3779  nsIContent* child = aNode.GetFirstChild();
   3780  while (child) {
   3781    bool isEmptyNodeShouldNotInserted = false;
   3782    if (HTMLEditUtils::IsListElement(*child)) {
   3783      // Current limitation of HTMLEditor:
   3784      //   Cannot put caret in a list element which does not have list item
   3785      //   element even as a descendant.  I.e., HTMLEditor does not support
   3786      //   editing in such empty list element, and does not support to delete
   3787      //   it from outside.  Therefore, HTMLWithContextInserter should not
   3788      //   insert empty list element.
   3789      isEmptyNodeShouldNotInserted = HTMLEditUtils::IsEmptyNode(
   3790          *child,
   3791          {
   3792              // Although we don't check relation between list item element
   3793              // and parent list element, but it should not be a problem in the
   3794              // wild because appearing such invalid list element is an edge
   3795              // case and anyway HTMLEditor supports editing in them.
   3796              EmptyCheckOption::TreatListItemAsVisible,
   3797              // A non-editable list item element may make the list element
   3798              // visible.  Although HTMLEditor does not support to edit list
   3799              // elements which have only non-editable list item elements, but
   3800              // it should be deleted from outside.  Therefore, don't treat
   3801              // non-editable things as invisible.
   3802              // TODO: Currently, HTMLEditor does not support deleting such list
   3803              //       element with Backspace.  We should fix this issue.
   3804          });
   3805    }
   3806    // TODO: Perhaps, we should delete <table>s if they have no <td>/<th>
   3807    //       element, or something other elements which must have specific
   3808    //       children but they don't.
   3809    if (isEmptyNodeShouldNotInserted) {
   3810      nsIContent* nextChild = child->GetNextSibling();
   3811      OwningNonNull<nsIContent> removingChild(*child);
   3812      removingChild->Remove();
   3813      child = nextChild;
   3814      continue;
   3815    }
   3816    if (child->HasChildNodes()) {
   3817      RemoveIncompleteDescendantsFromInsertingFragment(*child);
   3818    }
   3819    child = child->GetNextSibling();
   3820  }
   3821 }
   3822 
   3823 // static
   3824 bool HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
   3825    IsInsertionCookie(const nsIContent& aContent) {
   3826  // Is this child the magical cookie?
   3827  if (const auto* comment = Comment::FromNode(&aContent)) {
   3828    nsAutoString data;
   3829    comment->GetData(data);
   3830 
   3831    return data.EqualsLiteral(kInsertCookie);
   3832  }
   3833 
   3834  return false;
   3835 }
   3836 
   3837 /**
   3838 * This function finds the target node that we will be pasting into. aStart is
   3839 * the context that we're given and aResult will be the target. Initially,
   3840 * *aResult must be nullptr.
   3841 *
   3842 * The target for a paste is found by either finding the node that contains
   3843 * the magical comment node containing kInsertCookie or, failing that, the
   3844 * firstChild of the firstChild (until we reach a leaf).
   3845 */
   3846 bool HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
   3847    FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie(
   3848        nsINode& aStart, nsCOMPtr<nsINode>& aResult) {
   3849  nsIContent* firstChild = aStart.GetFirstChild();
   3850  if (!firstChild) {
   3851    // If the current result is nullptr, then aStart is a leaf, and is the
   3852    // fallback result.
   3853    if (!aResult) {
   3854      aResult = &aStart;
   3855    }
   3856    return false;
   3857  }
   3858 
   3859  for (nsCOMPtr<nsIContent> child = firstChild; child;
   3860       child = child->GetNextSibling()) {
   3861    if (FragmentFromPasteCreator::IsInsertionCookie(*child)) {
   3862      // Yes it is! Return an error so we bubble out and short-circuit the
   3863      // search.
   3864      aResult = &aStart;
   3865 
   3866      child->Remove();
   3867 
   3868      return true;
   3869    }
   3870 
   3871    if (FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie(*child,
   3872                                                                     aResult)) {
   3873      return true;
   3874    }
   3875  }
   3876 
   3877  return false;
   3878 }
   3879 
   3880 class MOZ_STACK_CLASS HTMLEditor::HTMLWithContextInserter::FragmentParser
   3881    final {
   3882 public:
   3883  FragmentParser(const Document& aDocument, SafeToInsertData aSafeToInsertData);
   3884 
   3885  [[nodiscard]] nsresult ParseContext(const nsAString& aContextString,
   3886                                      DocumentFragment** aFragment);
   3887 
   3888  [[nodiscard]] nsresult ParsePastedHTML(const nsAString& aInputString,
   3889                                         nsAtom* aContextLocalNameAtom,
   3890                                         DocumentFragment** aFragment);
   3891 
   3892 private:
   3893  static nsresult ParseFragment(const nsAString& aStr,
   3894                                nsAtom* aContextLocalName,
   3895                                const Document* aTargetDoc,
   3896                                dom::DocumentFragment** aFragment,
   3897                                SafeToInsertData aSafeToInsertData);
   3898 
   3899  const Document& mDocument;
   3900  const SafeToInsertData mSafeToInsertData;
   3901 };
   3902 
   3903 HTMLEditor::HTMLWithContextInserter::FragmentParser::FragmentParser(
   3904    const Document& aDocument, SafeToInsertData aSafeToInsertData)
   3905    : mDocument{aDocument}, mSafeToInsertData{aSafeToInsertData} {}
   3906 
   3907 nsresult HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext(
   3908    const nsAString& aContextStr, DocumentFragment** aFragment) {
   3909  return FragmentParser::ParseFragment(aContextStr, nullptr, &mDocument,
   3910                                       aFragment, mSafeToInsertData);
   3911 }
   3912 
   3913 nsresult HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML(
   3914    const nsAString& aInputString, nsAtom* aContextLocalNameAtom,
   3915    DocumentFragment** aFragment) {
   3916  return FragmentParser::ParseFragment(aInputString, aContextLocalNameAtom,
   3917                                       &mDocument, aFragment,
   3918                                       mSafeToInsertData);
   3919 }
   3920 
   3921 nsresult HTMLEditor::HTMLWithContextInserter::CreateDOMFragmentFromPaste(
   3922    const nsAString& aInputString, const nsAString& aContextStr,
   3923    const nsAString& aInfoStr, nsCOMPtr<nsINode>* aOutFragNode,
   3924    nsCOMPtr<nsINode>* aOutStartNode, nsCOMPtr<nsINode>* aOutEndNode,
   3925    uint32_t* aOutStartOffset, uint32_t* aOutEndOffset,
   3926    SafeToInsertData aSafeToInsertData) const {
   3927  if (NS_WARN_IF(!aOutFragNode) || NS_WARN_IF(!aOutStartNode) ||
   3928      NS_WARN_IF(!aOutEndNode) || NS_WARN_IF(!aOutStartOffset) ||
   3929      NS_WARN_IF(!aOutEndOffset)) {
   3930    return NS_ERROR_INVALID_ARG;
   3931  }
   3932 
   3933  RefPtr<const Document> document = mHTMLEditor.GetDocument();
   3934  if (NS_WARN_IF(!document)) {
   3935    return NS_ERROR_FAILURE;
   3936  }
   3937 
   3938  FragmentFromPasteCreator fragmentFromPasteCreator;
   3939 
   3940  const nsresult rv = fragmentFromPasteCreator.Run(
   3941      *document, aInputString, aContextStr, aInfoStr, aOutFragNode,
   3942      aOutStartNode, aOutEndNode, aSafeToInsertData);
   3943 
   3944  *aOutStartOffset = 0;
   3945  *aOutEndOffset = (*aOutEndNode)->Length();
   3946 
   3947  return rv;
   3948 }
   3949 
   3950 // static
   3951 nsAtom* HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
   3952    DetermineContextLocalNameForParsingPastedHTML(
   3953        const nsIContent* aParentContentOfPastedHTMLInContext) {
   3954  if (!aParentContentOfPastedHTMLInContext) {
   3955    return nsGkAtoms::body;
   3956  }
   3957 
   3958  nsAtom* contextLocalNameAtom =
   3959      aParentContentOfPastedHTMLInContext->NodeInfo()->NameAtom();
   3960 
   3961  return (aParentContentOfPastedHTMLInContext->IsHTMLElement(nsGkAtoms::html))
   3962             ? nsGkAtoms::body
   3963             : contextLocalNameAtom;
   3964 }
   3965 
   3966 // static
   3967 nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
   3968    MergeAndPostProcessFragmentsForPastedHTMLAndContext(
   3969        DocumentFragment& aDocumentFragmentForPastedHTML,
   3970        DocumentFragment& aDocumentFragmentForContext,
   3971        nsIContent& aTargetContentOfContextForPastedHTML) {
   3972  FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren(
   3973      aDocumentFragmentForPastedHTML);
   3974 
   3975  FragmentFromPasteCreator::RemoveIncompleteDescendantsFromInsertingFragment(
   3976      aDocumentFragmentForPastedHTML);
   3977 
   3978  // unite the two trees
   3979  IgnoredErrorResult ignoredError;
   3980  aTargetContentOfContextForPastedHTML.AppendChild(
   3981      aDocumentFragmentForPastedHTML, ignoredError);
   3982  NS_WARNING_ASSERTION(!ignoredError.Failed(),
   3983                       "nsINode::AppendChild() failed, but ignored");
   3984  const nsresult rv = FragmentFromPasteCreator::
   3985      RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
   3986          aDocumentFragmentForContext, NodesToRemove::eOnlyListItems);
   3987 
   3988  if (NS_FAILED(rv)) {
   3989    NS_WARNING(
   3990        "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
   3991        "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces()"
   3992        " failed");
   3993    return rv;
   3994  }
   3995 
   3996  return rv;
   3997 }
   3998 
   3999 // static
   4000 nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
   4001    PostProcessFragmentForPastedHTMLWithoutContext(
   4002        DocumentFragment& aDocumentFragmentForPastedHTML) {
   4003  FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren(
   4004      aDocumentFragmentForPastedHTML);
   4005 
   4006  FragmentFromPasteCreator::RemoveIncompleteDescendantsFromInsertingFragment(
   4007      aDocumentFragmentForPastedHTML);
   4008 
   4009  const nsresult rv = FragmentFromPasteCreator::
   4010      RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
   4011          aDocumentFragmentForPastedHTML, NodesToRemove::eOnlyListItems);
   4012 
   4013  if (NS_FAILED(rv)) {
   4014    NS_WARNING(
   4015        "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
   4016        "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces() "
   4017        "failed");
   4018    return rv;
   4019  }
   4020 
   4021  return rv;
   4022 }
   4023 
   4024 // static
   4025 nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
   4026    PreProcessContextDocumentFragmentForMerging(
   4027        DocumentFragment& aDocumentFragmentForContext) {
   4028  // The context is expected to contain text nodes only in block level
   4029  // elements. Hence, if they contain only whitespace, they're invisible.
   4030  const nsresult rv = FragmentFromPasteCreator::
   4031      RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
   4032          aDocumentFragmentForContext, NodesToRemove::eAll);
   4033  if (NS_FAILED(rv)) {
   4034    NS_WARNING(
   4035        "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
   4036        "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces() "
   4037        "failed");
   4038    return rv;
   4039  }
   4040 
   4041  FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren(
   4042      aDocumentFragmentForContext);
   4043 
   4044  return rv;
   4045 }
   4046 
   4047 nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
   4048    CreateDocumentFragmentAndGetParentOfPastedHTMLInContext(
   4049        const Document& aDocument, const nsAString& aInputString,
   4050        const nsAString& aContextStr, SafeToInsertData aSafeToInsertData,
   4051        nsCOMPtr<nsINode>& aParentNodeOfPastedHTMLInContext,
   4052        RefPtr<DocumentFragment>& aDocumentFragmentToInsert) const {
   4053  // if we have context info, create a fragment for that
   4054  RefPtr<DocumentFragment> documentFragmentForContext;
   4055 
   4056  FragmentParser fragmentParser{aDocument, aSafeToInsertData};
   4057  if (!aContextStr.IsEmpty()) {
   4058    nsresult rv = fragmentParser.ParseContext(
   4059        aContextStr, getter_AddRefs(documentFragmentForContext));
   4060    if (NS_FAILED(rv)) {
   4061      NS_WARNING(
   4062          "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext() "
   4063          "failed");
   4064      return rv;
   4065    }
   4066    if (!documentFragmentForContext) {
   4067      NS_WARNING(
   4068          "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext() "
   4069          "returned nullptr");
   4070      return NS_ERROR_FAILURE;
   4071    }
   4072 
   4073    rv = FragmentFromPasteCreator::PreProcessContextDocumentFragmentForMerging(
   4074        *documentFragmentForContext);
   4075    if (NS_FAILED(rv)) {
   4076      NS_WARNING(
   4077          "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
   4078          "PreProcessContextDocumentFragmentForMerging() failed.");
   4079      return rv;
   4080    }
   4081 
   4082    FragmentFromPasteCreator::
   4083        FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie(
   4084            *documentFragmentForContext, aParentNodeOfPastedHTMLInContext);
   4085    MOZ_ASSERT(aParentNodeOfPastedHTMLInContext);
   4086  }
   4087 
   4088  nsCOMPtr<nsIContent> parentContentOfPastedHTMLInContext =
   4089      nsIContent::FromNodeOrNull(aParentNodeOfPastedHTMLInContext);
   4090  MOZ_ASSERT_IF(aParentNodeOfPastedHTMLInContext,
   4091                parentContentOfPastedHTMLInContext);
   4092 
   4093  nsAtom* contextLocalNameAtom =
   4094      FragmentFromPasteCreator::DetermineContextLocalNameForParsingPastedHTML(
   4095          parentContentOfPastedHTMLInContext);
   4096  RefPtr<DocumentFragment> documentFragmentForPastedHTML;
   4097  nsresult rv = fragmentParser.ParsePastedHTML(
   4098      aInputString, contextLocalNameAtom,
   4099      getter_AddRefs(documentFragmentForPastedHTML));
   4100  if (NS_FAILED(rv)) {
   4101    NS_WARNING(
   4102        "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML()"
   4103        " failed");
   4104    return rv;
   4105  }
   4106  if (!documentFragmentForPastedHTML) {
   4107    NS_WARNING(
   4108        "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML()"
   4109        " returned nullptr");
   4110    return NS_ERROR_FAILURE;
   4111  }
   4112 
   4113  if (aParentNodeOfPastedHTMLInContext) {
   4114    const nsresult rv = FragmentFromPasteCreator::
   4115        MergeAndPostProcessFragmentsForPastedHTMLAndContext(
   4116            *documentFragmentForPastedHTML, *documentFragmentForContext,
   4117            *parentContentOfPastedHTMLInContext);
   4118    if (NS_FAILED(rv)) {
   4119      NS_WARNING(
   4120          "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
   4121          "MergeAndPostProcessFragmentsForPastedHTMLAndContext() failed.");
   4122      return rv;
   4123    }
   4124    aDocumentFragmentToInsert = std::move(documentFragmentForContext);
   4125  } else {
   4126    const nsresult rv = PostProcessFragmentForPastedHTMLWithoutContext(
   4127        *documentFragmentForPastedHTML);
   4128    if (NS_FAILED(rv)) {
   4129      NS_WARNING(
   4130          "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
   4131          "PostProcessFragmentForPastedHTMLWithoutContext() failed.");
   4132      return rv;
   4133    }
   4134 
   4135    aDocumentFragmentToInsert = std::move(documentFragmentForPastedHTML);
   4136  }
   4137 
   4138  return rv;
   4139 }
   4140 
   4141 nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::Run(
   4142    const Document& aDocument, const nsAString& aInputString,
   4143    const nsAString& aContextStr, const nsAString& aInfoStr,
   4144    nsCOMPtr<nsINode>* aOutFragNode, nsCOMPtr<nsINode>* aOutStartNode,
   4145    nsCOMPtr<nsINode>* aOutEndNode, SafeToInsertData aSafeToInsertData) const {
   4146  MOZ_ASSERT(aOutFragNode);
   4147  MOZ_ASSERT(aOutStartNode);
   4148  MOZ_ASSERT(aOutEndNode);
   4149 
   4150  nsCOMPtr<nsINode> parentNodeOfPastedHTMLInContext;
   4151  RefPtr<DocumentFragment> documentFragmentToInsert;
   4152  nsresult rv = CreateDocumentFragmentAndGetParentOfPastedHTMLInContext(
   4153      aDocument, aInputString, aContextStr, aSafeToInsertData,
   4154      parentNodeOfPastedHTMLInContext, documentFragmentToInsert);
   4155  if (NS_FAILED(rv)) {
   4156    NS_WARNING(
   4157        "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
   4158        "CreateDocumentFragmentAndGetParentOfPastedHTMLInContext() failed.");
   4159    return rv;
   4160  }
   4161 
   4162  // If there was no context, then treat all of the data we did get as the
   4163  // pasted data.
   4164  if (parentNodeOfPastedHTMLInContext) {
   4165    *aOutEndNode = *aOutStartNode = parentNodeOfPastedHTMLInContext;
   4166  } else {
   4167    *aOutEndNode = *aOutStartNode = documentFragmentToInsert;
   4168  }
   4169 
   4170  *aOutFragNode = std::move(documentFragmentToInsert);
   4171 
   4172  if (!aInfoStr.IsEmpty()) {
   4173    const nsresult rv =
   4174        FragmentFromPasteCreator::MoveStartAndEndAccordingToHTMLInfo(
   4175            aInfoStr, aOutStartNode, aOutEndNode);
   4176    if (NS_FAILED(rv)) {
   4177      NS_WARNING(
   4178          "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
   4179          "MoveStartAndEndAccordingToHTMLInfo() failed");
   4180      return rv;
   4181    }
   4182  }
   4183 
   4184  return NS_OK;
   4185 }
   4186 
   4187 // static
   4188 nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::
   4189    MoveStartAndEndAccordingToHTMLInfo(const nsAString& aInfoStr,
   4190                                       nsCOMPtr<nsINode>* aOutStartNode,
   4191                                       nsCOMPtr<nsINode>* aOutEndNode) {
   4192  int32_t sep = aInfoStr.FindChar((char16_t)',');
   4193  nsAutoString numstr1(Substring(aInfoStr, 0, sep));
   4194  nsAutoString numstr2(
   4195      Substring(aInfoStr, sep + 1, aInfoStr.Length() - (sep + 1)));
   4196 
   4197  // Move the start and end children.
   4198  nsresult rvIgnored;
   4199  int32_t num = numstr1.ToInteger(&rvIgnored);
   4200  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   4201                       "nsAString::ToInteger() failed, but ignored");
   4202  while (num--) {
   4203    nsINode* tmp = (*aOutStartNode)->GetFirstChild();
   4204    if (!tmp) {
   4205      NS_WARNING("aOutStartNode did not have children");
   4206      return NS_ERROR_FAILURE;
   4207    }
   4208    *aOutStartNode = tmp;
   4209  }
   4210 
   4211  num = numstr2.ToInteger(&rvIgnored);
   4212  while (num--) {
   4213    nsINode* tmp = (*aOutEndNode)->GetLastChild();
   4214    if (!tmp) {
   4215      NS_WARNING("aOutEndNode did not have children");
   4216      return NS_ERROR_FAILURE;
   4217    }
   4218    *aOutEndNode = tmp;
   4219  }
   4220 
   4221  return NS_OK;
   4222 }
   4223 
   4224 // static
   4225 nsresult HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseFragment(
   4226    const nsAString& aFragStr, nsAtom* aContextLocalName,
   4227    const Document* aTargetDocument, DocumentFragment** aFragment,
   4228    SafeToInsertData aSafeToInsertData) {
   4229  nsAutoScriptBlockerSuppressNodeRemoved autoBlocker;
   4230 
   4231  nsCOMPtr<Document> doc =
   4232      nsContentUtils::CreateInertHTMLDocument(aTargetDocument);
   4233  if (!doc) {
   4234    return NS_ERROR_FAILURE;
   4235  }
   4236  RefPtr<DocumentFragment> fragment =
   4237      new (doc->NodeInfoManager()) DocumentFragment(doc->NodeInfoManager());
   4238  nsresult rv = nsContentUtils::ParseFragmentHTML(
   4239      aFragStr, fragment,
   4240      aContextLocalName ? aContextLocalName : nsGkAtoms::body,
   4241      kNameSpaceID_XHTML, false, true);
   4242  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   4243                       "nsContentUtils::ParseFragmentHTML() failed");
   4244  if (aSafeToInsertData == SafeToInsertData::No) {
   4245    nsTreeSanitizer sanitizer(aContextLocalName
   4246                                  ? nsIParserUtils::SanitizerAllowStyle
   4247                                  : nsIParserUtils::SanitizerAllowComments);
   4248    sanitizer.Sanitize(fragment);
   4249  }
   4250  fragment.forget(aFragment);
   4251  return rv;
   4252 }
   4253 
   4254 // static
   4255 void HTMLEditor::HTMLWithContextInserter::
   4256    CollectTopMostChildContentsCompletelyInRange(
   4257        const EditorRawDOMPoint& aStartPoint,
   4258        const EditorRawDOMPoint& aEndPoint,
   4259        nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents) {
   4260  MOZ_ASSERT(aStartPoint.IsSetAndValid());
   4261  MOZ_ASSERT(aEndPoint.IsSetAndValid());
   4262 
   4263  RefPtr<nsRange> range =
   4264      nsRange::Create(aStartPoint.ToRawRangeBoundary(),
   4265                      aEndPoint.ToRawRangeBoundary(), IgnoreErrors());
   4266  if (!range) {
   4267    NS_WARNING("nsRange::Create() failed");
   4268    return;
   4269  }
   4270  DOMSubtreeIterator iter;
   4271  if (NS_FAILED(iter.Init(*range))) {
   4272    NS_WARNING("DOMSubtreeIterator::Init() failed, but ignored");
   4273    return;
   4274  }
   4275 
   4276  iter.AppendAllNodesToArray(aOutArrayOfContents);
   4277 }
   4278 
   4279 /******************************************************************************
   4280 * HTMLEditor::AutoHTMLFragmentBoundariesFixer
   4281 ******************************************************************************/
   4282 
   4283 HTMLEditor::AutoHTMLFragmentBoundariesFixer::AutoHTMLFragmentBoundariesFixer(
   4284    nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents) {
   4285  EnsureBeginsOrEndsWithValidContent(StartOrEnd::start,
   4286                                     aArrayOfTopMostChildContents);
   4287  EnsureBeginsOrEndsWithValidContent(StartOrEnd::end,
   4288                                     aArrayOfTopMostChildContents);
   4289 }
   4290 
   4291 // static
   4292 void HTMLEditor::AutoHTMLFragmentBoundariesFixer::
   4293    CollectTableAndAnyListElementsOfInclusiveAncestorsAt(
   4294        nsIContent& aContent,
   4295        nsTArray<OwningNonNull<Element>>& aOutArrayOfListAndTableElements) {
   4296  for (Element* element = aContent.GetAsElementOrParentElement(); element;
   4297       element = element->GetParentElement()) {
   4298    if (HTMLEditUtils::IsListElement(*element) ||
   4299        element->IsHTMLElement(nsGkAtoms::table)) {
   4300      aOutArrayOfListAndTableElements.AppendElement(*element);
   4301    }
   4302  }
   4303 }
   4304 
   4305 // static
   4306 Element* HTMLEditor::AutoHTMLFragmentBoundariesFixer::
   4307    GetMostDistantAncestorListOrTableElement(
   4308        const nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents,
   4309        const nsTArray<OwningNonNull<Element>>&
   4310            aInclusiveAncestorsTableOrListElements) {
   4311  Element* lastFoundAncestorListOrTableElement = nullptr;
   4312  for (const OwningNonNull<nsIContent>& content :
   4313       aArrayOfTopMostChildContents) {
   4314    if (HTMLEditUtils::IsAnyTableElementExceptTableElementAndColumElement(
   4315            content)) {
   4316      Element* tableElement =
   4317          HTMLEditUtils::GetClosestAncestorTableElement(*content);
   4318      if (!tableElement) {
   4319        continue;
   4320      }
   4321      // If we find a `<table>` element which is an ancestor of a table
   4322      // related element and is not an acestor of first nor last of
   4323      // aArrayOfNodes, return the last found list or `<table>` element.
   4324      // XXX Is that really expected that this returns a list element in this
   4325      //     case?
   4326      if (!aInclusiveAncestorsTableOrListElements.Contains(tableElement)) {
   4327        return lastFoundAncestorListOrTableElement;
   4328      }
   4329      // If we find a `<table>` element which is topmost list or `<table>`
   4330      // element at first or last of aArrayOfNodes, return it.
   4331      if (aInclusiveAncestorsTableOrListElements.LastElement().get() ==
   4332          tableElement) {
   4333        return tableElement;
   4334      }
   4335      // Otherwise, store the `<table>` element which is an ancestor but
   4336      // not topmost ancestor of first or last of aArrayOfNodes.
   4337      lastFoundAncestorListOrTableElement = tableElement;
   4338      continue;
   4339    }
   4340 
   4341    if (!HTMLEditUtils::IsListItemElement(*content)) {
   4342      continue;
   4343    }
   4344    Element* listElement =
   4345        HTMLEditUtils::GetClosestAncestorAnyListElement(*content);
   4346    if (!listElement) {
   4347      continue;
   4348    }
   4349    // If we find a list element which is ancestor of a list item element and
   4350    // is not an acestor of first nor last of aArrayOfNodes, return the last
   4351    // found list or `<table>` element.
   4352    // XXX Is that really expected that this returns a `<table>` element in
   4353    //     this case?
   4354    if (!aInclusiveAncestorsTableOrListElements.Contains(listElement)) {
   4355      return lastFoundAncestorListOrTableElement;
   4356    }
   4357    // If we find a list element which is topmost list or `<table>` element at
   4358    // first or last of aArrayOfNodes, return it.
   4359    if (aInclusiveAncestorsTableOrListElements.LastElement().get() ==
   4360        listElement) {
   4361      return listElement;
   4362    }
   4363    // Otherwise, store the list element which is an ancestor but not topmost
   4364    // ancestor of first or last of aArrayOfNodes.
   4365    lastFoundAncestorListOrTableElement = listElement;
   4366  }
   4367 
   4368  // If we find only non-topmost list or `<table>` element, returns the last
   4369  // found one (meaning bottommost one).  Otherwise, nullptr.
   4370  return lastFoundAncestorListOrTableElement;
   4371 }
   4372 
   4373 Element*
   4374 HTMLEditor::AutoHTMLFragmentBoundariesFixer::FindReplaceableTableElement(
   4375    Element& aTableElement, nsIContent& aContentMaybeInTableElement) const {
   4376  MOZ_ASSERT(aTableElement.IsHTMLElement(nsGkAtoms::table));
   4377  // Perhaps, this is designed for climbing up the DOM tree from
   4378  // aContentMaybeInTableElement to aTableElement and making sure that
   4379  // aContentMaybeInTableElement itself or its ancestor is a `<td>`, `<th>`,
   4380  // `<tr>`, `<thead>`, `<tbody>`, `<tfoot>` or `<caption>`.
   4381  // But this looks really buggy because this loop may skip aTableElement
   4382  // as the following NS_ASSERTION.  We should write automated tests and
   4383  // check right behavior.
   4384  for (Element* element =
   4385           aContentMaybeInTableElement.GetAsElementOrParentElement();
   4386       element; element = element->GetParentElement()) {
   4387    if (!HTMLEditUtils::IsAnyTableElementExceptColumnElement(*element) ||
   4388        element->IsHTMLElement(nsGkAtoms::table)) {
   4389      // XXX Perhaps, the original developer of this method assumed that
   4390      //     aTableElement won't be skipped because if it's assumed, we can
   4391      //     stop climbing up the tree in that case.
   4392      NS_ASSERTION(element != &aTableElement,
   4393                   "The table element which is looking for is ignored");
   4394      continue;
   4395    }
   4396    Element* tableElement = nullptr;
   4397    for (Element* maybeTableElement = element->GetParentElement();
   4398         maybeTableElement;
   4399         maybeTableElement = maybeTableElement->GetParentElement()) {
   4400      if (maybeTableElement->IsHTMLElement(nsGkAtoms::table)) {
   4401        tableElement = maybeTableElement;
   4402        break;
   4403      }
   4404    }
   4405    if (tableElement == &aTableElement) {
   4406      return element;
   4407    }
   4408    // XXX If we find another `<table>` element, why don't we keep searching
   4409    //     from its parent?
   4410  }
   4411  return nullptr;
   4412 }
   4413 
   4414 bool HTMLEditor::AutoHTMLFragmentBoundariesFixer::IsReplaceableListElement(
   4415    Element& aListElement, nsIContent& aContentMaybeInListElement) const {
   4416  MOZ_ASSERT(HTMLEditUtils::IsListElement(aListElement));
   4417  // Perhaps, this is designed for climbing up the DOM tree from
   4418  // aContentMaybeInListElement to aListElement and making sure that
   4419  // aContentMaybeInListElement itself or its ancestor is an list item.
   4420  // But this looks really buggy because this loop may skip aListElement
   4421  // as the following NS_ASSERTION.  We should write automated tests and
   4422  // check right behavior.
   4423  for (Element* element =
   4424           aContentMaybeInListElement.GetAsElementOrParentElement();
   4425       element; element = element->GetParentElement()) {
   4426    if (!HTMLEditUtils::IsListItemElement(*element)) {
   4427      // XXX Perhaps, the original developer of this method assumed that
   4428      //     aListElement won't be skipped because if it's assumed, we can
   4429      //     stop climbing up the tree in that case.
   4430      NS_ASSERTION(element != &aListElement,
   4431                   "The list element which is looking for is ignored");
   4432      continue;
   4433    }
   4434    Element* listElement =
   4435        HTMLEditUtils::GetClosestAncestorAnyListElement(*element);
   4436    if (listElement == &aListElement) {
   4437      return true;
   4438    }
   4439    // XXX If we find another list element, why don't we keep searching
   4440    //     from its parent?
   4441  }
   4442  return false;
   4443 }
   4444 
   4445 void HTMLEditor::AutoHTMLFragmentBoundariesFixer::
   4446    EnsureBeginsOrEndsWithValidContent(StartOrEnd aStartOrEnd,
   4447                                       nsTArray<OwningNonNull<nsIContent>>&
   4448                                           aArrayOfTopMostChildContents) const {
   4449  MOZ_ASSERT(!aArrayOfTopMostChildContents.IsEmpty());
   4450 
   4451  // Collect list elements and table related elements at first or last node
   4452  // in aArrayOfTopMostChildContents.
   4453  AutoTArray<OwningNonNull<Element>, 4> inclusiveAncestorsListOrTableElements;
   4454  CollectTableAndAnyListElementsOfInclusiveAncestorsAt(
   4455      aStartOrEnd == StartOrEnd::end
   4456          ? aArrayOfTopMostChildContents.LastElement()
   4457          : aArrayOfTopMostChildContents[0],
   4458      inclusiveAncestorsListOrTableElements);
   4459  if (inclusiveAncestorsListOrTableElements.IsEmpty()) {
   4460    return;
   4461  }
   4462 
   4463  // Get most ancestor list or `<table>` element in
   4464  // inclusiveAncestorsListOrTableElements which contains earlier
   4465  // node in aArrayOfTopMostChildContents as far as possible.
   4466  // XXX With inclusiveAncestorsListOrTableElements, this returns a
   4467  //     list or `<table>` element which contains first or last node of
   4468  //     aArrayOfTopMostChildContents.  However, this seems slow when
   4469  //     aStartOrEnd is StartOrEnd::end and only the last node is in
   4470  //     different list or `<table>`.  But I'm not sure whether it's
   4471  //     possible case or not.  We need to add tests to
   4472  //     test_content_iterator_subtree.html for checking how
   4473  //     SubtreeContentIterator works.
   4474  Element* listOrTableElement = GetMostDistantAncestorListOrTableElement(
   4475      aArrayOfTopMostChildContents, inclusiveAncestorsListOrTableElements);
   4476  if (!listOrTableElement) {
   4477    return;
   4478  }
   4479 
   4480  // If we have pieces of tables or lists to be inserted, let's force the
   4481  // insertion to deal with table elements right away, so that it doesn't
   4482  // orphan some table or list contents outside the table or list.
   4483 
   4484  OwningNonNull<nsIContent>& firstOrLastChildContent =
   4485      aStartOrEnd == StartOrEnd::end
   4486          ? aArrayOfTopMostChildContents.LastElement()
   4487          : aArrayOfTopMostChildContents[0];
   4488 
   4489  // Find substructure of list or table that must be included in paste.
   4490  Element* replaceElement;
   4491  if (HTMLEditUtils::IsListElement(*listOrTableElement)) {
   4492    if (!IsReplaceableListElement(*listOrTableElement,
   4493                                  firstOrLastChildContent)) {
   4494      return;
   4495    }
   4496    replaceElement = listOrTableElement;
   4497  } else {
   4498    MOZ_ASSERT(listOrTableElement->IsHTMLElement(nsGkAtoms::table));
   4499    replaceElement = FindReplaceableTableElement(*listOrTableElement,
   4500                                                 firstOrLastChildContent);
   4501    if (!replaceElement) {
   4502      return;
   4503    }
   4504  }
   4505 
   4506  // If we can replace the given list element or found a table related element
   4507  // in the `<table>` element, insert it into aArrayOfTopMostChildContents which
   4508  // is tompost children to be inserted instead of descendants of them in
   4509  // aArrayOfTopMostChildContents.
   4510  for (size_t i = 0; i < aArrayOfTopMostChildContents.Length();) {
   4511    OwningNonNull<nsIContent>& content = aArrayOfTopMostChildContents[i];
   4512    if (content == replaceElement) {
   4513      // If the element is n aArrayOfTopMostChildContents, its descendants must
   4514      // not be in the array.  Therefore, we don't need to optimize this case.
   4515      // XXX Perhaps, we can break this loop right now.
   4516      aArrayOfTopMostChildContents.RemoveElementAt(i);
   4517      continue;
   4518    }
   4519    if (!EditorUtils::IsDescendantOf(content, *replaceElement)) {
   4520      i++;
   4521      continue;
   4522    }
   4523    // For saving number of calls of EditorUtils::IsDescendantOf(), we should
   4524    // remove its siblings in the array.
   4525    nsIContent* parent = content->GetParent();
   4526    aArrayOfTopMostChildContents.RemoveElementAt(i);
   4527    while (i < aArrayOfTopMostChildContents.Length() &&
   4528           aArrayOfTopMostChildContents[i]->GetParent() == parent) {
   4529      aArrayOfTopMostChildContents.RemoveElementAt(i);
   4530    }
   4531  }
   4532 
   4533  // Now replace the removed nodes with the structural parent
   4534  if (aStartOrEnd == StartOrEnd::end) {
   4535    aArrayOfTopMostChildContents.AppendElement(*replaceElement);
   4536  } else {
   4537    aArrayOfTopMostChildContents.InsertElementAt(0, *replaceElement);
   4538  }
   4539 }
   4540 
   4541 }  // namespace mozilla