From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- editor/libeditor/HTMLEditorDataTransfer.cpp | 4325 +++++++++++++++++++++++++++ 1 file changed, 4325 insertions(+) create mode 100644 editor/libeditor/HTMLEditorDataTransfer.cpp (limited to 'editor/libeditor/HTMLEditorDataTransfer.cpp') diff --git a/editor/libeditor/HTMLEditorDataTransfer.cpp b/editor/libeditor/HTMLEditorDataTransfer.cpp new file mode 100644 index 0000000000..70110fd6a6 --- /dev/null +++ b/editor/libeditor/HTMLEditorDataTransfer.cpp @@ -0,0 +1,4325 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=78: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "HTMLEditor.h" + +#include + +#include "EditAction.h" +#include "EditorDOMPoint.h" +#include "EditorUtils.h" +#include "HTMLEditHelpers.h" +#include "HTMLEditUtils.h" +#include "InternetCiter.h" +#include "PendingStyles.h" +#include "SelectionState.h" +#include "WSRunObject.h" + +#include "ErrorList.h" +#include "mozilla/dom/Comment.h" +#include "mozilla/dom/DataTransfer.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/DocumentFragment.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/DOMStringList.h" +#include "mozilla/dom/DOMStringList.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/FileBlobImpl.h" +#include "mozilla/dom/FileReader.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/dom/StaticRange.h" +#include "mozilla/dom/WorkerRef.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/Base64.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Maybe.h" +#include "mozilla/OwningNonNull.h" +#include "mozilla/Preferences.h" +#include "mozilla/Result.h" +#include "nsAString.h" +#include "nsCOMPtr.h" +#include "nsCRTGlue.h" // for CRLF +#include "nsComponentManagerUtils.h" +#include "nsIScriptError.h" +#include "nsContentUtils.h" +#include "nsDebug.h" +#include "nsDependentSubstring.h" +#include "nsError.h" +#include "nsFocusManager.h" +#include "nsGkAtoms.h" +#include "nsIClipboard.h" +#include "nsIContent.h" +#include "nsIDocumentEncoder.h" +#include "nsIFile.h" +#include "nsIInputStream.h" +#include "nsIMIMEService.h" +#include "nsINode.h" +#include "nsIParserUtils.h" +#include "nsIPrincipal.h" +#include "nsISupportsImpl.h" +#include "nsISupportsPrimitives.h" +#include "nsISupportsUtils.h" +#include "nsITransferable.h" +#include "nsIVariant.h" +#include "nsLinebreakConverter.h" +#include "nsLiteralString.h" +#include "nsNameSpaceManager.h" +#include "nsNetUtil.h" +#include "nsPrintfCString.h" +#include "nsRange.h" +#include "nsReadableUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsStringFwd.h" +#include "nsStringIterator.h" +#include "nsTreeSanitizer.h" +#include "nsXPCOM.h" +#include "nscore.h" +#include "nsContentUtils.h" +#include "nsQueryObject.h" + +class nsAtom; +class nsILoadContext; + +namespace mozilla { + +using namespace dom; +using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; +using LeafNodeType = HTMLEditUtils::LeafNodeType; + +#define kInsertCookie "_moz_Insert Here_moz_" + +// some little helpers +static bool FindIntegerAfterString(const char* aLeadingString, + const nsCString& aCStr, + int32_t& foundNumber); +static void RemoveFragComments(nsCString& aStr); + +nsresult HTMLEditor::InsertDroppedDataTransferAsAction( + AutoEditActionDataSetter& aEditActionData, DataTransfer& aDataTransfer, + const EditorDOMPoint& aDroppedAt, nsIPrincipal* aSourcePrincipal) { + MOZ_ASSERT(aEditActionData.GetEditAction() == EditAction::eDrop); + MOZ_ASSERT(GetEditAction() == EditAction::eDrop); + MOZ_ASSERT(aDroppedAt.IsSet()); + MOZ_ASSERT(aDataTransfer.MozItemCount() > 0); + + if (IsReadonly()) { + return NS_OK; + } + + aEditActionData.InitializeDataTransfer(&aDataTransfer); + RefPtr targetRange = StaticRange::Create( + aDroppedAt.GetContainer(), aDroppedAt.Offset(), aDroppedAt.GetContainer(), + aDroppedAt.Offset(), IgnoreErrors()); + NS_WARNING_ASSERTION(targetRange && targetRange->IsPositioned(), + "Why did we fail to create collapsed static range at " + "dropped position?"); + if (targetRange && targetRange->IsPositioned()) { + aEditActionData.AppendTargetRange(*targetRange); + } + nsresult rv = aEditActionData.MaybeDispatchBeforeInputEvent(); + if (NS_FAILED(rv)) { + NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, + "MaybeDispatchBeforeInputEvent() failed"); + return rv; + } + uint32_t numItems = aDataTransfer.MozItemCount(); + for (uint32_t i = 0; i < numItems; ++i) { + DebugOnly rvIgnored = + InsertFromDataTransfer(&aDataTransfer, i, aSourcePrincipal, aDroppedAt, + DeleteSelectedContent::No); + if (NS_WARN_IF(Destroyed())) { + return NS_OK; + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "HTMLEditor::InsertFromDataTransfer(" + "DeleteSelectedContent::No) failed, but ignored"); + } + return NS_OK; +} + +nsresult HTMLEditor::LoadHTML(const nsAString& aInputString) { + MOZ_ASSERT(IsEditActionDataAvailable()); + + if (NS_WARN_IF(!mInitSucceeded)) { + return NS_ERROR_NOT_INITIALIZED; + } + + // force IME commit; set up rules sniffing and batching + DebugOnly rvIgnored = CommitComposition(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "EditorBase::CommitComposition() failed, but ignored"); + if (NS_WARN_IF(Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; + } + + AutoPlaceholderBatch treatAsOneTransaction( + *this, ScrollSelectionIntoView::Yes, __FUNCTION__); + IgnoredErrorResult ignoredError; + AutoEditSubActionNotifier startToHandleEditSubAction( + *this, EditSubAction::eInsertHTMLSource, nsIEditor::eNext, ignoredError); + if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { + return ignoredError.StealNSResult(); + } + NS_WARNING_ASSERTION( + !ignoredError.Failed(), + "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); + + nsresult rv = EnsureNoPaddingBRElementForEmptyEditor(); + if (NS_FAILED(rv)) { + NS_WARNING("EditorBase::EnsureNoPaddingBRElementForEmptyEditor() failed"); + return rv; + } + + // Delete Selection, but only if it isn't collapsed, see bug #106269 + if (!SelectionRef().IsCollapsed()) { + nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip); + if (NS_FAILED(rv)) { + NS_WARNING( + "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed"); + return rv; + } + } + + // Get the first range in the selection, for context: + RefPtr range = SelectionRef().GetRangeAt(0); + if (NS_WARN_IF(!range)) { + return NS_ERROR_FAILURE; + } + + // Create fragment for pasted HTML. + ErrorResult error; + RefPtr documentFragment = + range->CreateContextualFragment(aInputString, error); + if (error.Failed()) { + NS_WARNING("nsRange::CreateContextualFragment() failed"); + return error.StealNSResult(); + } + + // Put the fragment into the document at start of selection. + EditorDOMPoint pointToInsert(range->StartRef()); + // XXX We need to make pointToInsert store offset for keeping traditional + // behavior since using only child node to pointing insertion point + // changes the behavior when inserted child is moved by mutation + // observer. We need to investigate what we should do here. + Unused << pointToInsert.Offset(); + EditorDOMPoint pointToPutCaret; + for (nsCOMPtr contentToInsert = documentFragment->GetFirstChild(); + contentToInsert; contentToInsert = documentFragment->GetFirstChild()) { + Result insertChildContentNodeResult = + InsertNodeWithTransaction(*contentToInsert, pointToInsert); + if (MOZ_UNLIKELY(insertChildContentNodeResult.isErr())) { + NS_WARNING("EditorBase::InsertNodeWithTransaction() failed"); + return insertChildContentNodeResult.unwrapErr(); + } + CreateContentResult unwrappedInsertChildContentNodeResult = + insertChildContentNodeResult.unwrap(); + unwrappedInsertChildContentNodeResult.MoveCaretPointTo( + pointToPutCaret, *this, + {SuggestCaret::OnlyIfHasSuggestion, + SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); + // XXX If the inserted node has been moved by mutation observer, + // incrementing offset will cause odd result. Next new node + // will be inserted after existing node and the offset will be + // overflown from the container node. + pointToInsert.Set(pointToInsert.GetContainer(), pointToInsert.Offset() + 1); + if (NS_WARN_IF(!pointToInsert.Offset())) { + // Append the remaining children to the container if offset is + // overflown. + pointToInsert.SetToEndOf(pointToInsert.GetContainer()); + } + } + + if (pointToPutCaret.IsSet()) { + nsresult rv = CollapseSelectionTo(pointToPutCaret); + if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { + NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "EditorBase::CollapseSelectionTo() failed, but ignored"); + } + + return NS_OK; +} + +NS_IMETHODIMP HTMLEditor::InsertHTML(const nsAString& aInString) { + nsresult rv = InsertHTMLAsAction(aInString); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::InsertHTMLAsAction() failed"); + return rv; +} + +nsresult HTMLEditor::InsertHTMLAsAction(const nsAString& aInString, + nsIPrincipal* aPrincipal) { + // FIXME: This should keep handling inserting HTML if the caller is + // nsIHTMLEditor::InsertHTML. + if (IsReadonly()) { + return NS_OK; + } + + AutoEditActionDataSetter editActionData(*this, EditAction::eInsertHTML, + aPrincipal); + nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); + if (NS_FAILED(rv)) { + NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, + "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); + return EditorBase::ToGenericNSResult(rv); + } + + AutoPlaceholderBatch treatAsOneTransaction( + *this, ScrollSelectionIntoView::Yes, __FUNCTION__); + rv = InsertHTMLWithContextAsSubAction(aInString, u""_ns, u""_ns, u""_ns, + SafeToInsertData::Yes, EditorDOMPoint(), + DeleteSelectedContent::Yes, + InlineStylesAtInsertionPoint::Clear); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::InsertHTMLWithContextAsSubAction(" + "SafeToInsertData::Yes, DeleteSelectedContent::Yes, " + "InlineStylesAtInsertionPoint::Clear) failed"); + return EditorBase::ToGenericNSResult(rv); +} + +class MOZ_STACK_CLASS HTMLEditor::HTMLWithContextInserter final { + public: + MOZ_CAN_RUN_SCRIPT explicit HTMLWithContextInserter(HTMLEditor& aHTMLEditor) + : mHTMLEditor(aHTMLEditor) {} + + HTMLWithContextInserter() = delete; + HTMLWithContextInserter(const HTMLWithContextInserter&) = delete; + HTMLWithContextInserter(HTMLWithContextInserter&&) = delete; + + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result Run( + const nsAString& aInputString, const nsAString& aContextStr, + const nsAString& aInfoStr, SafeToInsertData aSafeToInsertData, + InlineStylesAtInsertionPoint aInlineStylesAtInsertionPoint); + + private: + class FragmentFromPasteCreator; + class FragmentParser; + /** + * CollectTopMostChildContentsCompletelyInRange() collects topmost child + * contents which are completely in the given range. + * For example, if the range points a node with its container node, the + * result is only the node (meaning does not include its descendants). + * If the range starts start of a node and ends end of it, and if the node + * does not have children, returns no nodes, otherwise, if the node has + * some children, the result includes its all children (not including their + * descendants). + * + * @param aStartPoint Start point of the range. + * @param aEndPoint End point of the range. + * @param aOutArrayOfContents [Out] Topmost children which are completely in + * the range. + */ + static void CollectTopMostChildContentsCompletelyInRange( + const EditorRawDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint, + nsTArray>& aOutArrayOfContents); + + /** + * @return nullptr, if there's no invisible `
`. + */ + HTMLBRElement* GetInvisibleBRElementAtPoint( + const EditorDOMPoint& aPointToInsert) const; + + EditorDOMPoint GetNewCaretPointAfterInsertingHTML( + const EditorDOMPoint& aLastInsertedPoint) const; + + /** + * @return error result or the last inserted point. The latter is only set, if + * content was inserted. + */ + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result + InsertContents( + const EditorDOMPoint& aPointToInsert, + nsTArray>& aArrayOfTopMostChildContents, + const nsINode* aFragmentAsNode); + + /** + * @param aContextStr as indicated by nsITransferable's kHTMLContext. + * @param aInfoStr as indicated by nsITransferable's kHTMLInfo. + */ + nsresult CreateDOMFragmentFromPaste( + const nsAString& aInputString, const nsAString& aContextStr, + const nsAString& aInfoStr, nsCOMPtr* aOutFragNode, + nsCOMPtr* aOutStartNode, nsCOMPtr* aOutEndNode, + uint32_t* aOutStartOffset, uint32_t* aOutEndOffset, + SafeToInsertData aSafeToInsertData) const; + + [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult MoveCaretOutsideOfLink( + Element& aLinkElement, const EditorDOMPoint& aPointToPutCaret); + + // MOZ_KNOWN_LIVE because this is set only by the constructor which is + // marked as MOZ_CAN_RUN_SCRIPT and this is allocated only in the stack. + MOZ_KNOWN_LIVE HTMLEditor& mHTMLEditor; +}; + +class MOZ_STACK_CLASS + HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator final { + public: + nsresult Run(const Document& aDocument, const nsAString& aInputString, + const nsAString& aContextStr, const nsAString& aInfoStr, + nsCOMPtr* aOutFragNode, + nsCOMPtr* aOutStartNode, nsCOMPtr* aOutEndNode, + SafeToInsertData aSafeToInsertData) const; + + private: + nsresult CreateDocumentFragmentAndGetParentOfPastedHTMLInContext( + const Document& aDocument, const nsAString& aInputString, + const nsAString& aContextStr, SafeToInsertData aSafeToInsertData, + nsCOMPtr& aParentNodeOfPastedHTMLInContext, + RefPtr& aDocumentFragmentToInsert) const; + + static nsAtom* DetermineContextLocalNameForParsingPastedHTML( + const nsIContent* aParentContentOfPastedHTMLInContext); + + static bool FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie( + nsINode& aStart, nsCOMPtr& aResult); + + static bool IsInsertionCookie(const nsIContent& aContent); + + /** + * @param aDocumentFragmentForContext contains the merged result. + */ + static nsresult MergeAndPostProcessFragmentsForPastedHTMLAndContext( + DocumentFragment& aDocumentFragmentForPastedHTML, + DocumentFragment& aDocumentFragmentForContext, + nsIContent& aTargetContentOfContextForPastedHTML); + + /** + * @param aInfoStr as indicated by nsITransferable's kHTMLInfo. + */ + [[nodiscard]] static nsresult MoveStartAndEndAccordingToHTMLInfo( + const nsAString& aInfoStr, nsCOMPtr* aOutStartNode, + nsCOMPtr* aOutEndNode); + + static nsresult PostProcessFragmentForPastedHTMLWithoutContext( + DocumentFragment& aDocumentFragmentForPastedHTML); + + static nsresult PreProcessContextDocumentFragmentForMerging( + DocumentFragment& aDocumentFragmentForContext); + + static void RemoveHeadChildAndStealBodyChildsChildren(nsINode& aNode); + + /** + * This is designed for a helper class to remove disturbing nodes at inserting + * the HTML fragment into the DOM tree. This walks the children and if some + * elements do not have enough children, e.g., list elements not having + * another visible list elements nor list item elements, + * will be removed. + * + * @param aNode Should not be a node whose mutation may be observed by + * JS. + */ + static void RemoveIncompleteDescendantsFromInsertingFragment(nsINode& aNode); + + enum class NodesToRemove { + eAll, + eOnlyListItems /*!< List items are always block-level elements, hence such + whitespace-only nodes are always invisible. */ + }; + static nsresult + RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces( + nsIContent& aNode, NodesToRemove aNodesToRemove); +}; + +HTMLBRElement* +HTMLEditor::HTMLWithContextInserter::GetInvisibleBRElementAtPoint( + const EditorDOMPoint& aPointToInsert) const { + WSRunScanner wsRunScannerAtInsertionPoint(mHTMLEditor.ComputeEditingHost(), + aPointToInsert); + if (wsRunScannerAtInsertionPoint.EndsByInvisibleBRElement()) { + return wsRunScannerAtInsertionPoint.EndReasonBRElementPtr(); + } + return nullptr; +} + +EditorDOMPoint +HTMLEditor::HTMLWithContextInserter::GetNewCaretPointAfterInsertingHTML( + const EditorDOMPoint& aLastInsertedPoint) const { + EditorDOMPoint pointToPutCaret; + + // but don't cross tables + nsIContent* containerContent = nullptr; + if (!HTMLEditUtils::IsTable(aLastInsertedPoint.GetChild())) { + containerContent = HTMLEditUtils::GetLastLeafContent( + *aLastInsertedPoint.GetChild(), {LeafNodeType::OnlyEditableLeafNode}, + aLastInsertedPoint.GetChild()->GetAsElementOrParentElement()); + if (containerContent) { + Element* mostDistantInclusiveAncestorTableElement = nullptr; + for (Element* maybeTableElement = + containerContent->GetAsElementOrParentElement(); + maybeTableElement && + maybeTableElement != aLastInsertedPoint.GetChild(); + maybeTableElement = maybeTableElement->GetParentElement()) { + if (HTMLEditUtils::IsTable(maybeTableElement)) { + mostDistantInclusiveAncestorTableElement = maybeTableElement; + } + } + // If we're in table elements, we should put caret into the most ancestor + // table element. + if (mostDistantInclusiveAncestorTableElement) { + containerContent = mostDistantInclusiveAncestorTableElement; + } + } + } + // If we are not in table elements, we should put caret in the last inserted + // node. + if (!containerContent) { + containerContent = aLastInsertedPoint.GetChild(); + } + + // If the container is a text node or a container element except `` + // element, put caret a end of it. + if (containerContent->IsText() || + (HTMLEditUtils::IsContainerNode(*containerContent) && + !HTMLEditUtils::IsTable(containerContent))) { + pointToPutCaret.SetToEndOf(containerContent); + } + // Otherwise, i.e., it's an atomic element, `
` element or data node, + // put caret after it. + else { + pointToPutCaret.Set(containerContent); + DebugOnly advanced = pointToPutCaret.AdvanceOffset(); + NS_WARNING_ASSERTION(advanced, "Failed to advance offset from found node"); + } + + // Make sure we don't end up with selection collapsed after an invisible + // `
` element. + Element* editingHost = mHTMLEditor.ComputeEditingHost(); + WSRunScanner wsRunScannerAtCaret(editingHost, pointToPutCaret); + if (wsRunScannerAtCaret + .ScanPreviousVisibleNodeOrBlockBoundaryFrom(pointToPutCaret) + .ReachedInvisibleBRElement()) { + WSRunScanner wsRunScannerAtStartReason( + editingHost, + EditorDOMPoint(wsRunScannerAtCaret.GetStartReasonContent())); + WSScanResult backwardScanFromPointToCaretResult = + wsRunScannerAtStartReason.ScanPreviousVisibleNodeOrBlockBoundaryFrom( + pointToPutCaret); + if (backwardScanFromPointToCaretResult.InVisibleOrCollapsibleCharacters()) { + pointToPutCaret = + backwardScanFromPointToCaretResult.Point(); + } else if (backwardScanFromPointToCaretResult.ReachedSpecialContent()) { + // XXX In my understanding, this is odd. The end reason may not be + // same as the reached special content because the equality is + // guaranteed only when ReachedCurrentBlockBoundary() returns true. + // However, looks like that this code assumes that + // GetStartReasonContent() returns the content. + NS_ASSERTION(wsRunScannerAtStartReason.GetStartReasonContent() == + backwardScanFromPointToCaretResult.GetContent(), + "Start reason is not the reached special content"); + pointToPutCaret.SetAfter( + wsRunScannerAtStartReason.GetStartReasonContent()); + } + } + + return pointToPutCaret; +} + +nsresult HTMLEditor::InsertHTMLWithContextAsSubAction( + const nsAString& aInputString, const nsAString& aContextStr, + const nsAString& aInfoStr, const nsAString& aFlavor, + SafeToInsertData aSafeToInsertData, const EditorDOMPoint& aPointToInsert, + DeleteSelectedContent aDeleteSelectedContent, + InlineStylesAtInsertionPoint aInlineStylesAtInsertionPoint) { + MOZ_ASSERT(IsEditActionDataAvailable()); + + if (NS_WARN_IF(!mInitSucceeded)) { + return NS_ERROR_NOT_INITIALIZED; + } + + CommitComposition(); + + IgnoredErrorResult ignoredError; + AutoEditSubActionNotifier startToHandleEditSubAction( + *this, EditSubAction::ePasteHTMLContent, nsIEditor::eNext, ignoredError); + if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { + return ignoredError.StealNSResult(); + } + NS_WARNING_ASSERTION( + !ignoredError.Failed(), + "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); + ignoredError.SuppressException(); + + { + Result result = CanHandleHTMLEditSubAction(); + if (MOZ_UNLIKELY(result.isErr())) { + NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); + return result.unwrapErr(); + } + if (result.inspect().Canceled()) { + return NS_OK; + } + } + + // If we have a destination / target node, we want to insert there rather than + // in place of the selection. Ignore aDeleteSelectedContent here if + // aPointToInsert is not set since deletion will also occur later in + // HTMLWithContextInserter and will be collapsed around there; this block + // is intended to cover the various scenarios where we are dropping in an + // editor (and may want to delete the selection before collapsing the + // selection in the new destination) + if (aPointToInsert.IsSet()) { + nsresult rv = + PrepareToInsertContent(aPointToInsert, aDeleteSelectedContent); + if (NS_FAILED(rv)) { + NS_WARNING("EditorBase::PrepareToInsertContent() failed"); + return rv; + } + aDeleteSelectedContent = DeleteSelectedContent::No; + } + + HTMLWithContextInserter htmlWithContextInserter(*this); + + Result result = htmlWithContextInserter.Run( + aInputString, aContextStr, aInfoStr, aSafeToInsertData, + aInlineStylesAtInsertionPoint); + if (MOZ_UNLIKELY(result.isErr())) { + return result.unwrapErr(); + } + + // If nothing is inserted and delete selection is required, we need to + // delete selection right now. + if (result.inspect().Ignored() && + aDeleteSelectedContent == DeleteSelectedContent::Yes) { + nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip); + if (NS_FAILED(rv)) { + NS_WARNING( + "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed"); + return rv; + } + } + return NS_OK; +} + +Result HTMLEditor::HTMLWithContextInserter::Run( + const nsAString& aInputString, const nsAString& aContextStr, + const nsAString& aInfoStr, SafeToInsertData aSafeToInsertData, + InlineStylesAtInsertionPoint aInlineStylesAtInsertionPoint) { + MOZ_ASSERT(mHTMLEditor.IsEditActionDataAvailable()); + + // create a dom document fragment that represents the structure to paste + nsCOMPtr fragmentAsNode, streamStartParent, streamEndParent; + uint32_t streamStartOffset = 0, streamEndOffset = 0; + + nsresult rv = CreateDOMFragmentFromPaste( + aInputString, aContextStr, aInfoStr, address_of(fragmentAsNode), + address_of(streamStartParent), address_of(streamEndParent), + &streamStartOffset, &streamEndOffset, aSafeToInsertData); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::HTMLWithContextInserter::CreateDOMFragmentFromPaste() " + "failed"); + return Err(rv); + } + + // we need to recalculate various things based on potentially new offsets + // this is work to be completed at a later date (probably by jfrancis) + + AutoTArray, 64> arrayOfTopMostChildContents; + // If we have stream start point information, lets use it and end point. + // Otherwise, we should make a range all over the document fragment. + EditorRawDOMPoint streamStartPoint = + streamStartParent + ? EditorRawDOMPoint(streamStartParent, + AssertedCast(streamStartOffset)) + : EditorRawDOMPoint(fragmentAsNode, 0); + EditorRawDOMPoint streamEndPoint = + streamStartParent ? EditorRawDOMPoint(streamEndParent, streamEndOffset) + : EditorRawDOMPoint::AtEndOf(fragmentAsNode); + + Unused << streamStartPoint; + Unused << streamEndPoint; + + HTMLWithContextInserter::CollectTopMostChildContentsCompletelyInRange( + EditorRawDOMPoint(streamStartParent, + AssertedCast(streamStartOffset)), + EditorRawDOMPoint(streamEndParent, + AssertedCast(streamEndOffset)), + arrayOfTopMostChildContents); + + if (arrayOfTopMostChildContents.IsEmpty()) { + return EditActionResult::IgnoredResult(); // Nothing to insert. + } + + // Are there any table elements in the list? + // check for table cell selection mode + bool cellSelectionMode = + HTMLEditUtils::IsInTableCellSelectionMode(mHTMLEditor.SelectionRef()); + + if (cellSelectionMode) { + // do we have table content to paste? If so, we want to delete + // the selected table cells and replace with new table elements; + // but if not we want to delete _contents_ of cells and replace + // with non-table elements. Use cellSelectionMode bool to + // indicate results. + if (!HTMLEditUtils::IsAnyTableElement(arrayOfTopMostChildContents[0])) { + cellSelectionMode = false; + } + } + + if (!cellSelectionMode) { + rv = mHTMLEditor.DeleteSelectionAndPrepareToCreateNode(); + if (NS_FAILED(rv)) { + NS_WARNING("HTMLEditor::DeleteSelectionAndPrepareToCreateNode() failed"); + return Err(rv); + } + + if (aInlineStylesAtInsertionPoint == InlineStylesAtInsertionPoint::Clear) { + // pasting does not inherit local inline styles + Result pointToPutCaretOrError = + mHTMLEditor.ClearStyleAt( + EditorDOMPoint(mHTMLEditor.SelectionRef().AnchorRef()), + EditorInlineStyle::RemoveAllStyles(), SpecifiedStyle::Preserve); + if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { + NS_WARNING("HTMLEditor::ClearStyleAt() failed"); + return pointToPutCaretOrError.propagateErr(); + } + if (pointToPutCaretOrError.inspect().IsSetAndValid()) { + nsresult rv = + mHTMLEditor.CollapseSelectionTo(pointToPutCaretOrError.unwrap()); + if (NS_FAILED(rv)) { + NS_WARNING("EditorBase::CollapseSelectionTo() failed"); + return Err(rv); + } + } + } + } else { + // Delete whole cells: we will replace with new table content. + + // Braces for artificial block to scope AutoSelectionRestorer. + // Save current selection since DeleteTableCellWithTransaction() perturbs + // it. + { + AutoSelectionRestorer restoreSelectionLater(mHTMLEditor); + rv = mHTMLEditor.DeleteTableCellWithTransaction(1); + if (NS_FAILED(rv)) { + NS_WARNING("HTMLEditor::DeleteTableCellWithTransaction(1) failed"); + return Err(rv); + } + } + // collapse selection to beginning of deleted table content + IgnoredErrorResult ignoredError; + mHTMLEditor.SelectionRef().CollapseToStart(ignoredError); + NS_WARNING_ASSERTION(!ignoredError.Failed(), + "Selection::Collapse() failed, but ignored"); + } + + { + Result result = + mHTMLEditor.CanHandleHTMLEditSubAction(); + if (MOZ_UNLIKELY(result.isErr())) { + NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); + return result; + } + if (result.inspect().Canceled()) { + return result; + } + } + + mHTMLEditor.UndefineCaretBidiLevel(); + + rv = mHTMLEditor.EnsureNoPaddingBRElementForEmptyEditor(); + if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { + return Err(NS_ERROR_EDITOR_DESTROYED); + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " + "failed, but ignored"); + + if (NS_SUCCEEDED(rv) && mHTMLEditor.SelectionRef().IsCollapsed()) { + nsresult rv = mHTMLEditor.EnsureCaretNotAfterInvisibleBRElement(); + if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { + return Err(NS_ERROR_EDITOR_DESTROYED); + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " + "failed, but ignored"); + if (NS_SUCCEEDED(rv)) { + nsresult rv = mHTMLEditor.PrepareInlineStylesForCaret(); + if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { + return Err(NS_ERROR_EDITOR_DESTROYED); + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); + } + } + + Element* editingHost = + mHTMLEditor.ComputeEditingHost(HTMLEditor::LimitInBodyElement::No); + if (NS_WARN_IF(!editingHost)) { + return Err(NS_ERROR_FAILURE); + } + + // Adjust position based on the first node we are going to insert. + EditorDOMPoint pointToInsert = + HTMLEditUtils::GetBetterInsertionPointFor( + arrayOfTopMostChildContents[0], + mHTMLEditor.GetFirstSelectionStartPoint(), + *editingHost); + if (!pointToInsert.IsSet()) { + NS_WARNING("HTMLEditor::GetBetterInsertionPointFor() failed"); + return Err(NS_ERROR_FAILURE); + } + + // Remove invisible `
` element at the point because if there is a `
` + // element at end of what we paste, it will make the existing invisible + // `
` element visible. + if (HTMLBRElement* invisibleBRElement = + GetInvisibleBRElementAtPoint(pointToInsert)) { + AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert); + nsresult rv = mHTMLEditor.DeleteNodeWithTransaction( + MOZ_KnownLive(*invisibleBRElement)); + if (NS_FAILED(rv)) { + NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed."); + return Err(rv); + } + } + + const bool insertionPointWasInLink = + !!HTMLEditor::GetLinkElement(pointToInsert.GetContainer()); + + if (pointToInsert.IsInTextNode()) { + Result splitNodeResult = + mHTMLEditor.SplitNodeDeepWithTransaction( + MOZ_KnownLive(*pointToInsert.ContainerAs()), + pointToInsert, SplitAtEdges::eAllowToCreateEmptyContainer); + if (MOZ_UNLIKELY(splitNodeResult.isErr())) { + NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed"); + return splitNodeResult.propagateErr(); + } + nsresult rv = splitNodeResult.inspect().SuggestCaretPointTo( + mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, + SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); + if (NS_FAILED(rv)) { + NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed"); + return Err(rv); + } + pointToInsert = splitNodeResult.inspect().AtSplitPoint(); + if (MOZ_UNLIKELY(!pointToInsert.IsSet())) { + NS_WARNING( + "HTMLEditor::SplitNodeDeepWithTransaction() didn't return split " + "point"); + return Err(NS_ERROR_FAILURE); + } + } + + { // Block only for AutoHTMLFragmentBoundariesFixer to hide it from the + // following code. Note that it may modify arrayOfTopMostChildContents. + AutoHTMLFragmentBoundariesFixer fixPiecesOfTablesAndLists( + arrayOfTopMostChildContents); + } + + MOZ_ASSERT(pointToInsert.GetContainer()->GetChildAt_Deprecated( + pointToInsert.Offset()) == pointToInsert.GetChild()); + + Result lastInsertedPoint = InsertContents( + pointToInsert, arrayOfTopMostChildContents, fragmentAsNode); + if (lastInsertedPoint.isErr()) { + NS_WARNING("HTMLWithContextInserter::InsertContents() failed."); + return lastInsertedPoint.propagateErr(); + } + + mHTMLEditor.TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements = + false; + + if (MOZ_UNLIKELY(!lastInsertedPoint.inspect().IsInComposedDoc())) { + return EditActionResult::HandledResult(); + } + + const EditorDOMPoint pointToPutCaret = + GetNewCaretPointAfterInsertingHTML(lastInsertedPoint.inspect()); + // Now collapse the selection to the end of what we just inserted. + rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret); + if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { + NS_WARNING( + "EditorBase::CollapseSelectionTo() caused destroying the editor"); + return Err(NS_ERROR_EDITOR_DESTROYED); + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::CollapseSelectionTo() failed, but ignored"); + + // If we didn't start from an `` element, we should not keep + // caret in the link to make users type something outside the link. + if (insertionPointWasInLink) { + return EditActionResult::HandledResult(); + } + RefPtr linkElement = GetLinkElement(pointToPutCaret.GetContainer()); + + if (!linkElement) { + return EditActionResult::HandledResult(); + } + + rv = MoveCaretOutsideOfLink(*linkElement, pointToPutCaret); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::HTMLWithContextInserter::MoveCaretOutsideOfLink " + "failed."); + return Err(rv); + } + + return EditActionResult::HandledResult(); +} + +Result +HTMLEditor::HTMLWithContextInserter::InsertContents( + const EditorDOMPoint& aPointToInsert, + nsTArray>& aArrayOfTopMostChildContents, + const nsINode* aFragmentAsNode) { + MOZ_ASSERT(aPointToInsert.IsSetAndValidInComposedDoc()); + + EditorDOMPoint pointToInsert{aPointToInsert}; + + // Loop over the node list and paste the nodes: + const RefPtr maybeNonEditableBlockElement = + pointToInsert.IsInContentNode() + ? HTMLEditUtils::GetInclusiveAncestorElement( + *pointToInsert.ContainerAs(), + HTMLEditUtils::ClosestBlockElement) + : nullptr; + + EditorDOMPoint lastInsertedPoint; + nsCOMPtr insertedContextParentContent; + for (OwningNonNull& content : aArrayOfTopMostChildContents) { + if (NS_WARN_IF(content == aFragmentAsNode) || + NS_WARN_IF(content->IsHTMLElement(nsGkAtoms::body))) { + return Err(NS_ERROR_FAILURE); + } + + if (insertedContextParentContent) { + // If we had to insert something higher up in the paste hierarchy, + // we want to skip any further paste nodes that descend from that. + // Else we will paste twice. + // XXX This check may be really expensive. Cannot we check whether + // the node's `ownerDocument` is the `aFragmentAsNode` or not? + // XXX If content was moved to outside of insertedContextParentContent + // by mutation event listeners, we will anyway duplicate it. + if (EditorUtils::IsDescendantOf(*content, + *insertedContextParentContent)) { + continue; + } + } + + // If a `
` or `` element on the clipboard, and pasting it into + // a `
` or `` element, insert only the appropriate children + // instead. + bool inserted = false; + if (HTMLEditUtils::IsTableRow(content) && + HTMLEditUtils::IsTableRow(pointToInsert.GetContainer()) && + (HTMLEditUtils::IsTable(content) || + HTMLEditUtils::IsTable(pointToInsert.GetContainer()))) { + // Move children of current node to the insertion point. + AutoTArray, 24> children; + HTMLEditUtils::CollectAllChildren(*content, children); + EditorDOMPoint pointToPutCaret; + for (const OwningNonNull& child : children) { + // MOZ_KnownLive(child) because of bug 1622253 + Result moveChildResult = + mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction( + MOZ_KnownLive(child), pointToInsert, + SplitAtEdges::eDoNotCreateEmptyContainer); + if (MOZ_UNLIKELY(moveChildResult.isErr())) { + // If moving node is moved to different place, we should ignore + // this result and keep trying to insert next content node to same + // position. + if (moveChildResult.inspectErr() == + NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) { + inserted = true; + continue; // the inner `for` loop + } + NS_WARNING( + "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(" + "SplitAtEdges::eDoNotCreateEmptyContainer) failed, maybe " + "ignored"); + break; // from the inner `for` loop + } + if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) { + continue; + } + inserted = true; + lastInsertedPoint.Set(child); + pointToInsert = lastInsertedPoint.NextPoint(); + MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); + CreateContentResult unwrappedMoveChildResult = moveChildResult.unwrap(); + unwrappedMoveChildResult.MoveCaretPointTo( + pointToPutCaret, mHTMLEditor, + {SuggestCaret::OnlyIfHasSuggestion, + SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); + } // end of the inner `for` loop + + if (pointToPutCaret.IsSet()) { + nsresult rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret); + if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { + NS_WARNING( + "EditorBase::CollapseSelectionTo() caused destroying the editor"); + return Err(NS_ERROR_EDITOR_DESTROYED); + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "EditorBase::CollapseSelectionTo() failed, but ignored"); + } + } + // If a list element on the clipboard, and pasting it into a list or + // list item element, insert the appropriate children instead. I.e., + // merge the list elements instead of pasting as a sublist. + else if (HTMLEditUtils::IsAnyListElement(content) && + (HTMLEditUtils::IsAnyListElement(pointToInsert.GetContainer()) || + HTMLEditUtils::IsListItem(pointToInsert.GetContainer()))) { + AutoTArray, 24> children; + HTMLEditUtils::CollectAllChildren(*content, children); + EditorDOMPoint pointToPutCaret; + for (const OwningNonNull& child : children) { + if (HTMLEditUtils::IsListItem(child) || + HTMLEditUtils::IsAnyListElement(child)) { + // If we're pasting into empty list item, we should remove it + // and past current node into the parent list directly. + // XXX This creates invalid structure if current list item element + // is not proper child of the parent element, or current node + // is a list element. + if (HTMLEditUtils::IsListItem(pointToInsert.GetContainer()) && + HTMLEditUtils::IsEmptyNode(*pointToInsert.GetContainer())) { + NS_WARNING_ASSERTION(pointToInsert.GetContainerParent(), + "Insertion point is out of the DOM tree"); + if (pointToInsert.GetContainerParent()) { + pointToInsert.Set(pointToInsert.GetContainer()); + MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); + AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert); + nsresult rv = mHTMLEditor.DeleteNodeWithTransaction( + MOZ_KnownLive(*pointToInsert.GetChild())); + if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { + NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); + return Err(NS_ERROR_EDITOR_DESTROYED); + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::DeleteNodeWithTransaction() " + "failed, but ignored"); + } + } + // MOZ_KnownLive(child) because of bug 1622253 + Result moveChildResult = + mHTMLEditor + .InsertNodeIntoProperAncestorWithTransaction( + MOZ_KnownLive(child), pointToInsert, + SplitAtEdges::eDoNotCreateEmptyContainer); + if (MOZ_UNLIKELY(moveChildResult.isErr())) { + // If moving node is moved to different place, we should ignore + // this result and keep trying to insert next content node to + // same position. + if (moveChildResult.inspectErr() == + NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) { + inserted = true; + continue; // the inner `for` loop + } + if (NS_WARN_IF(moveChildResult.inspectErr() == + NS_ERROR_EDITOR_DESTROYED)) { + return Err(NS_ERROR_EDITOR_DESTROYED); + } + NS_WARNING( + "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(" + "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but maybe " + "ignored"); + break; // from the inner `for` loop + } + if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) { + continue; + } + inserted = true; + lastInsertedPoint.Set(child); + pointToInsert = lastInsertedPoint.NextPoint(); + MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); + CreateContentResult unwrappedMoveChildResult = + moveChildResult.unwrap(); + unwrappedMoveChildResult.MoveCaretPointTo( + pointToPutCaret, mHTMLEditor, + {SuggestCaret::OnlyIfHasSuggestion, + SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); + } + // If the child of current node is not list item nor list element, + // we should remove it from the DOM tree. + else if (HTMLEditUtils::IsRemovableNode(child)) { + AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert); + IgnoredErrorResult ignoredError; + content->RemoveChild(child, ignoredError); + if (MOZ_UNLIKELY(mHTMLEditor.Destroyed())) { + NS_WARNING( + "nsIContent::RemoveChild() caused destroying the editor"); + return Err(NS_ERROR_EDITOR_DESTROYED); + } + NS_WARNING_ASSERTION(!ignoredError.Failed(), + "nsINode::RemoveChild() failed, but ignored"); + } else { + NS_WARNING( + "Failed to delete the first child of a list element because the " + "list element non-editable"); + break; // from the inner `for` loop + } + } // end of the inner `for` loop + + if (MOZ_UNLIKELY(mHTMLEditor.Destroyed())) { + NS_WARNING("The editor has been destroyed"); + return Err(NS_ERROR_EDITOR_DESTROYED); + } + if (pointToPutCaret.IsSet()) { + nsresult rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret); + if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { + NS_WARNING( + "EditorBase::CollapseSelectionTo() caused destroying the editor"); + return Err(NS_ERROR_EDITOR_DESTROYED); + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "EditorBase::CollapseSelectionTo() failed, but ignored"); + } + } + // If pasting into a `
` element and current node is a `
` element,
+    // move only its children.
+    else if (HTMLEditUtils::IsPre(maybeNonEditableBlockElement) &&
+             HTMLEditUtils::IsPre(content)) {
+      // Check for pre's going into pre's.
+      AutoTArray, 24> children;
+      HTMLEditUtils::CollectAllChildren(*content, children);
+      EditorDOMPoint pointToPutCaret;
+      for (const OwningNonNull& child : children) {
+        // MOZ_KnownLive(child) because of bug 1622253
+        Result moveChildResult =
+            mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction(
+                MOZ_KnownLive(child), pointToInsert,
+                SplitAtEdges::eDoNotCreateEmptyContainer);
+        if (MOZ_UNLIKELY(moveChildResult.isErr())) {
+          // If moving node is moved to different place, we should ignore
+          // this result and keep trying to insert next content node there.
+          if (moveChildResult.inspectErr() ==
+              NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) {
+            inserted = true;
+            continue;  // the inner `for` loop
+          }
+          if (NS_WARN_IF(moveChildResult.inspectErr() ==
+                         NS_ERROR_EDITOR_DESTROYED)) {
+            return moveChildResult.propagateErr();
+          }
+          NS_WARNING(
+              "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
+              "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but maybe "
+              "ignored");
+          break;  // from the inner `for` loop
+        }
+        if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) {
+          continue;
+        }
+        CreateContentResult unwrappedMoveChildResult = moveChildResult.unwrap();
+        inserted = true;
+        lastInsertedPoint.Set(child);
+        pointToInsert = lastInsertedPoint.NextPoint();
+        MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
+        unwrappedMoveChildResult.MoveCaretPointTo(
+            pointToPutCaret, mHTMLEditor,
+            {SuggestCaret::OnlyIfHasSuggestion,
+             SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
+      }  // end of the inner `for` loop
+
+      if (pointToPutCaret.IsSet()) {
+        nsresult rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret);
+        if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
+          NS_WARNING(
+              "EditorBase::CollapseSelectionTo() caused destroying the editor");
+          return Err(NS_ERROR_EDITOR_DESTROYED);
+        }
+        NS_WARNING_ASSERTION(
+            NS_SUCCEEDED(rv),
+            "EditorBase::CollapseSelectionTo() failed, but ignored");
+      }
+    }
+
+    // TODO: For making the above code clearer, we should move this fallback
+    //       path into a lambda and call it in each if/else-if block.
+    // If we haven't inserted current node nor its children, move current node
+    // to the insertion point.
+    if (!inserted) {
+      // MOZ_KnownLive(content) because 'aArrayOfTopMostChildContents' is
+      // guaranteed to keep it alive.
+      Result moveContentResult =
+          mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction(
+              MOZ_KnownLive(content), pointToInsert,
+              SplitAtEdges::eDoNotCreateEmptyContainer);
+      if (MOZ_LIKELY(moveContentResult.isOk())) {
+        if (MOZ_UNLIKELY(!moveContentResult.inspect().Handled())) {
+          continue;
+        }
+        lastInsertedPoint.Set(content);
+        pointToInsert = lastInsertedPoint;
+        MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
+        nsresult rv = moveContentResult.inspect().SuggestCaretPointTo(
+            mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
+                          SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
+                          SuggestCaret::AndIgnoreTrivialError});
+        if (NS_FAILED(rv)) {
+          NS_WARNING("CreateContentResult::SuggestCaretPointTo() failed");
+          return Err(rv);
+        }
+        NS_WARNING_ASSERTION(
+            rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
+            "CreateContentResult::SuggestCaretPointTo() failed, but ignored");
+      } else if (moveContentResult.inspectErr() ==
+                 NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) {
+        // Moving node is moved to different place, we should keep trying to
+        // insert the next content to same position.
+      } else {
+        NS_WARNING(
+            "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
+            "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but ignored");
+        // Assume failure means no legal parent in the document hierarchy,
+        // try again with the parent of content in the paste hierarchy.
+        // FYI: We cannot use `InclusiveAncestorOfType` here because of
+        //      calling `InsertNodeIntoProperAncestorWithTransaction()`.
+        for (nsCOMPtr childContent = content; childContent;
+             childContent = childContent->GetParent()) {
+          if (NS_WARN_IF(!childContent->GetParent()) ||
+              NS_WARN_IF(
+                  childContent->GetParent()->IsHTMLElement(nsGkAtoms::body))) {
+            break;  // for the inner `for` loop
+          }
+          const OwningNonNull oldParentContent =
+              *childContent->GetParent();
+          Result moveParentResult =
+              mHTMLEditor
+                  .InsertNodeIntoProperAncestorWithTransaction(
+                      oldParentContent, pointToInsert,
+                      SplitAtEdges::eDoNotCreateEmptyContainer);
+          if (MOZ_UNLIKELY(moveParentResult.isErr())) {
+            // Moving node is moved to different place, we should keep trying to
+            // insert the next content to same position.
+            if (moveParentResult.inspectErr() ==
+                NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) {
+              break;  // from the inner `for` loop
+            }
+            if (NS_WARN_IF(moveParentResult.inspectErr() ==
+                           NS_ERROR_EDITOR_DESTROYED)) {
+              return Err(NS_ERROR_EDITOR_DESTROYED);
+            }
+            NS_WARNING(
+                "HTMLEditor::InsertNodeInToProperAncestorWithTransaction("
+                "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but "
+                "ignored");
+            continue;  // the inner `for` loop
+          }
+          if (MOZ_UNLIKELY(!moveParentResult.inspect().Handled())) {
+            continue;
+          }
+          insertedContextParentContent = oldParentContent;
+          pointToInsert.Set(oldParentContent);
+          MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
+          nsresult rv = moveParentResult.inspect().SuggestCaretPointTo(
+              mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
+                            SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
+                            SuggestCaret::AndIgnoreTrivialError});
+          if (NS_FAILED(rv)) {
+            NS_WARNING("CreateContentResult::SuggestCaretPointTo() failed");
+            return Err(rv);
+          }
+          NS_WARNING_ASSERTION(
+              rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
+              "CreateContentResult::SuggestCaretPointTo() failed, but ignored");
+          break;  // from the inner `for` loop
+        }         // end of the inner `for` loop
+      }
+    }
+    if (lastInsertedPoint.IsSet()) {
+      if (MOZ_UNLIKELY(lastInsertedPoint.GetContainer() !=
+                       lastInsertedPoint.GetChild()->GetParentNode())) {
+        NS_WARNING(
+            "HTMLEditor::InsertHTMLWithContextAsSubAction() got lost insertion "
+            "point");
+        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
+      }
+      pointToInsert = lastInsertedPoint.NextPoint();
+      MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
+    }
+  }  // end of the `for` loop
+
+  return lastInsertedPoint;
+}
+
+nsresult HTMLEditor::HTMLWithContextInserter::MoveCaretOutsideOfLink(
+    Element& aLinkElement, const EditorDOMPoint& aPointToPutCaret) {
+  MOZ_ASSERT(HTMLEditUtils::IsLink(&aLinkElement));
+
+  // The reason why do that instead of just moving caret after it is, the
+  // link might have ended in an invisible `
` element. If so, the code + // above just placed selection inside that. So we need to split it instead. + // XXX Sounds like that it's not really expensive comparing with the reason + // to use SplitNodeDeepWithTransaction() here. + Result splitLinkResult = + mHTMLEditor.SplitNodeDeepWithTransaction( + aLinkElement, aPointToPutCaret, + SplitAtEdges::eDoNotCreateEmptyContainer); + if (MOZ_UNLIKELY(splitLinkResult.isErr())) { + if (splitLinkResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING( + "HTMLEditor::SplitNodeDeepWithTransaction() failed, but ignored"); + } + + if (nsIContent* previousContentOfSplitPoint = + splitLinkResult.inspect().GetPreviousContent()) { + splitLinkResult.inspect().IgnoreCaretPointSuggestion(); + nsresult rv = mHTMLEditor.CollapseSelectionTo( + EditorRawDOMPoint::After(*previousContentOfSplitPoint)); + if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "EditorBase::CollapseSelectionTo() failed, but ignored"); + return NS_OK; + } + + nsresult rv = splitLinkResult.inspect().SuggestCaretPointTo( + mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, + SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "SplitNodeResult::SuggestCaretPointTo() failed"); + return rv; +} + +// static +Element* HTMLEditor::GetLinkElement(nsINode* aNode) { + if (NS_WARN_IF(!aNode)) { + return nullptr; + } + nsINode* node = aNode; + while (node) { + if (HTMLEditUtils::IsLink(node)) { + return node->AsElement(); + } + node = node->GetParentNode(); + } + return nullptr; +} + +// static +nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: + RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces( + nsIContent& aNode, NodesToRemove aNodesToRemove) { + if (aNode.TextIsOnlyWhitespace()) { + nsCOMPtr parent = aNode.GetParentNode(); + // TODO: presumably, if the parent is a `
` element, the node
+    // shouldn't be removed.
+    if (parent) {
+      if (aNodesToRemove == NodesToRemove::eAll ||
+          HTMLEditUtils::IsAnyListElement(parent)) {
+        ErrorResult error;
+        parent->RemoveChild(aNode, error);
+        NS_WARNING_ASSERTION(!error.Failed(), "nsINode::RemoveChild() failed");
+        return error.StealNSResult();
+      }
+      return NS_OK;
+    }
+  }
+
+  if (!aNode.IsHTMLElement(nsGkAtoms::pre)) {
+    nsCOMPtr child = aNode.GetLastChild();
+    while (child) {
+      nsCOMPtr previous = child->GetPreviousSibling();
+      nsresult rv = FragmentFromPasteCreator::
+          RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces(
+              *child, aNodesToRemove);
+      if (NS_FAILED(rv)) {
+        NS_WARNING(
+            "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::"
+            "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces"
+            "() "
+            "failed");
+        return rv;
+      }
+      child = std::move(previous);
+    }
+  }
+  return NS_OK;
+}
+
+class MOZ_STACK_CLASS HTMLEditor::HTMLTransferablePreparer {
+ public:
+  HTMLTransferablePreparer(const HTMLEditor& aHTMLEditor,
+                           nsITransferable** aTransferable);
+
+  nsresult Run();
+
+ private:
+  void AddDataFlavorsInBestOrder(nsITransferable& aTransferable) const;
+
+  const HTMLEditor& mHTMLEditor;
+  nsITransferable** mTransferable;
+};
+
+HTMLEditor::HTMLTransferablePreparer::HTMLTransferablePreparer(
+    const HTMLEditor& aHTMLEditor, nsITransferable** aTransferable)
+    : mHTMLEditor{aHTMLEditor}, mTransferable{aTransferable} {
+  MOZ_ASSERT(mTransferable);
+  MOZ_ASSERT(!*mTransferable);
+}
+
+nsresult HTMLEditor::PrepareHTMLTransferable(
+    nsITransferable** aTransferable) const {
+  HTMLTransferablePreparer htmlTransferablePreparer{*this, aTransferable};
+  return htmlTransferablePreparer.Run();
+}
+
+nsresult HTMLEditor::HTMLTransferablePreparer::Run() {
+  // Create generic Transferable for getting the data
+  nsresult rv;
+  RefPtr transferable =
+      do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("do_CreateInstance() failed to create nsITransferable instance");
+    return rv;
+  }
+
+  if (!transferable) {
+    NS_WARNING("do_CreateInstance() returned nullptr, but ignored");
+    return NS_OK;
+  }
+
+  // Get the nsITransferable interface for getting the data from the clipboard
+  RefPtr destdoc = mHTMLEditor.GetDocument();
+  nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
+  DebugOnly rvIgnored = transferable->Init(loadContext);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                       "nsITransferable::Init() failed, but ignored");
+
+  // See `HTMLEditor::InsertFromTransferableAtSelection`.
+  AddDataFlavorsInBestOrder(*transferable);
+
+  transferable.forget(mTransferable);
+
+  return NS_OK;
+}
+
+void HTMLEditor::HTMLTransferablePreparer::AddDataFlavorsInBestOrder(
+    nsITransferable& aTransferable) const {
+  // Create the desired DataFlavor for the type of data
+  // we want to get out of the transferable
+  // This should only happen in html editors, not plaintext
+  if (!mHTMLEditor.IsInPlaintextMode()) {
+    DebugOnly rvIgnored =
+        aTransferable.AddDataFlavor(kNativeHTMLMime);
+    NS_WARNING_ASSERTION(
+        NS_SUCCEEDED(rvIgnored),
+        "nsITransferable::AddDataFlavor(kNativeHTMLMime) failed, but ignored");
+    rvIgnored = aTransferable.AddDataFlavor(kHTMLMime);
+    NS_WARNING_ASSERTION(
+        NS_SUCCEEDED(rvIgnored),
+        "nsITransferable::AddDataFlavor(kHTMLMime) failed, but ignored");
+    rvIgnored = aTransferable.AddDataFlavor(kFileMime);
+    NS_WARNING_ASSERTION(
+        NS_SUCCEEDED(rvIgnored),
+        "nsITransferable::AddDataFlavor(kFileMime) failed, but ignored");
+
+    switch (Preferences::GetInt("clipboard.paste_image_type", 1)) {
+      case 0:  // prefer JPEG over PNG over GIF encoding
+        rvIgnored = aTransferable.AddDataFlavor(kJPEGImageMime);
+        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                             "nsITransferable::AddDataFlavor(kJPEGImageMime) "
+                             "failed, but ignored");
+        rvIgnored = aTransferable.AddDataFlavor(kJPGImageMime);
+        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                             "nsITransferable::AddDataFlavor(kJPGImageMime) "
+                             "failed, but ignored");
+        rvIgnored = aTransferable.AddDataFlavor(kPNGImageMime);
+        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                             "nsITransferable::AddDataFlavor(kPNGImageMime) "
+                             "failed, but ignored");
+        rvIgnored = aTransferable.AddDataFlavor(kGIFImageMime);
+        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                             "nsITransferable::AddDataFlavor(kGIFImageMime) "
+                             "failed, but ignored");
+        break;
+      case 1:  // prefer PNG over JPEG over GIF encoding (default)
+      default:
+        rvIgnored = aTransferable.AddDataFlavor(kPNGImageMime);
+        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                             "nsITransferable::AddDataFlavor(kPNGImageMime) "
+                             "failed, but ignored");
+        rvIgnored = aTransferable.AddDataFlavor(kJPEGImageMime);
+        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                             "nsITransferable::AddDataFlavor(kJPEGImageMime) "
+                             "failed, but ignored");
+        rvIgnored = aTransferable.AddDataFlavor(kJPGImageMime);
+        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                             "nsITransferable::AddDataFlavor(kJPGImageMime) "
+                             "failed, but ignored");
+        rvIgnored = aTransferable.AddDataFlavor(kGIFImageMime);
+        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                             "nsITransferable::AddDataFlavor(kGIFImageMime) "
+                             "failed, but ignored");
+        break;
+      case 2:  // prefer GIF over JPEG over PNG encoding
+        rvIgnored = aTransferable.AddDataFlavor(kGIFImageMime);
+        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                             "nsITransferable::AddDataFlavor(kGIFImageMime) "
+                             "failed, but ignored");
+        rvIgnored = aTransferable.AddDataFlavor(kJPEGImageMime);
+        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                             "nsITransferable::AddDataFlavor(kJPEGImageMime) "
+                             "failed, but ignored");
+        rvIgnored = aTransferable.AddDataFlavor(kJPGImageMime);
+        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                             "nsITransferable::AddDataFlavor(kJPGImageMime) "
+                             "failed, but ignored");
+        rvIgnored = aTransferable.AddDataFlavor(kPNGImageMime);
+        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                             "nsITransferable::AddDataFlavor(kPNGImageMime) "
+                             "failed, but ignored");
+        break;
+    }
+  }
+  DebugOnly rvIgnored = aTransferable.AddDataFlavor(kTextMime);
+  NS_WARNING_ASSERTION(
+      NS_SUCCEEDED(rvIgnored),
+      "nsITransferable::AddDataFlavor(kTextMime) failed, but ignored");
+  rvIgnored = aTransferable.AddDataFlavor(kMozTextInternal);
+  NS_WARNING_ASSERTION(
+      NS_SUCCEEDED(rvIgnored),
+      "nsITransferable::AddDataFlavor(kMozTextInternal) failed, but ignored");
+}
+
+bool FindIntegerAfterString(const char* aLeadingString, const nsCString& aCStr,
+                            int32_t& foundNumber) {
+  // first obtain offsets from cfhtml str
+  int32_t numFront = aCStr.Find(aLeadingString);
+  if (numFront == -1) {
+    return false;
+  }
+  numFront += strlen(aLeadingString);
+
+  int32_t numBack = aCStr.FindCharInSet(CRLF, numFront);
+  if (numBack == -1) {
+    return false;
+  }
+
+  nsAutoCString numStr(Substring(aCStr, numFront, numBack - numFront));
+  nsresult errorCode;
+  foundNumber = numStr.ToInteger(&errorCode);
+  return true;
+}
+
+void RemoveFragComments(nsCString& aStr) {
+  // remove the StartFragment/EndFragment comments from the str, if present
+  int32_t startCommentIndx = aStr.Find("", startCommentIndx);
+    if (startCommentEnd > startCommentIndx) {
+      aStr.Cut(startCommentIndx, (startCommentEnd + 3) - startCommentIndx);
+    }
+  }
+  int32_t endCommentIndx = aStr.Find("", endCommentIndx);
+    if (endCommentEnd > endCommentIndx) {
+      aStr.Cut(endCommentIndx, (endCommentEnd + 3) - endCommentIndx);
+    }
+  }
+}
+
+nsresult HTMLEditor::ParseCFHTML(const nsCString& aCfhtml,
+                                 char16_t** aStuffToPaste,
+                                 char16_t** aCfcontext) {
+  // First obtain offsets from cfhtml str.
+  int32_t startHTML, endHTML, startFragment, endFragment;
+  if (!FindIntegerAfterString("StartHTML:", aCfhtml, startHTML) ||
+      startHTML < -1) {
+    return NS_ERROR_FAILURE;
+  }
+  if (!FindIntegerAfterString("EndHTML:", aCfhtml, endHTML) || endHTML < -1) {
+    return NS_ERROR_FAILURE;
+  }
+  if (!FindIntegerAfterString("StartFragment:", aCfhtml, startFragment) ||
+      startFragment < 0) {
+    return NS_ERROR_FAILURE;
+  }
+  if (!FindIntegerAfterString("EndFragment:", aCfhtml, endFragment) ||
+      startFragment < 0) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // The StartHTML and EndHTML markers are allowed to be -1 to include
+  // everything.
+  //   See Reference: MSDN doc entitled "HTML Clipboard Format"
+  //   http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
+  if (startHTML == -1) {
+    startHTML = aCfhtml.Find("");
+    if (startHTML == -1) {
+      return NS_OK;
+    }
+  }
+  if (endHTML == -1) {
+    const char endFragmentMarker[] = "";
+    endHTML = aCfhtml.Find(endFragmentMarker);
+    if (endHTML == -1) {
+      return NS_OK;
+    }
+    endHTML += ArrayLength(endFragmentMarker) - 1;
+  }
+
+  // create context string
+  nsAutoCString contextUTF8(
+      Substring(aCfhtml, startHTML, startFragment - startHTML) +
+      ""_ns +
+      Substring(aCfhtml, endFragment, endHTML - endFragment));
+
+  // validate startFragment
+  // make sure it's not in the middle of a HTML tag
+  // see bug #228879 for more details
+  int32_t curPos = startFragment;
+  while (curPos > startHTML) {
+    if (aCfhtml[curPos] == '>') {
+      // working backwards, the first thing we see is the end of a tag
+      // so StartFragment is good, so do nothing.
+      break;
+    }
+    if (aCfhtml[curPos] == '<') {
+      // if we are at the start, then we want to see the '<'
+      if (curPos != startFragment) {
+        // working backwards, the first thing we see is the start of a tag
+        // so StartFragment is bad, so we need to update it.
+        NS_ERROR(
+            "StartFragment byte count in the clipboard looks bad, see bug "
+            "#228879");
+        startFragment = curPos - 1;
+      }
+      break;
+    }
+    curPos--;
+  }
+
+  // create fragment string
+  nsAutoCString fragmentUTF8(
+      Substring(aCfhtml, startFragment, endFragment - startFragment));
+
+  // remove the StartFragment/EndFragment comments from the fragment, if present
+  RemoveFragComments(fragmentUTF8);
+
+  // remove the StartFragment/EndFragment comments from the context, if present
+  RemoveFragComments(contextUTF8);
+
+  // convert both strings to usc2
+  const nsString& fragUcs2Str = NS_ConvertUTF8toUTF16(fragmentUTF8);
+  const nsString& cntxtUcs2Str = NS_ConvertUTF8toUTF16(contextUTF8);
+
+  // translate platform linebreaks for fragment
+  int32_t oldLengthInChars =
+      fragUcs2Str.Length() + 1;  // +1 to include null terminator
+  int32_t newLengthInChars = 0;
+  *aStuffToPaste = nsLinebreakConverter::ConvertUnicharLineBreaks(
+      fragUcs2Str.get(), nsLinebreakConverter::eLinebreakAny,
+      nsLinebreakConverter::eLinebreakContent, oldLengthInChars,
+      &newLengthInChars);
+  if (!*aStuffToPaste) {
+    NS_WARNING("nsLinebreakConverter::ConvertUnicharLineBreaks() failed");
+    return NS_ERROR_FAILURE;
+  }
+
+  // translate platform linebreaks for context
+  oldLengthInChars =
+      cntxtUcs2Str.Length() + 1;  // +1 to include null terminator
+  newLengthInChars = 0;
+  *aCfcontext = nsLinebreakConverter::ConvertUnicharLineBreaks(
+      cntxtUcs2Str.get(), nsLinebreakConverter::eLinebreakAny,
+      nsLinebreakConverter::eLinebreakContent, oldLengthInChars,
+      &newLengthInChars);
+  // it's ok for context to be empty.  frag might be whole doc and contain all
+  // its context.
+
+  // we're done!
+  return NS_OK;
+}
+
+static nsresult ImgFromData(const nsACString& aType, const nsACString& aData,
+                            nsString& aOutput) {
+  aOutput.AssignLiteral("\"\"");
+  return NS_OK;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLEditor::BlobReader)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HTMLEditor::BlobReader)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mBlob)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mHTMLEditor)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPointToInsert)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HTMLEditor::BlobReader)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBlob)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHTMLEditor)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPointToInsert)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+HTMLEditor::BlobReader::BlobReader(BlobImpl* aBlob, HTMLEditor* aHTMLEditor,
+                                   SafeToInsertData aSafeToInsertData,
+                                   const EditorDOMPoint& aPointToInsert,
+                                   DeleteSelectedContent aDeleteSelectedContent)
+    : mBlob(aBlob),
+      mHTMLEditor(aHTMLEditor),
+      // "beforeinput" event should've been dispatched before we read blob,
+      // but anyway, we need to clone dataTransfer for "input" event.
+      mDataTransfer(mHTMLEditor->GetInputEventDataTransfer()),
+      mPointToInsert(aPointToInsert),
+      mEditAction(aHTMLEditor->GetEditAction()),
+      mSafeToInsertData(aSafeToInsertData),
+      mDeleteSelectedContent(aDeleteSelectedContent),
+      mNeedsToDispatchBeforeInputEvent(
+          !mHTMLEditor->HasTriedToDispatchBeforeInputEvent()) {
+  MOZ_ASSERT(mBlob);
+  MOZ_ASSERT(mHTMLEditor);
+  MOZ_ASSERT(mHTMLEditor->IsEditActionDataAvailable());
+  MOZ_ASSERT(mDataTransfer);
+
+  // Take only offset here since it's our traditional behavior.
+  if (mPointToInsert.IsSet()) {
+    AutoEditorDOMPointChildInvalidator storeOnlyWithOffset(mPointToInsert);
+  }
+}
+
+nsresult HTMLEditor::BlobReader::OnResult(const nsACString& aResult) {
+  AutoEditActionDataSetter editActionData(*mHTMLEditor, mEditAction);
+  editActionData.InitializeDataTransfer(mDataTransfer);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return EditorBase::ToGenericNSResult(NS_ERROR_FAILURE);
+  }
+
+  if (NS_WARN_IF(mNeedsToDispatchBeforeInputEvent)) {
+    nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
+    if (NS_FAILED(rv)) {
+      NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
+                           "MaybeDispatchBeforeInputEvent(), failed");
+      return EditorBase::ToGenericNSResult(rv);
+    }
+  } else {
+    editActionData.MarkAsBeforeInputHasBeenDispatched();
+  }
+
+  nsString blobType;
+  mBlob->GetType(blobType);
+
+  // TODO: This does not work well.
+  // * If the data is not an image file, this inserts  element with odd
+  //   data URI (bug 1610220).
+  // * If the data is valid image file data, an  file is inserted with
+  //   data URI, but it's not loaded (bug 1610219).
+  NS_ConvertUTF16toUTF8 type(blobType);
+  nsAutoString stuffToPaste;
+  nsresult rv = ImgFromData(type, aResult, stuffToPaste);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("ImgFormData() failed");
+    return EditorBase::ToGenericNSResult(rv);
+  }
+
+  RefPtr htmlEditor = std::move(mHTMLEditor);
+  AutoPlaceholderBatch treatAsOneTransaction(
+      *htmlEditor, ScrollSelectionIntoView::Yes, __FUNCTION__);
+  EditorDOMPoint pointToInsert = std::move(mPointToInsert);
+  rv = htmlEditor->InsertHTMLWithContextAsSubAction(
+      stuffToPaste, u""_ns, u""_ns, NS_LITERAL_STRING_FROM_CSTRING(kFileMime),
+      mSafeToInsertData, pointToInsert, mDeleteSelectedContent,
+      InlineStylesAtInsertionPoint::Preserve);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                       "HTMLEditor::InsertHTMLWithContextAsSubAction("
+                       "InlineStylesAtInsertionPoint::Preserve) failed");
+  return EditorBase::ToGenericNSResult(rv);
+}
+
+nsresult HTMLEditor::BlobReader::OnError(const nsAString& aError) {
+  AutoTArray error;
+  error.AppendElement(aError);
+  nsContentUtils::ReportToConsole(
+      nsIScriptError::warningFlag, "Editor"_ns, mHTMLEditor->GetDocument(),
+      nsContentUtils::eDOM_PROPERTIES, "EditorFileDropFailed", error);
+  return NS_OK;
+}
+
+class SlurpBlobEventListener final : public nsIDOMEventListener {
+ public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(SlurpBlobEventListener)
+
+  explicit SlurpBlobEventListener(HTMLEditor::BlobReader* aListener)
+      : mListener(aListener) {}
+
+  MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override;
+
+ private:
+  ~SlurpBlobEventListener() = default;
+
+  RefPtr mListener;
+};
+
+NS_IMPL_CYCLE_COLLECTION(SlurpBlobEventListener, mListener)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SlurpBlobEventListener)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SlurpBlobEventListener)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(SlurpBlobEventListener)
+
+NS_IMETHODIMP SlurpBlobEventListener::HandleEvent(Event* aEvent) {
+  EventTarget* target = aEvent->GetTarget();
+  if (!target || !mListener) {
+    return NS_OK;
+  }
+
+  RefPtr reader = do_QueryObject(target);
+  if (!reader) {
+    return NS_OK;
+  }
+
+  EventMessage message = aEvent->WidgetEventPtr()->mMessage;
+
+  RefPtr listener(mListener);
+  if (message == eLoad) {
+    MOZ_ASSERT(reader->DataFormat() == FileReader::FILE_AS_BINARY);
+
+    // The original data has been converted from Latin1 to UTF-16, this just
+    // undoes that conversion.
+    DebugOnly rvIgnored =
+        listener->OnResult(NS_LossyConvertUTF16toASCII(reader->Result()));
+    NS_WARNING_ASSERTION(
+        NS_SUCCEEDED(rvIgnored),
+        "HTMLEditor::BlobReader::OnResult() failed, but ignored");
+    return NS_OK;
+  }
+
+  if (message == eLoadError) {
+    nsAutoString errorMessage;
+    reader->GetError()->GetErrorMessage(errorMessage);
+    DebugOnly rvIgnored = listener->OnError(errorMessage);
+    NS_WARNING_ASSERTION(
+        NS_SUCCEEDED(rvIgnored),
+        "HTMLEditor::BlobReader::OnError() failed, but ignored");
+    return NS_OK;
+  }
+
+  return NS_OK;
+}
+
+// static
+nsresult HTMLEditor::SlurpBlob(Blob* aBlob, nsIGlobalObject* aGlobal,
+                               BlobReader* aBlobReader) {
+  MOZ_ASSERT(aBlob);
+  MOZ_ASSERT(aGlobal);
+  MOZ_ASSERT(aBlobReader);
+
+  RefPtr workerRef;
+  RefPtr reader = new FileReader(aGlobal, workerRef);
+
+  RefPtr eventListener =
+      new SlurpBlobEventListener(aBlobReader);
+
+  nsresult rv = reader->AddEventListener(u"load"_ns, eventListener, false);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("FileReader::AddEventListener(load) failed");
+    return rv;
+  }
+
+  rv = reader->AddEventListener(u"error"_ns, eventListener, false);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("FileReader::AddEventListener(error) failed");
+    return rv;
+  }
+
+  ErrorResult error;
+  reader->ReadAsBinaryString(*aBlob, error);
+  NS_WARNING_ASSERTION(!error.Failed(),
+                       "FileReader::ReadAsBinaryString() failed");
+  return error.StealNSResult();
+}
+
+nsresult HTMLEditor::InsertObject(
+    const nsACString& aType, nsISupports* aObject,
+    SafeToInsertData aSafeToInsertData, const EditorDOMPoint& aPointToInsert,
+    DeleteSelectedContent aDeleteSelectedContent) {
+  MOZ_ASSERT(IsEditActionDataAvailable());
+
+  // Check to see if we the file is actually an image.
+  nsAutoCString type(aType);
+  if (type.EqualsLiteral(kFileMime)) {
+    if (nsCOMPtr file = do_QueryInterface(aObject)) {
+      nsCOMPtr mime = do_GetService("@mozilla.org/mime;1");
+      if (NS_WARN_IF(!mime)) {
+        return NS_ERROR_FAILURE;
+      }
+
+      nsresult rv = mime->GetTypeFromFile(file, type);
+      if (NS_FAILED(rv)) {
+        NS_WARNING("nsIMIMEService::GetTypeFromFile() failed");
+        return rv;
+      }
+    }
+  }
+
+  nsCOMPtr object = aObject;
+  if (type.EqualsLiteral(kJPEGImageMime) || type.EqualsLiteral(kJPGImageMime) ||
+      type.EqualsLiteral(kPNGImageMime) || type.EqualsLiteral(kGIFImageMime)) {
+    if (nsCOMPtr file = do_QueryInterface(object)) {
+      object = new FileBlobImpl(file);
+      // Fallthrough to BlobImpl code below.
+    } else if (RefPtr blob = do_QueryObject(object)) {
+      object = blob->Impl();
+      // Fallthrough.
+    } else if (nsCOMPtr imageStream =
+                   do_QueryInterface(object)) {
+      nsCString imageData;
+      nsresult rv = NS_ConsumeStream(imageStream, UINT32_MAX, imageData);
+      if (NS_FAILED(rv)) {
+        NS_WARNING("NS_ConsumeStream() failed");
+        return rv;
+      }
+
+      rv = imageStream->Close();
+      if (NS_FAILED(rv)) {
+        NS_WARNING("nsIInputStream::Close() failed");
+        return rv;
+      }
+
+      nsAutoString stuffToPaste;
+      rv = ImgFromData(type, imageData, stuffToPaste);
+      if (NS_FAILED(rv)) {
+        NS_WARNING("ImgFromData() failed");
+        return rv;
+      }
+
+      AutoPlaceholderBatch treatAsOneTransaction(
+          *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
+      rv = InsertHTMLWithContextAsSubAction(
+          stuffToPaste, u""_ns, u""_ns,
+          NS_LITERAL_STRING_FROM_CSTRING(kFileMime), aSafeToInsertData,
+          aPointToInsert, aDeleteSelectedContent,
+          InlineStylesAtInsertionPoint::Preserve);
+      NS_WARNING_ASSERTION(
+          NS_SUCCEEDED(rv),
+          "HTMLEditor::InsertHTMLWithContextAsSubAction("
+          "InlineStylesAtInsertionPoint::Preserve) failed, but ignored");
+      return NS_OK;
+    } else {
+      NS_WARNING("HTMLEditor::InsertObject: Unexpected type for image mime");
+      return NS_OK;
+    }
+  }
+
+  // We always try to insert BlobImpl even without a known image mime.
+  nsCOMPtr blob = do_QueryInterface(object);
+  if (!blob) {
+    return NS_OK;
+  }
+
+  RefPtr br = new BlobReader(
+      blob, this, aSafeToInsertData, aPointToInsert, aDeleteSelectedContent);
+
+  nsCOMPtr inner = GetInnerWindow();
+  nsCOMPtr global = do_QueryInterface(inner);
+  if (!global) {
+    NS_WARNING("Could not get global");
+    return NS_ERROR_FAILURE;
+  }
+
+  RefPtr domBlob = Blob::Create(global, blob);
+  if (!domBlob) {
+    NS_WARNING("Blob::Create() failed");
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv = SlurpBlob(domBlob, global, br);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::::SlurpBlob() failed");
+  return rv;
+}
+
+static bool GetString(nsISupports* aData, nsAString& aText) {
+  if (nsCOMPtr str = do_QueryInterface(aData)) {
+    DebugOnly rvIgnored = str->GetData(aText);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                         "nsISupportsString::GetData() failed, but ignored");
+    return !aText.IsEmpty();
+  }
+
+  return false;
+}
+
+static bool GetCString(nsISupports* aData, nsACString& aText) {
+  if (nsCOMPtr str = do_QueryInterface(aData)) {
+    DebugOnly rvIgnored = str->GetData(aText);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                         "nsISupportsString::GetData() failed, but ignored");
+    return !aText.IsEmpty();
+  }
+
+  return false;
+}
+
+nsresult HTMLEditor::InsertFromTransferableAtSelection(
+    nsITransferable* aTransferable, const nsAString& aContextStr,
+    const nsAString& aInfoStr, HavePrivateHTMLFlavor aHavePrivateHTMLFlavor) {
+  nsAutoCString bestFlavor;
+  nsCOMPtr genericDataObj;
+
+  // See `HTMLTransferablePreparer::AddDataFlavorsInBestOrder`.
+  nsresult rv = aTransferable->GetAnyTransferData(
+      bestFlavor, getter_AddRefs(genericDataObj));
+  NS_WARNING_ASSERTION(
+      NS_SUCCEEDED(rv),
+      "nsITransferable::GetAnyTransferData() failed, but ignored");
+  if (NS_SUCCEEDED(rv)) {
+    AutoTransactionsConserveSelection dontChangeMySelection(*this);
+    nsAutoString flavor;
+    CopyASCIItoUTF16(bestFlavor, flavor);
+    const SafeToInsertData safeToInsertData = IsSafeToInsertData(nullptr);
+
+    if (bestFlavor.EqualsLiteral(kFileMime) ||
+        bestFlavor.EqualsLiteral(kJPEGImageMime) ||
+        bestFlavor.EqualsLiteral(kJPGImageMime) ||
+        bestFlavor.EqualsLiteral(kPNGImageMime) ||
+        bestFlavor.EqualsLiteral(kGIFImageMime)) {
+      nsresult rv = InsertObject(bestFlavor, genericDataObj, safeToInsertData,
+                                 EditorDOMPoint(), DeleteSelectedContent::Yes);
+      if (NS_FAILED(rv)) {
+        NS_WARNING("HTMLEditor::InsertObject() failed");
+        return rv;
+      }
+    } else if (bestFlavor.EqualsLiteral(kNativeHTMLMime)) {
+      // note cf_html uses utf8
+      nsAutoCString cfhtml;
+      if (GetCString(genericDataObj, cfhtml)) {
+        // cfselection left emtpy for now.
+        nsString cfcontext, cffragment, cfselection;
+        nsresult rv = ParseCFHTML(cfhtml, getter_Copies(cffragment),
+                                  getter_Copies(cfcontext));
+        if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty()) {
+          // If we have our private HTML flavor, we will only use the fragment
+          // from the CF_HTML. The rest comes from the clipboard.
+          if (aHavePrivateHTMLFlavor == HavePrivateHTMLFlavor::Yes) {
+            AutoPlaceholderBatch treatAsOneTransaction(
+                *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
+            rv = InsertHTMLWithContextAsSubAction(
+                cffragment, aContextStr, aInfoStr, flavor, safeToInsertData,
+                EditorDOMPoint(), DeleteSelectedContent::Yes,
+                InlineStylesAtInsertionPoint::Clear);
+            if (NS_FAILED(rv)) {
+              NS_WARNING(
+                  "HTMLEditor::InsertHTMLWithContextAsSubAction("
+                  "DeleteSelectedContent::Yes, "
+                  "InlineStylesAtInsertionPoint::Clear) failed");
+              return rv;
+            }
+          } else {
+            AutoPlaceholderBatch treatAsOneTransaction(
+                *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
+            rv = InsertHTMLWithContextAsSubAction(
+                cffragment, cfcontext, cfselection, flavor, safeToInsertData,
+                EditorDOMPoint(), DeleteSelectedContent::Yes,
+                InlineStylesAtInsertionPoint::Clear);
+            if (NS_FAILED(rv)) {
+              NS_WARNING(
+                  "HTMLEditor::InsertHTMLWithContextAsSubAction("
+                  "DeleteSelectedContent::Yes, "
+                  "InlineStylesAtInsertionPoint::Clear) failed");
+              return rv;
+            }
+          }
+        } else {
+          // In some platforms (like Linux), the clipboard might return data
+          // requested for unknown flavors (for example:
+          // application/x-moz-nativehtml).  In this case, treat the data
+          // to be pasted as mere HTML to get the best chance of pasting it
+          // correctly.
+          bestFlavor.AssignLiteral(kHTMLMime);
+          // Fall through the next case
+        }
+      }
+    }
+    if (bestFlavor.EqualsLiteral(kHTMLMime) ||
+        bestFlavor.EqualsLiteral(kTextMime) ||
+        bestFlavor.EqualsLiteral(kMozTextInternal)) {
+      nsAutoString stuffToPaste;
+      if (!GetString(genericDataObj, stuffToPaste)) {
+        nsAutoCString text;
+        if (GetCString(genericDataObj, text)) {
+          CopyUTF8toUTF16(text, stuffToPaste);
+        }
+      }
+
+      if (!stuffToPaste.IsEmpty()) {
+        if (bestFlavor.EqualsLiteral(kHTMLMime)) {
+          AutoPlaceholderBatch treatAsOneTransaction(
+              *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
+          nsresult rv = InsertHTMLWithContextAsSubAction(
+              stuffToPaste, aContextStr, aInfoStr, flavor, safeToInsertData,
+              EditorDOMPoint(), DeleteSelectedContent::Yes,
+              InlineStylesAtInsertionPoint::Clear);
+          if (NS_FAILED(rv)) {
+            NS_WARNING(
+                "HTMLEditor::InsertHTMLWithContextAsSubAction("
+                "DeleteSelectedContent::Yes, "
+                "InlineStylesAtInsertionPoint::Clear) failed");
+            return rv;
+          }
+        } else {
+          AutoPlaceholderBatch treatAsOneTransaction(
+              *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
+          nsresult rv =
+              InsertTextAsSubAction(stuffToPaste, SelectionHandling::Delete);
+          if (NS_FAILED(rv)) {
+            NS_WARNING("EditorBase::InsertTextAsSubAction() failed");
+            return rv;
+          }
+        }
+      }
+    }
+  }
+
+  // Try to scroll the selection into view if the paste succeeded
+  DebugOnly rvIgnored = ScrollSelectionFocusIntoView();
+  NS_WARNING_ASSERTION(
+      NS_SUCCEEDED(rvIgnored),
+      "EditorBase::ScrollSelectionFocusIntoView() failed, but ignored");
+  return NS_OK;
+}
+
+static void GetStringFromDataTransfer(const DataTransfer* aDataTransfer,
+                                      const nsAString& aType, uint32_t aIndex,
+                                      nsString& aOutputString) {
+  nsCOMPtr variant;
+  DebugOnly rvIgnored = aDataTransfer->GetDataAtNoSecurityCheck(
+      aType, aIndex, getter_AddRefs(variant));
+  if (!variant) {
+    MOZ_ASSERT(aOutputString.IsEmpty());
+    return;
+  }
+  NS_WARNING_ASSERTION(
+      NS_SUCCEEDED(rvIgnored),
+      "DataTransfer::GetDataAtNoSecurityCheck() failed, but ignored");
+  variant->GetAsAString(aOutputString);
+  nsContentUtils::PlatformToDOMLineBreaks(aOutputString);
+}
+
+nsresult HTMLEditor::InsertFromDataTransfer(
+    const DataTransfer* aDataTransfer, uint32_t aIndex,
+    nsIPrincipal* aSourcePrincipal, const EditorDOMPoint& aDroppedAt,
+    DeleteSelectedContent aDeleteSelectedContent) {
+  MOZ_ASSERT(GetEditAction() == EditAction::eDrop ||
+             GetEditAction() == EditAction::ePaste);
+  MOZ_ASSERT(mPlaceholderBatch,
+             "HTMLEditor::InsertFromDataTransfer() should be called by "
+             "HandleDropEvent() or paste action and there should've already "
+             "been placeholder transaction");
+  MOZ_ASSERT_IF(GetEditAction() == EditAction::eDrop, aDroppedAt.IsSet());
+
+  ErrorResult error;
+  RefPtr types = aDataTransfer->MozTypesAt(aIndex, error);
+  if (error.Failed()) {
+    NS_WARNING("DataTransfer::MozTypesAt() failed");
+    return error.StealNSResult();
+  }
+
+  const bool hasPrivateHTMLFlavor =
+      types->Contains(NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext));
+
+  const bool isPlaintextEditor = IsInPlaintextMode();
+  const SafeToInsertData safeToInsertData =
+      IsSafeToInsertData(aSourcePrincipal);
+
+  uint32_t length = types->Length();
+  for (uint32_t i = 0; i < length; i++) {
+    nsAutoString type;
+    types->Item(i, type);
+
+    if (!isPlaintextEditor) {
+      if (type.EqualsLiteral(kFileMime) || type.EqualsLiteral(kJPEGImageMime) ||
+          type.EqualsLiteral(kJPGImageMime) ||
+          type.EqualsLiteral(kPNGImageMime) ||
+          type.EqualsLiteral(kGIFImageMime)) {
+        nsCOMPtr variant;
+        DebugOnly rvIgnored = aDataTransfer->GetDataAtNoSecurityCheck(
+            type, aIndex, getter_AddRefs(variant));
+        if (variant) {
+          NS_WARNING_ASSERTION(
+              NS_SUCCEEDED(rvIgnored),
+              "DataTransfer::GetDataAtNoSecurityCheck() failed, but ignored");
+          nsCOMPtr object;
+          rvIgnored = variant->GetAsISupports(getter_AddRefs(object));
+          NS_WARNING_ASSERTION(
+              NS_SUCCEEDED(rvIgnored),
+              "nsIVariant::GetAsISupports() failed, but ignored");
+          nsresult rv = InsertObject(NS_ConvertUTF16toUTF8(type), object,
+                                     safeToInsertData, aDroppedAt,
+                                     aDeleteSelectedContent);
+          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                               "HTMLEditor::InsertObject() failed");
+          return rv;
+        }
+      } else if (type.EqualsLiteral(kNativeHTMLMime)) {
+        // Windows only clipboard parsing.
+        nsAutoString text;
+        GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
+        NS_ConvertUTF16toUTF8 cfhtml(text);
+
+        nsString cfcontext, cffragment,
+            cfselection;  // cfselection left emtpy for now
+
+        nsresult rv = ParseCFHTML(cfhtml, getter_Copies(cffragment),
+                                  getter_Copies(cfcontext));
+        if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty()) {
+          if (hasPrivateHTMLFlavor) {
+            // If we have our private HTML flavor, we will only use the fragment
+            // from the CF_HTML. The rest comes from the clipboard.
+            nsAutoString contextString, infoString;
+            GetStringFromDataTransfer(
+                aDataTransfer, NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext),
+                aIndex, contextString);
+            GetStringFromDataTransfer(aDataTransfer,
+                                      NS_LITERAL_STRING_FROM_CSTRING(kHTMLInfo),
+                                      aIndex, infoString);
+            AutoPlaceholderBatch treatAsOneTransaction(
+                *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
+            nsresult rv = InsertHTMLWithContextAsSubAction(
+                cffragment, contextString, infoString, type, safeToInsertData,
+                aDroppedAt, aDeleteSelectedContent,
+                InlineStylesAtInsertionPoint::Clear);
+            NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                                 "HTMLEditor::InsertHTMLWithContextAsSubAction("
+                                 "InlineStylesAtInsertionPoint::Clear) failed");
+            return rv;
+          }
+          AutoPlaceholderBatch treatAsOneTransaction(
+              *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
+          nsresult rv = InsertHTMLWithContextAsSubAction(
+              cffragment, cfcontext, cfselection, type, safeToInsertData,
+              aDroppedAt, aDeleteSelectedContent,
+              InlineStylesAtInsertionPoint::Clear);
+          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                               "HTMLEditor::InsertHTMLWithContextAsSubAction("
+                               "InlineStylesAtInsertionPoint::Clear) failed");
+          return rv;
+        }
+      } else if (type.EqualsLiteral(kHTMLMime)) {
+        nsAutoString text, contextString, infoString;
+        GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
+        GetStringFromDataTransfer(aDataTransfer,
+                                  NS_LITERAL_STRING_FROM_CSTRING(kHTMLContext),
+                                  aIndex, contextString);
+        GetStringFromDataTransfer(aDataTransfer,
+                                  NS_LITERAL_STRING_FROM_CSTRING(kHTMLInfo),
+                                  aIndex, infoString);
+        if (type.EqualsLiteral(kHTMLMime)) {
+          AutoPlaceholderBatch treatAsOneTransaction(
+              *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
+          nsresult rv = InsertHTMLWithContextAsSubAction(
+              text, contextString, infoString, type, safeToInsertData,
+              aDroppedAt, aDeleteSelectedContent,
+              InlineStylesAtInsertionPoint::Clear);
+          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                               "HTMLEditor::InsertHTMLWithContextAsSubAction("
+                               "InlineStylesAtInsertionPoint::Clear) failed");
+          return rv;
+        }
+      }
+    }
+
+    if (type.EqualsLiteral(kTextMime) || type.EqualsLiteral(kMozTextInternal)) {
+      nsAutoString text;
+      GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
+      AutoPlaceholderBatch treatAsOneTransaction(
+          *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
+      nsresult rv = InsertTextAt(text, aDroppedAt, aDeleteSelectedContent);
+      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                           "EditorBase::InsertTextAt() failed");
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
+// static
+HTMLEditor::HavePrivateHTMLFlavor HTMLEditor::ClipboardHasPrivateHTMLFlavor(
+    nsIClipboard* aClipboard) {
+  if (NS_WARN_IF(!aClipboard)) {
+    return HavePrivateHTMLFlavor::No;
+  }
+
+  // check the clipboard for our special kHTMLContext flavor.  If that is there,
+  // we know we have our own internal html format on clipboard.
+  bool hasPrivateHTMLFlavor = false;
+  AutoTArray flavArray = {nsDependentCString(kHTMLContext)};
+  nsresult rv = aClipboard->HasDataMatchingFlavors(
+      flavArray, nsIClipboard::kGlobalClipboard, &hasPrivateHTMLFlavor);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                       "nsIClipboard::HasDataMatchingFlavors(nsIClipboard::"
+                       "kGlobalClipboard) failed");
+  return NS_SUCCEEDED(rv) && hasPrivateHTMLFlavor ? HavePrivateHTMLFlavor::Yes
+                                                  : HavePrivateHTMLFlavor::No;
+}
+
+nsresult HTMLEditor::HandlePaste(AutoEditActionDataSetter& aEditActionData,
+                                 int32_t aClipboardType) {
+  aEditActionData.InitializeDataTransferWithClipboard(
+      SettingDataTransfer::eWithFormat, aClipboardType);
+  nsresult rv = aEditActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
+  if (NS_FAILED(rv)) {
+    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
+                         "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
+    return rv;
+  }
+  rv = PasteInternal(aClipboardType);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::PasteInternal() failed");
+  return rv;
+}
+
+nsresult HTMLEditor::PasteInternal(int32_t aClipboardType) {
+  MOZ_ASSERT(IsEditActionDataAvailable());
+
+  // Get Clipboard Service
+  nsresult rv = NS_OK;
+  nsCOMPtr clipboard =
+      do_GetService("@mozilla.org/widget/clipboard;1", &rv);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to get nsIClipboard service");
+    return rv;
+  }
+
+  // Get the nsITransferable interface for getting the data from the clipboard
+  nsCOMPtr transferable;
+  rv = PrepareHTMLTransferable(getter_AddRefs(transferable));
+  if (NS_FAILED(rv)) {
+    NS_WARNING("HTMLEditor::PrepareHTMLTransferable() failed");
+    return rv;
+  }
+  if (!transferable) {
+    NS_WARNING("HTMLEditor::PrepareHTMLTransferable() returned nullptr");
+    return NS_ERROR_FAILURE;
+  }
+  // Get the Data from the clipboard
+  rv = clipboard->GetData(transferable, aClipboardType);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("nsIClipboard::GetData() failed");
+    return rv;
+  }
+
+  // XXX Why don't you check this first?
+  if (!IsModifiable()) {
+    return NS_OK;
+  }
+
+  // also get additional html copy hints, if present
+  nsAutoString contextStr, infoStr;
+
+  // If we have our internal html flavor on the clipboard, there is special
+  // context to use instead of cfhtml context.
+  const HavePrivateHTMLFlavor clipboardHasPrivateHTMLFlavor =
+      ClipboardHasPrivateHTMLFlavor(clipboard);
+  if (clipboardHasPrivateHTMLFlavor == HavePrivateHTMLFlavor::Yes) {
+    nsCOMPtr contextTransferable =
+        do_CreateInstance("@mozilla.org/widget/transferable;1");
+    if (!contextTransferable) {
+      NS_WARNING(
+          "do_CreateInstance() failed to create nsITransferable instance");
+      return NS_ERROR_FAILURE;
+    }
+    DebugOnly rvIgnored = contextTransferable->Init(nullptr);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                         "nsITransferable::Init() failed, but ignored");
+    contextTransferable->SetIsPrivateData(transferable->GetIsPrivateData());
+    rvIgnored = contextTransferable->AddDataFlavor(kHTMLContext);
+    NS_WARNING_ASSERTION(
+        NS_SUCCEEDED(rvIgnored),
+        "nsITransferable::AddDataFlavor(kHTMLContext) failed, but ignored");
+    rvIgnored = clipboard->GetData(contextTransferable, aClipboardType);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                         "nsIClipboard::GetData() failed, but ignored");
+    nsCOMPtr contextDataObj;
+    rv = contextTransferable->GetTransferData(kHTMLContext,
+                                              getter_AddRefs(contextDataObj));
+    if (NS_SUCCEEDED(rv) && contextDataObj) {
+      if (nsCOMPtr str = do_QueryInterface(contextDataObj)) {
+        DebugOnly rvIgnored = str->GetData(contextStr);
+        NS_WARNING_ASSERTION(
+            NS_SUCCEEDED(rvIgnored),
+            "nsISupportsString::GetData() failed, but ignored");
+      }
+    }
+
+    nsCOMPtr infoTransferable =
+        do_CreateInstance("@mozilla.org/widget/transferable;1");
+    if (!infoTransferable) {
+      NS_WARNING(
+          "do_CreateInstance() failed to create nsITransferable instance");
+      return NS_ERROR_FAILURE;
+    }
+    rvIgnored = infoTransferable->Init(nullptr);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                         "nsITransferable::Init() failed, but ignored");
+    contextTransferable->SetIsPrivateData(transferable->GetIsPrivateData());
+    rvIgnored = infoTransferable->AddDataFlavor(kHTMLInfo);
+    NS_WARNING_ASSERTION(
+        NS_SUCCEEDED(rvIgnored),
+        "nsITransferable::AddDataFlavor(kHTMLInfo) failed, but ignored");
+    clipboard->GetData(infoTransferable, aClipboardType);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                         "nsIClipboard::GetData() failed, but ignored");
+    nsCOMPtr infoDataObj;
+    rv = infoTransferable->GetTransferData(kHTMLInfo,
+                                           getter_AddRefs(infoDataObj));
+    if (NS_SUCCEEDED(rv) && infoDataObj) {
+      if (nsCOMPtr str = do_QueryInterface(infoDataObj)) {
+        DebugOnly rvIgnored = str->GetData(infoStr);
+        NS_WARNING_ASSERTION(
+            NS_SUCCEEDED(rvIgnored),
+            "nsISupportsString::GetData() failed, but ignored");
+      }
+    }
+  }
+
+  rv = InsertFromTransferableAtSelection(transferable, contextStr, infoStr,
+                                         clipboardHasPrivateHTMLFlavor);
+  NS_WARNING_ASSERTION(
+      NS_SUCCEEDED(rv),
+      "HTMLEditor::InsertFromTransferableAtSelection() failed");
+  return rv;
+}
+
+nsresult HTMLEditor::HandlePasteTransferable(
+    AutoEditActionDataSetter& aEditActionData, nsITransferable& aTransferable) {
+  // InitializeDataTransfer may fetch input stream in aTransferable, so it
+  // may be invalid after calling this.
+  aEditActionData.InitializeDataTransfer(&aTransferable);
+
+  nsresult rv = aEditActionData.MaybeDispatchBeforeInputEvent();
+  if (NS_FAILED(rv)) {
+    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
+                         "MaybeDispatchBeforeInputEvent(), failed");
+    return rv;
+  }
+
+  RefPtr dataTransfer = GetInputEventDataTransfer();
+  if (dataTransfer->HasFile() && dataTransfer->MozItemCount() > 0) {
+    // Now aTransferable has moved to DataTransfer. Use DataTransfer.
+    AutoPlaceholderBatch treatAsOneTransaction(
+        *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
+
+    rv = InsertFromDataTransfer(dataTransfer, 0, nullptr, EditorDOMPoint(),
+                                DeleteSelectedContent::Yes);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                         "HTMLEditor::InsertFromDataTransfer("
+                         "DeleteSelectedContent::Yes) failed");
+    return rv;
+  }
+
+  nsAutoString contextStr, infoStr;
+  rv = InsertFromTransferableAtSelection(&aTransferable, contextStr, infoStr,
+                                         HavePrivateHTMLFlavor::No);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                       "HTMLEditor::InsertFromTransferableAtSelection("
+                       "HavePrivateHTMLFlavor::No) failed");
+  return rv;
+}
+
+nsresult HTMLEditor::PasteNoFormattingAsAction(
+    int32_t aClipboardType, DispatchPasteEvent aDispatchPasteEvent,
+    nsIPrincipal* aPrincipal) {
+  if (IsReadonly()) {
+    return NS_OK;
+  }
+
+  AutoEditActionDataSetter editActionData(*this, EditAction::ePaste,
+                                          aPrincipal);
+  if (NS_WARN_IF(!editActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  editActionData.InitializeDataTransferWithClipboard(
+      SettingDataTransfer::eWithoutFormat, aClipboardType);
+
+  if (aDispatchPasteEvent == DispatchPasteEvent::Yes) {
+    RefPtr focusManager = nsFocusManager::GetFocusManager();
+    if (NS_WARN_IF(!focusManager)) {
+      return NS_ERROR_UNEXPECTED;
+    }
+    const RefPtr focusedElement = focusManager->GetFocusedElement();
+
+    Result ret =
+        DispatchClipboardEventAndUpdateClipboard(ePasteNoFormatting,
+                                                 aClipboardType);
+    if (MOZ_UNLIKELY(ret.isErr())) {
+      NS_WARNING(
+          "EditorBase::DispatchClipboardEventAndUpdateClipboard("
+          "ePasteNoFormatting) failed");
+      return EditorBase::ToGenericNSResult(ret.unwrapErr());
+    }
+    switch (ret.inspect()) {
+      case ClipboardEventResult::DoDefault:
+        break;
+      case ClipboardEventResult::DefaultPreventedOfPaste:
+      case ClipboardEventResult::IgnoredOrError:
+        return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
+      case ClipboardEventResult::CopyOrCutHandled:
+        MOZ_ASSERT_UNREACHABLE("Invalid result for ePaste");
+    }
+
+    // If focus is changed by a "paste" event listener, we should keep handling
+    // the "pasting" in new focused editor because Chrome works as so.
+    const RefPtr newFocusedElement = focusManager->GetFocusedElement();
+    if (MOZ_UNLIKELY(focusedElement != newFocusedElement)) {
+      // For the privacy reason, let's top handling it if new focused element is
+      // in different document.
+      if (focusManager->GetFocusedWindow() != GetWindow()) {
+        return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
+      }
+      RefPtr editorBase =
+          nsContentUtils::GetActiveEditor(GetPresContext());
+      if (!editorBase || (editorBase->IsHTMLEditor() &&
+                          !editorBase->AsHTMLEditor()->IsActiveInDOMWindow())) {
+        return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
+      }
+      if (editorBase != this) {
+        if (editorBase->IsHTMLEditor()) {
+          nsresult rv =
+              MOZ_KnownLive(editorBase->AsHTMLEditor())
+                  ->PasteNoFormattingAsAction(
+                      aClipboardType, DispatchPasteEvent::No, aPrincipal);
+          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                               "HTMLEditor::PasteNoFormattingAsAction("
+                               "DispatchPasteEvent::No) failed");
+          return EditorBase::ToGenericNSResult(rv);
+        }
+        nsresult rv = editorBase->PasteAsAction(
+            aClipboardType, DispatchPasteEvent::No, aPrincipal);
+        NS_WARNING_ASSERTION(
+            NS_SUCCEEDED(rv),
+            "EditorBase::PasteAsAction(DispatchPasteEvent::No) failed");
+        return EditorBase::ToGenericNSResult(rv);
+      }
+    }
+  }
+
+  // Dispatch "beforeinput" event after "paste" event.  And perhaps, before
+  // committing composition because if pasting is canceled, we don't need to
+  // commit the active composition.
+  nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
+  if (NS_FAILED(rv)) {
+    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
+                         "MaybeDispatchBeforeInputEvent(), failed");
+    return EditorBase::ToGenericNSResult(rv);
+  }
+
+  DebugOnly rvIgnored = CommitComposition();
+  if (NS_WARN_IF(Destroyed())) {
+    return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
+  }
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                       "EditorBase::CommitComposition() failed, but ignored");
+
+  // Get Clipboard Service
+  nsCOMPtr clipboard(
+      do_GetService("@mozilla.org/widget/clipboard;1", &rv));
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to get nsIClipboard service");
+    return rv;
+  }
+
+  if (!GetDocument()) {
+    NS_WARNING("Editor didn't have document, but ignored");
+    return NS_OK;
+  }
+
+  Result, nsresult> maybeTransferable =
+      EditorUtils::CreateTransferableForPlainText(*GetDocument());
+  if (maybeTransferable.isErr()) {
+    NS_WARNING("EditorUtils::CreateTransferableForPlainText() failed");
+    return EditorBase::ToGenericNSResult(maybeTransferable.unwrapErr());
+  }
+  nsCOMPtr transferable(maybeTransferable.unwrap());
+  if (!transferable) {
+    NS_WARNING(
+        "EditorUtils::CreateTransferableForPlainText() returned nullptr, but "
+        "ignored");
+    return NS_OK;
+  }
+
+  if (!IsModifiable()) {
+    return NS_OK;
+  }
+
+  // Get the Data from the clipboard
+  rv = clipboard->GetData(transferable, aClipboardType);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("nsIClipboard::GetData() failed");
+    return rv;
+  }
+
+  rv = InsertFromTransferableAtSelection(transferable, u""_ns, u""_ns,
+                                         HavePrivateHTMLFlavor::No);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                       "HTMLEditor::InsertFromTransferableAtSelection("
+                       "HavePrivateHTMLFlavor::No) failed");
+  return EditorBase::ToGenericNSResult(rv);
+}
+
+// The following arrays contain the MIME types that we can paste. The arrays
+// are used by CanPaste() and CanPasteTransferable() below.
+
+static const char* textEditorFlavors[] = {kTextMime};
+static const char* textHtmlEditorFlavors[] = {kTextMime,      kHTMLMime,
+                                              kJPEGImageMime, kJPGImageMime,
+                                              kPNGImageMime,  kGIFImageMime};
+
+bool HTMLEditor::CanPaste(int32_t aClipboardType) const {
+  if (AreClipboardCommandsUnconditionallyEnabled()) {
+    return true;
+  }
+
+  // can't paste if readonly
+  if (!IsModifiable()) {
+    return false;
+  }
+
+  nsresult rv;
+  nsCOMPtr clipboard(
+      do_GetService("@mozilla.org/widget/clipboard;1", &rv));
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to get nsIClipboard service");
+    return false;
+  }
+
+  // Use the flavors depending on the current editor mask
+  if (IsInPlaintextMode()) {
+    AutoTArray flavors;
+    flavors.AppendElements(Span(textEditorFlavors));
+    bool haveFlavors;
+    nsresult rv = clipboard->HasDataMatchingFlavors(flavors, aClipboardType,
+                                                    &haveFlavors);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                         "nsIClipboard::HasDataMatchingFlavors() failed");
+    return NS_SUCCEEDED(rv) && haveFlavors;
+  }
+
+  AutoTArray flavors;
+  flavors.AppendElements(Span(textHtmlEditorFlavors));
+  bool haveFlavors;
+  rv = clipboard->HasDataMatchingFlavors(flavors, aClipboardType, &haveFlavors);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                       "nsIClipboard::HasDataMatchingFlavors() failed");
+  return NS_SUCCEEDED(rv) && haveFlavors;
+}
+
+bool HTMLEditor::CanPasteTransferable(nsITransferable* aTransferable) {
+  // can't paste if readonly
+  if (!IsModifiable()) {
+    return false;
+  }
+
+  // If |aTransferable| is null, assume that a paste will succeed.
+  if (!aTransferable) {
+    return true;
+  }
+
+  // Peek in |aTransferable| to see if it contains a supported MIME type.
+
+  // Use the flavors depending on the current editor mask
+  const char** flavors;
+  size_t length;
+  if (IsInPlaintextMode()) {
+    flavors = textEditorFlavors;
+    length = ArrayLength(textEditorFlavors);
+  } else {
+    flavors = textHtmlEditorFlavors;
+    length = ArrayLength(textHtmlEditorFlavors);
+  }
+
+  for (size_t i = 0; i < length; i++, flavors++) {
+    nsCOMPtr data;
+    nsresult rv =
+        aTransferable->GetTransferData(*flavors, getter_AddRefs(data));
+    if (NS_SUCCEEDED(rv) && data) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+nsresult HTMLEditor::HandlePasteAsQuotation(
+    AutoEditActionDataSetter& aEditActionData, int32_t aClipboardType) {
+  MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
+             aClipboardType == nsIClipboard::kSelectionClipboard);
+  aEditActionData.InitializeDataTransferWithClipboard(
+      SettingDataTransfer::eWithFormat, aClipboardType);
+  if (NS_WARN_IF(!aEditActionData.CanHandle())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  nsresult rv = aEditActionData.MaybeDispatchBeforeInputEvent();
+  if (NS_FAILED(rv)) {
+    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
+                         "MaybeDispatchBeforeInputEvent(), failed");
+    return rv;
+  }
+
+  if (IsInPlaintextMode()) {
+    nsresult rv = PasteAsPlaintextQuotation(aClipboardType);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                         "HTMLEditor::PasteAsPlaintextQuotation() failed");
+    return rv;
+  }
+
+  // If it's not in plain text edit mode, paste text into new
+  // 
element after removing selection. + + { + // XXX Why don't we test these first? + Result result = CanHandleHTMLEditSubAction(); + if (MOZ_UNLIKELY(result.isErr())) { + NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); + return result.unwrapErr(); + } + if (result.inspect().Canceled()) { + return NS_OK; + } + } + + UndefineCaretBidiLevel(); + + AutoPlaceholderBatch treatAsOneTransaction( + *this, ScrollSelectionIntoView::Yes, __FUNCTION__); + IgnoredErrorResult ignoredError; + AutoEditSubActionNotifier startToHandleEditSubAction( + *this, EditSubAction::eInsertQuotation, nsIEditor::eNext, ignoredError); + if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { + return ignoredError.StealNSResult(); + } + NS_WARNING_ASSERTION( + !ignoredError.Failed(), + "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); + + rv = EnsureNoPaddingBRElementForEmptyEditor(); + if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " + "failed, but ignored"); + + if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) { + nsresult rv = EnsureCaretNotAfterInvisibleBRElement(); + if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " + "failed, but ignored"); + if (NS_SUCCEEDED(rv)) { + nsresult rv = PrepareInlineStylesForCaret(); + if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); + } + } + + // Remove Selection and create `
` now. + // XXX Why don't we insert the `
` into the DOM tree after + // pasting the content in clipboard into it? + Result, nsresult> blockquoteElementOrError = + DeleteSelectionAndCreateElement( + *nsGkAtoms::blockquote, + // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868 + [](HTMLEditor&, Element& aBlockquoteElement, const EditorDOMPoint&) + MOZ_CAN_RUN_SCRIPT_BOUNDARY { + DebugOnly rvIgnored = aBlockquoteElement.SetAttr( + kNameSpaceID_None, nsGkAtoms::type, u"cite"_ns, + aBlockquoteElement.IsInComposedDoc()); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + nsPrintfCString( + "Element::SetAttr(nsGkAtoms::type, \"cite\", %s) " + "failed, but ignored", + aBlockquoteElement.IsInComposedDoc() ? "true" : "false") + .get()); + return NS_OK; + }); + if (MOZ_UNLIKELY(blockquoteElementOrError.isErr()) || + NS_WARN_IF(Destroyed())) { + NS_WARNING( + "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::blockquote) " + "failed"); + return Destroyed() ? NS_ERROR_EDITOR_DESTROYED + : blockquoteElementOrError.unwrapErr(); + } + MOZ_ASSERT(blockquoteElementOrError.inspect()); + + // Collapse Selection in the new `
` element. + rv = CollapseSelectionToStartOf( + MOZ_KnownLive(*blockquoteElementOrError.inspect())); + if (NS_FAILED(rv)) { + NS_WARNING("EditorBase::CollapseSelectionToStartOf() failed"); + return rv; + } + + rv = PasteInternal(aClipboardType); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::PasteInternal() failed"); + return rv; +} + +nsresult HTMLEditor::PasteAsPlaintextQuotation(int32_t aSelectionType) { + // Get Clipboard Service + nsresult rv; + nsCOMPtr clipboard = + do_GetService("@mozilla.org/widget/clipboard;1", &rv); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to get nsIClipboard service"); + return rv; + } + + // Create generic Transferable for getting the data + nsCOMPtr transferable = + do_CreateInstance("@mozilla.org/widget/transferable;1", &rv); + if (NS_FAILED(rv)) { + NS_WARNING("do_CreateInstance() failed to create nsITransferable instance"); + return rv; + } + if (!transferable) { + NS_WARNING("do_CreateInstance() returned nullptr"); + return NS_ERROR_FAILURE; + } + + RefPtr destdoc = GetDocument(); + nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr; + DebugOnly rvIgnored = transferable->Init(loadContext); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "nsITransferable::Init() failed, but ignored"); + + // We only handle plaintext pastes here + rvIgnored = transferable->AddDataFlavor(kTextMime); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "nsITransferable::AddDataFlavor(kTextMime) failed, but ignored"); + + // Get the Data from the clipboard + rvIgnored = clipboard->GetData(transferable, aSelectionType); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "nsIClipboard::GetData() failed, but ignored"); + + // Now we ask the transferable for the data + // it still owns the data, we just have a pointer to it. + // If it can't support a "text" output of the data the call will fail + nsCOMPtr genericDataObj; + nsAutoCString flavor; + rv = transferable->GetAnyTransferData(flavor, getter_AddRefs(genericDataObj)); + if (NS_FAILED(rv)) { + NS_WARNING("nsITransferable::GetAnyTransferData() failed"); + return rv; + } + + if (!flavor.EqualsLiteral(kTextMime)) { + return NS_OK; + } + + nsAutoString stuffToPaste; + if (!GetString(genericDataObj, stuffToPaste)) { + return NS_OK; + } + + AutoPlaceholderBatch treatAsOneTransaction( + *this, ScrollSelectionIntoView::Yes, __FUNCTION__); + rv = InsertAsPlaintextQuotation(stuffToPaste, true, 0); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::InsertAsPlaintextQuotation() failed"); + return rv; +} + +nsresult HTMLEditor::InsertWithQuotationsAsSubAction( + const nsAString& aQuotedText) { + MOZ_ASSERT(IsEditActionDataAvailable()); + + { + Result result = CanHandleHTMLEditSubAction(); + if (MOZ_UNLIKELY(result.isErr())) { + NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); + return result.unwrapErr(); + } + if (result.inspect().Canceled()) { + return NS_OK; + } + } + + UndefineCaretBidiLevel(); + + // Let the citer quote it for us: + nsString quotedStuff; + InternetCiter::GetCiteString(aQuotedText, quotedStuff); + + // It's best to put a blank line after the quoted text so that mails + // written without thinking won't be so ugly. + if (!aQuotedText.IsEmpty() && + (aQuotedText.Last() != HTMLEditUtils::kNewLine)) { + quotedStuff.Append(HTMLEditUtils::kNewLine); + } + + IgnoredErrorResult ignoredError; + AutoEditSubActionNotifier startToHandleEditSubAction( + *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError); + if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { + return ignoredError.StealNSResult(); + } + NS_WARNING_ASSERTION( + !ignoredError.Failed(), + "OnStartToHandleTopLevelEditSubAction() failed, but ignored"); + + nsresult rv = EnsureNoPaddingBRElementForEmptyEditor(); + if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " + "failed, but ignored"); + + if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) { + nsresult rv = EnsureCaretNotAfterInvisibleBRElement(); + if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " + "failed, but ignored"); + if (NS_SUCCEEDED(rv)) { + nsresult rv = PrepareInlineStylesForCaret(); + if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); + } + } + + rv = InsertTextAsSubAction(quotedStuff, SelectionHandling::Delete); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::InsertTextAsSubAction() failed"); + return rv; +} + +NS_IMETHODIMP HTMLEditor::InsertTextWithQuotations( + const nsAString& aStringToInsert) { + AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText); + MOZ_ASSERT(!aStringToInsert.IsVoid()); + editActionData.SetData(aStringToInsert); + nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); + if (NS_FAILED(rv)) { + NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, + "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); + return EditorBase::ToGenericNSResult(rv); + } + if (aStringToInsert.IsEmpty()) { + return NS_OK; + } + + // The whole operation should be undoable in one transaction: + // XXX Why isn't enough to use only AutoPlaceholderBatch here? + AutoTransactionBatch bundleAllTransactions(*this, __FUNCTION__); + AutoPlaceholderBatch treatAsOneTransaction( + *this, ScrollSelectionIntoView::Yes, __FUNCTION__); + + rv = InsertTextWithQuotationsInternal(aStringToInsert); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::InsertTextWithQuotationsInternal() failed"); + return EditorBase::ToGenericNSResult(rv); +} + +nsresult HTMLEditor::InsertTextWithQuotationsInternal( + const nsAString& aStringToInsert) { + MOZ_ASSERT(!aStringToInsert.IsEmpty()); + // We're going to loop over the string, collecting up a "hunk" + // that's all the same type (quoted or not), + // Whenever the quotedness changes (or we reach the string's end) + // we will insert the hunk all at once, quoted or non. + static const char16_t cite('>'); + bool curHunkIsQuoted = (aStringToInsert.First() == cite); + + nsAString::const_iterator hunkStart, strEnd; + aStringToInsert.BeginReading(hunkStart); + aStringToInsert.EndReading(strEnd); + + // In the loop below, we only look for DOM newlines (\n), + // because we don't have a FindChars method that can look + // for both \r and \n. \r is illegal in the dom anyway, + // but in debug builds, let's take the time to verify that + // there aren't any there: +#ifdef DEBUG + nsAString::const_iterator dbgStart(hunkStart); + if (FindCharInReadable(HTMLEditUtils::kCarriageReturn, dbgStart, strEnd)) { + NS_ASSERTION( + false, + "Return characters in DOM! InsertTextWithQuotations may be wrong"); + } +#endif /* DEBUG */ + + // Loop over lines: + nsresult rv = NS_OK; + nsAString::const_iterator lineStart(hunkStart); + // We will break from inside when we run out of newlines. + for (;;) { + // Search for the end of this line (dom newlines, see above): + bool found = FindCharInReadable(HTMLEditUtils::kNewLine, lineStart, strEnd); + bool quoted = false; + if (found) { + // if there's another newline, lineStart now points there. + // Loop over any consecutive newline chars: + nsAString::const_iterator firstNewline(lineStart); + while (*lineStart == HTMLEditUtils::kNewLine) { + ++lineStart; + } + quoted = (*lineStart == cite); + if (quoted == curHunkIsQuoted) { + continue; + } + // else we're changing state, so we need to insert + // from curHunk to lineStart then loop around. + + // But if the current hunk is quoted, then we want to make sure + // that any extra newlines on the end do not get included in + // the quoted section: blank lines flaking a quoted section + // should be considered unquoted, so that if the user clicks + // there and starts typing, the new text will be outside of + // the quoted block. + if (curHunkIsQuoted) { + lineStart = firstNewline; + + // 'firstNewline' points to the first '\n'. We want to + // ensure that this first newline goes into the hunk + // since quoted hunks can be displayed as blocks + // (and the newline should become invisible in this case). + // So the next line needs to start at the next character. + lineStart++; + } + } + + // If no newline found, lineStart is now strEnd and we can finish up, + // inserting from curHunk to lineStart then returning. + const nsAString& curHunk = Substring(hunkStart, lineStart); + nsCOMPtr dummyNode; + if (curHunkIsQuoted) { + rv = + InsertAsPlaintextQuotation(curHunk, false, getter_AddRefs(dummyNode)); + if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::InsertAsPlaintextQuotation() failed, " + "but might be ignored"); + } else { + rv = InsertTextAsSubAction(curHunk, SelectionHandling::Delete); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "EditorBase::InsertTextAsSubAction() failed, but might be ignored"); + } + if (!found) { + break; + } + curHunkIsQuoted = quoted; + hunkStart = lineStart; + } + + // XXX This returns the last result of InsertAsPlaintextQuotation() or + // InsertTextAsSubAction() in the loop. This must be a bug. + return rv; +} + +nsresult HTMLEditor::InsertAsQuotation(const nsAString& aQuotedText, + nsINode** aNodeInserted) { + if (IsInPlaintextMode()) { + AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText); + MOZ_ASSERT(!aQuotedText.IsVoid()); + editActionData.SetData(aQuotedText); + nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); + if (NS_FAILED(rv)) { + NS_WARNING_ASSERTION( + rv == NS_ERROR_EDITOR_ACTION_CANCELED, + "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); + return EditorBase::ToGenericNSResult(rv); + } + AutoPlaceholderBatch treatAsOneTransaction( + *this, ScrollSelectionIntoView::Yes, __FUNCTION__); + rv = InsertAsPlaintextQuotation(aQuotedText, true, aNodeInserted); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::InsertAsPlaintextQuotation() failed"); + return EditorBase::ToGenericNSResult(rv); + } + + AutoEditActionDataSetter editActionData(*this, + EditAction::eInsertBlockquoteElement); + nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); + if (NS_FAILED(rv)) { + NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, + "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); + return EditorBase::ToGenericNSResult(rv); + } + + AutoPlaceholderBatch treatAsOneTransaction( + *this, ScrollSelectionIntoView::Yes, __FUNCTION__); + nsAutoString citation; + rv = InsertAsCitedQuotationInternal(aQuotedText, citation, false, + aNodeInserted); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::InsertAsCitedQuotationInternal() failed"); + return EditorBase::ToGenericNSResult(rv); +} + +// Insert plaintext as a quotation, with cite marks (e.g. "> "). +// This differs from its corresponding method in TextEditor +// in that here, quoted material is enclosed in a
 tag
+// in order to preserve the original line wrapping.
+nsresult HTMLEditor::InsertAsPlaintextQuotation(const nsAString& aQuotedText,
+                                                bool aAddCites,
+                                                nsINode** aNodeInserted) {
+  MOZ_ASSERT(IsEditActionDataAvailable());
+
+  if (aNodeInserted) {
+    *aNodeInserted = nullptr;
+  }
+
+  {
+    Result result = CanHandleHTMLEditSubAction();
+    if (MOZ_UNLIKELY(result.isErr())) {
+      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
+      return result.unwrapErr();
+    }
+    if (result.inspect().Canceled()) {
+      return NS_OK;
+    }
+  }
+
+  UndefineCaretBidiLevel();
+
+  IgnoredErrorResult ignoredError;
+  AutoEditSubActionNotifier startToHandleEditSubAction(
+      *this, EditSubAction::eInsertQuotation, nsIEditor::eNext, ignoredError);
+  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
+    return ignoredError.StealNSResult();
+  }
+  NS_WARNING_ASSERTION(
+      !ignoredError.Failed(),
+      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
+
+  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
+  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+    return NS_ERROR_EDITOR_DESTROYED;
+  }
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
+                       "failed, but ignored");
+
+  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
+    nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
+    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+      return NS_ERROR_EDITOR_DESTROYED;
+    }
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
+                         "failed, but ignored");
+    if (NS_SUCCEEDED(rv)) {
+      nsresult rv = PrepareInlineStylesForCaret();
+      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+        return NS_ERROR_EDITOR_DESTROYED;
+      }
+      NS_WARNING_ASSERTION(
+          NS_SUCCEEDED(rv),
+          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
+    }
+  }
+
+  // Wrap the inserted quote in a  so we can distinguish it. If we're
+  // inserting into the , we use a  which is displayed as a block
+  // and sized to the screen using 98 viewport width units.
+  // We could use 100vw, but 98vw avoids a horizontal scroll bar where possible.
+  // All this is done to wrap overlong lines to the screen and not to the
+  // container element, the width-restricted body.
+  Result, nsresult> spanElementOrError =
+      DeleteSelectionAndCreateElement(
+          *nsGkAtoms::span, [](HTMLEditor&, Element& aSpanElement,
+                               const EditorDOMPoint& aPointToInsert) {
+            // Add an attribute on the pre node so we'll know it's a quotation.
+            DebugOnly rvIgnored = aSpanElement.SetAttr(
+                kNameSpaceID_None, nsGkAtoms::mozquote, u"true"_ns,
+                aSpanElement.IsInComposedDoc());
+            NS_WARNING_ASSERTION(
+                NS_SUCCEEDED(rvIgnored),
+                nsPrintfCString(
+                    "Element::SetAttr(nsGkAtoms::mozquote, \"true\", %s) "
+                    "failed",
+                    aSpanElement.IsInComposedDoc() ? "true" : "false")
+                    .get());
+            // Allow wrapping on spans so long lines get wrapped to the screen.
+            if (aPointToInsert.IsContainerHTMLElement(nsGkAtoms::body)) {
+              DebugOnly rvIgnored = aSpanElement.SetAttr(
+                  kNameSpaceID_None, nsGkAtoms::style,
+                  nsLiteralString(u"white-space: pre-wrap; display: block; "
+                                  u"width: 98vw;"),
+                  false);
+              NS_WARNING_ASSERTION(
+                  NS_SUCCEEDED(rvIgnored),
+                  "Element::SetAttr(nsGkAtoms::style, \"pre-wrap, block\", "
+                  "false) failed, but ignored");
+            } else {
+              DebugOnly rvIgnored =
+                  aSpanElement.SetAttr(kNameSpaceID_None, nsGkAtoms::style,
+                                       u"white-space: pre-wrap;"_ns, false);
+              NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                                   "Element::SetAttr(nsGkAtoms::style, "
+                                   "\"pre-wrap\", false) failed, but ignored");
+            }
+            return NS_OK;
+          });
+  NS_WARNING_ASSERTION(spanElementOrError.isOk(),
+                       "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::"
+                       "span) failed, but ignored");
+
+  // If this succeeded, then set selection inside the pre
+  // so the inserted text will end up there.
+  // If it failed, we don't care what the return value was,
+  // but we'll fall through and try to insert the text anyway.
+  if (spanElementOrError.isOk()) {
+    MOZ_ASSERT(spanElementOrError.inspect());
+    rv = CollapseSelectionToStartOf(
+        MOZ_KnownLive(*spanElementOrError.inspect()));
+    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
+      NS_WARNING(
+          "EditorBase::CollapseSelectionToStartOf() caused destroying the "
+          "editor");
+      return NS_ERROR_EDITOR_DESTROYED;
+    }
+    NS_WARNING_ASSERTION(
+        NS_SUCCEEDED(rv),
+        "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
+  }
+
+  // TODO: We should insert text at specific point rather than at selection.
+  //       Then, we can do this before inserting the  element.
+  if (aAddCites) {
+    rv = InsertWithQuotationsAsSubAction(aQuotedText);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("HTMLEditor::InsertWithQuotationsAsSubAction() failed");
+      return rv;
+    }
+  } else {
+    rv = InsertTextAsSubAction(aQuotedText, SelectionHandling::Delete);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("EditorBase::InsertTextAsSubAction() failed");
+      return rv;
+    }
+  }
+
+  // XXX Why don't we check this before inserting the quoted text?
+  if (spanElementOrError.isErr()) {
+    return NS_OK;
+  }
+
+  // Set the selection to just after the inserted node:
+  EditorRawDOMPoint afterNewSpanElement(
+      EditorRawDOMPoint::After(*spanElementOrError.inspect()));
+  NS_WARNING_ASSERTION(
+      afterNewSpanElement.IsSet(),
+      "Failed to set after the new  element, but ignored");
+  if (afterNewSpanElement.IsSet()) {
+    nsresult rv = CollapseSelectionTo(afterNewSpanElement);
+    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
+      NS_WARNING(
+          "EditorBase::CollapseSelectionTo() caused destroying the editor");
+      return NS_ERROR_EDITOR_DESTROYED;
+    }
+    NS_WARNING_ASSERTION(
+        NS_SUCCEEDED(rv),
+        "EditorBase::CollapseSelectionTo() failed, but ignored");
+  }
+
+  // Note that if !aAddCites, aNodeInserted isn't set.
+  // That's okay because the routines that use aAddCites
+  // don't need to know the inserted node.
+  if (aNodeInserted) {
+    spanElementOrError.unwrap().forget(aNodeInserted);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP HTMLEditor::Rewrap(bool aRespectNewlines) {
+  AutoEditActionDataSetter editActionData(*this, EditAction::eRewrap);
+  nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
+  if (NS_FAILED(rv)) {
+    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
+                         "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
+    return EditorBase::ToGenericNSResult(rv);
+  }
+
+  // Rewrap makes no sense if there's no wrap column; default to 72.
+  int32_t wrapWidth = WrapWidth();
+  if (wrapWidth <= 0) {
+    wrapWidth = 72;
+  }
+
+  nsAutoString current;
+  const bool isCollapsed = SelectionRef().IsCollapsed();
+  uint32_t flags = nsIDocumentEncoder::OutputFormatted |
+                   nsIDocumentEncoder::OutputLFLineBreak;
+  if (!isCollapsed) {
+    flags |= nsIDocumentEncoder::OutputSelectionOnly;
+  }
+  rv = ComputeValueInternal(u"text/plain"_ns, flags, current);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("EditorBase::ComputeValueInternal(text/plain) failed");
+    return EditorBase::ToGenericNSResult(rv);
+  }
+
+  if (current.IsEmpty()) {
+    return NS_OK;
+  }
+
+  nsString wrapped;
+  uint32_t firstLineOffset = 0;  // XXX need to reset this if there is a
+                                 //     selection
+  InternetCiter::Rewrap(current, wrapWidth, firstLineOffset, aRespectNewlines,
+                        wrapped);
+
+  if (wrapped.IsEmpty()) {
+    return NS_OK;
+  }
+
+  if (isCollapsed) {
+    DebugOnly rvIgnored = SelectAllInternal();
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                         "HTMLEditor::SelectAllInternal() failed");
+  }
+
+  // The whole operation in InsertTextWithQuotationsInternal() should be
+  // undoable in one transaction.
+  // XXX Why isn't enough to use only AutoPlaceholderBatch here?
+  AutoTransactionBatch bundleAllTransactions(*this, __FUNCTION__);
+  AutoPlaceholderBatch treatAsOneTransaction(
+      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
+  rv = InsertTextWithQuotationsInternal(wrapped);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                       "HTMLEditor::InsertTextWithQuotationsInternal() failed");
+  return EditorBase::ToGenericNSResult(rv);
+}
+
+NS_IMETHODIMP HTMLEditor::InsertAsCitedQuotation(const nsAString& aQuotedText,
+                                                 const nsAString& aCitation,
+                                                 bool aInsertHTML,
+                                                 nsINode** aNodeInserted) {
+  // Don't let anyone insert HTML when we're in plaintext mode.
+  if (IsInPlaintextMode()) {
+    NS_ASSERTION(
+        !aInsertHTML,
+        "InsertAsCitedQuotation: trying to insert html into plaintext editor");
+
+    AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText);
+    MOZ_ASSERT(!aQuotedText.IsVoid());
+    editActionData.SetData(aQuotedText);
+    nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
+    if (NS_FAILED(rv)) {
+      NS_WARNING_ASSERTION(
+          rv == NS_ERROR_EDITOR_ACTION_CANCELED,
+          "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
+      return EditorBase::ToGenericNSResult(rv);
+    }
+
+    AutoPlaceholderBatch treatAsOneTransaction(
+        *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
+    rv = InsertAsPlaintextQuotation(aQuotedText, true, aNodeInserted);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                         "HTMLEditor::InsertAsPlaintextQuotation() failed");
+    return EditorBase::ToGenericNSResult(rv);
+  }
+
+  AutoEditActionDataSetter editActionData(*this,
+                                          EditAction::eInsertBlockquoteElement);
+  nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
+  if (NS_FAILED(rv)) {
+    NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
+                         "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
+    return EditorBase::ToGenericNSResult(rv);
+  }
+
+  AutoPlaceholderBatch treatAsOneTransaction(
+      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
+  rv = InsertAsCitedQuotationInternal(aQuotedText, aCitation, aInsertHTML,
+                                      aNodeInserted);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                       "HTMLEditor::InsertAsCitedQuotationInternal() failed");
+  return EditorBase::ToGenericNSResult(rv);
+}
+
+nsresult HTMLEditor::InsertAsCitedQuotationInternal(
+    const nsAString& aQuotedText, const nsAString& aCitation, bool aInsertHTML,
+    nsINode** aNodeInserted) {
+  MOZ_ASSERT(IsEditActionDataAvailable());
+  MOZ_ASSERT(!IsInPlaintextMode());
+
+  {
+    Result result = CanHandleHTMLEditSubAction();
+    if (MOZ_UNLIKELY(result.isErr())) {
+      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
+      return result.unwrapErr();
+    }
+    if (result.inspect().Canceled()) {
+      return NS_OK;
+    }
+  }
+
+  UndefineCaretBidiLevel();
+
+  IgnoredErrorResult ignoredError;
+  AutoEditSubActionNotifier startToHandleEditSubAction(
+      *this, EditSubAction::eInsertQuotation, nsIEditor::eNext, ignoredError);
+  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
+    return ignoredError.StealNSResult();
+  }
+  NS_WARNING_ASSERTION(
+      !ignoredError.Failed(),
+      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
+
+  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
+  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+    return NS_ERROR_EDITOR_DESTROYED;
+  }
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
+                       "failed, but ignored");
+
+  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
+    nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
+    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+      return NS_ERROR_EDITOR_DESTROYED;
+    }
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
+                         "failed, but ignored");
+    if (NS_SUCCEEDED(rv)) {
+      nsresult rv = PrepareInlineStylesForCaret();
+      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+        return NS_ERROR_EDITOR_DESTROYED;
+      }
+      NS_WARNING_ASSERTION(
+          NS_SUCCEEDED(rv),
+          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
+    }
+  }
+
+  Result, nsresult> blockquoteElementOrError =
+      DeleteSelectionAndCreateElement(
+          *nsGkAtoms::blockquote,
+          // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
+          [&aCitation](HTMLEditor&, Element& aBlockquoteElement,
+                       const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+            // Try to set type=cite.  Ignore it if this fails.
+            DebugOnly rvIgnored = aBlockquoteElement.SetAttr(
+                kNameSpaceID_None, nsGkAtoms::type, u"cite"_ns,
+                aBlockquoteElement.IsInComposedDoc());
+            NS_WARNING_ASSERTION(
+                NS_SUCCEEDED(rvIgnored),
+                nsPrintfCString(
+                    "Element::SetAttr(nsGkAtoms::type, \"cite\", %s) failed, "
+                    "but ignored",
+                    aBlockquoteElement.IsInComposedDoc() ? "true" : "false")
+                    .get());
+            if (!aCitation.IsEmpty()) {
+              DebugOnly rvIgnored = aBlockquoteElement.SetAttr(
+                  kNameSpaceID_None, nsGkAtoms::cite, aCitation,
+                  aBlockquoteElement.IsInComposedDoc());
+              NS_WARNING_ASSERTION(
+                  NS_SUCCEEDED(rvIgnored),
+                  nsPrintfCString(
+                      "Element::SetAttr(nsGkAtoms::cite, \"...\", %s) failed, "
+                      "but ignored",
+                      aBlockquoteElement.IsInComposedDoc() ? "true" : "false")
+                      .get());
+            }
+            return NS_OK;
+          });
+  if (MOZ_UNLIKELY(blockquoteElementOrError.isErr() ||
+                   NS_WARN_IF(Destroyed()))) {
+    NS_WARNING(
+        "HTMLEditor::DeleteSelectionAndCreateElement(nsGkAtoms::blockquote) "
+        "failed");
+    return Destroyed() ? NS_ERROR_EDITOR_DESTROYED
+                       : blockquoteElementOrError.unwrapErr();
+  }
+  MOZ_ASSERT(blockquoteElementOrError.inspect());
+
+  // Set the selection inside the blockquote so aQuotedText will go there:
+  rv = CollapseSelectionTo(
+      EditorRawDOMPoint(blockquoteElementOrError.inspect(), 0u));
+  if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
+    NS_WARNING(
+        "EditorBase::CollapseSelectionTo() caused destroying the editor");
+    return NS_ERROR_EDITOR_DESTROYED;
+  }
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                       "EditorBase::CollapseSelectionTo() failed, but ignored");
+
+  // TODO: We should insert text at specific point rather than at selection.
+  //       Then, we can do this before inserting the 
element. + if (aInsertHTML) { + rv = LoadHTML(aQuotedText); + if (NS_WARN_IF(Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; + } + if (NS_FAILED(rv)) { + NS_WARNING("HTMLEditor::LoadHTML() failed"); + return rv; + } + } else { + rv = InsertTextAsSubAction( + aQuotedText, SelectionHandling::Delete); // XXX ignore charset + if (NS_WARN_IF(Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; + } + if (NS_FAILED(rv)) { + NS_WARNING("HTMLEditor::LoadHTML() failed"); + return rv; + } + } + + // Set the selection to just after the inserted node: + EditorRawDOMPoint afterNewBlockquoteElement( + EditorRawDOMPoint::After(blockquoteElementOrError.inspect())); + NS_WARNING_ASSERTION( + afterNewBlockquoteElement.IsSet(), + "Failed to set after new
element, but ignored"); + if (afterNewBlockquoteElement.IsSet()) { + nsresult rv = CollapseSelectionTo(afterNewBlockquoteElement); + if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { + NS_WARNING( + "EditorBase::CollapseSelectionTo() caused destroying the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "EditorBase::CollapseSelectionTo() failed, but ignored"); + } + + if (aNodeInserted) { + blockquoteElementOrError.unwrap().forget(aNodeInserted); + } + + return NS_OK; +} + +void HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: + RemoveHeadChildAndStealBodyChildsChildren(nsINode& aNode) { + nsCOMPtr body, head; + // find the body and head nodes if any. + // look only at immediate children of aNode. + for (nsCOMPtr child = aNode.GetFirstChild(); child; + child = child->GetNextSibling()) { + if (child->IsHTMLElement(nsGkAtoms::body)) { + body = child; + } else if (child->IsHTMLElement(nsGkAtoms::head)) { + head = child; + } + } + if (head) { + ErrorResult ignored; + aNode.RemoveChild(*head, ignored); + } + if (body) { + nsCOMPtr child = body->GetFirstChild(); + while (child) { + ErrorResult ignored; + aNode.InsertBefore(*child, body, ignored); + child = body->GetFirstChild(); + } + + ErrorResult ignored; + aNode.RemoveChild(*body, ignored); + } +} + +// static +void HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: + RemoveIncompleteDescendantsFromInsertingFragment(nsINode& aNode) { + nsIContent* child = aNode.GetFirstChild(); + while (child) { + bool isEmptyNodeShouldNotInserted = false; + if (HTMLEditUtils::IsAnyListElement(child)) { + // Current limitation of HTMLEditor: + // Cannot put caret in a list element which does not have list item + // element even as a descendant. I.e., HTMLEditor does not support + // editing in such empty list element, and does not support to delete + // it from outside. Therefore, HTMLWithContextInserter should not + // insert empty list element. + isEmptyNodeShouldNotInserted = HTMLEditUtils::IsEmptyNode( + *child, + {// Although we don't check relation between list item element + // and parent list element, but it should not be a problem in the + // wild because appearing such invalid list element is an edge case + // and anyway HTMLEditor supports editing in them. + EmptyCheckOption::TreatListItemAsVisible, + // Ignore editable state because non-editable list item element + // may make the list element visible. Although HTMLEditor does not + // support to edit list elements which have only non-editable list + // item elements, but it should be deleted from outside. + // TODO: Currently, HTMLEditor does not support deleting such list + // element with Backspace. We should fix this. + EmptyCheckOption::IgnoreEditableState}); + } + // TODO: Perhaps, we should delete
s if they have no
/ + // element, or something other elements which must have specific + // children but they don't. + if (isEmptyNodeShouldNotInserted) { + nsIContent* nextChild = child->GetNextSibling(); + OwningNonNull removingChild(*child); + removingChild->Remove(); + child = nextChild; + continue; + } + if (child->HasChildNodes()) { + RemoveIncompleteDescendantsFromInsertingFragment(*child); + } + child = child->GetNextSibling(); + } +} + +// static +bool HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: + IsInsertionCookie(const nsIContent& aContent) { + // Is this child the magical cookie? + if (const auto* comment = Comment::FromNode(&aContent)) { + nsAutoString data; + comment->GetData(data); + + return data.EqualsLiteral(kInsertCookie); + } + + return false; +} + +/** + * This function finds the target node that we will be pasting into. aStart is + * the context that we're given and aResult will be the target. Initially, + * *aResult must be nullptr. + * + * The target for a paste is found by either finding the node that contains + * the magical comment node containing kInsertCookie or, failing that, the + * firstChild of the firstChild (until we reach a leaf). + */ +bool HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: + FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie( + nsINode& aStart, nsCOMPtr& aResult) { + nsIContent* firstChild = aStart.GetFirstChild(); + if (!firstChild) { + // If the current result is nullptr, then aStart is a leaf, and is the + // fallback result. + if (!aResult) { + aResult = &aStart; + } + return false; + } + + for (nsCOMPtr child = firstChild; child; + child = child->GetNextSibling()) { + if (FragmentFromPasteCreator::IsInsertionCookie(*child)) { + // Yes it is! Return an error so we bubble out and short-circuit the + // search. + aResult = &aStart; + + child->Remove(); + + return true; + } + + if (FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie(*child, + aResult)) { + return true; + } + } + + return false; +} + +class MOZ_STACK_CLASS HTMLEditor::HTMLWithContextInserter::FragmentParser + final { + public: + FragmentParser(const Document& aDocument, SafeToInsertData aSafeToInsertData); + + [[nodiscard]] nsresult ParseContext(const nsAString& aContextString, + DocumentFragment** aFragment); + + [[nodiscard]] nsresult ParsePastedHTML(const nsAString& aInputString, + nsAtom* aContextLocalNameAtom, + DocumentFragment** aFragment); + + private: + static nsresult ParseFragment(const nsAString& aStr, + nsAtom* aContextLocalName, + const Document* aTargetDoc, + dom::DocumentFragment** aFragment, + SafeToInsertData aSafeToInsertData); + + const Document& mDocument; + const SafeToInsertData mSafeToInsertData; +}; + +HTMLEditor::HTMLWithContextInserter::FragmentParser::FragmentParser( + const Document& aDocument, SafeToInsertData aSafeToInsertData) + : mDocument{aDocument}, mSafeToInsertData{aSafeToInsertData} {} + +nsresult HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext( + const nsAString& aContextStr, DocumentFragment** aFragment) { + return FragmentParser::ParseFragment(aContextStr, nullptr, &mDocument, + aFragment, mSafeToInsertData); +} + +nsresult HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML( + const nsAString& aInputString, nsAtom* aContextLocalNameAtom, + DocumentFragment** aFragment) { + return FragmentParser::ParseFragment(aInputString, aContextLocalNameAtom, + &mDocument, aFragment, + mSafeToInsertData); +} + +nsresult HTMLEditor::HTMLWithContextInserter::CreateDOMFragmentFromPaste( + const nsAString& aInputString, const nsAString& aContextStr, + const nsAString& aInfoStr, nsCOMPtr* aOutFragNode, + nsCOMPtr* aOutStartNode, nsCOMPtr* aOutEndNode, + uint32_t* aOutStartOffset, uint32_t* aOutEndOffset, + SafeToInsertData aSafeToInsertData) const { + if (NS_WARN_IF(!aOutFragNode) || NS_WARN_IF(!aOutStartNode) || + NS_WARN_IF(!aOutEndNode) || NS_WARN_IF(!aOutStartOffset) || + NS_WARN_IF(!aOutEndOffset)) { + return NS_ERROR_INVALID_ARG; + } + + RefPtr document = mHTMLEditor.GetDocument(); + if (NS_WARN_IF(!document)) { + return NS_ERROR_FAILURE; + } + + FragmentFromPasteCreator fragmentFromPasteCreator; + + const nsresult rv = fragmentFromPasteCreator.Run( + *document, aInputString, aContextStr, aInfoStr, aOutFragNode, + aOutStartNode, aOutEndNode, aSafeToInsertData); + + *aOutStartOffset = 0; + *aOutEndOffset = (*aOutEndNode)->Length(); + + return rv; +} + +// static +nsAtom* HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: + DetermineContextLocalNameForParsingPastedHTML( + const nsIContent* aParentContentOfPastedHTMLInContext) { + if (!aParentContentOfPastedHTMLInContext) { + return nsGkAtoms::body; + } + + nsAtom* contextLocalNameAtom = + aParentContentOfPastedHTMLInContext->NodeInfo()->NameAtom(); + + return (aParentContentOfPastedHTMLInContext->IsHTMLElement(nsGkAtoms::html)) + ? nsGkAtoms::body + : contextLocalNameAtom; +} + +// static +nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: + MergeAndPostProcessFragmentsForPastedHTMLAndContext( + DocumentFragment& aDocumentFragmentForPastedHTML, + DocumentFragment& aDocumentFragmentForContext, + nsIContent& aTargetContentOfContextForPastedHTML) { + FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren( + aDocumentFragmentForPastedHTML); + + FragmentFromPasteCreator::RemoveIncompleteDescendantsFromInsertingFragment( + aDocumentFragmentForPastedHTML); + + // unite the two trees + IgnoredErrorResult ignoredError; + aTargetContentOfContextForPastedHTML.AppendChild( + aDocumentFragmentForPastedHTML, ignoredError); + NS_WARNING_ASSERTION(!ignoredError.Failed(), + "nsINode::AppendChild() failed, but ignored"); + const nsresult rv = FragmentFromPasteCreator:: + RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces( + aDocumentFragmentForContext, NodesToRemove::eOnlyListItems); + + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" + "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces()" + " failed"); + return rv; + } + + return rv; +} + +// static +nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: + PostProcessFragmentForPastedHTMLWithoutContext( + DocumentFragment& aDocumentFragmentForPastedHTML) { + FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren( + aDocumentFragmentForPastedHTML); + + FragmentFromPasteCreator::RemoveIncompleteDescendantsFromInsertingFragment( + aDocumentFragmentForPastedHTML); + + const nsresult rv = FragmentFromPasteCreator:: + RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces( + aDocumentFragmentForPastedHTML, NodesToRemove::eOnlyListItems); + + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" + "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces() " + "failed"); + return rv; + } + + return rv; +} + +// static +nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: + PreProcessContextDocumentFragmentForMerging( + DocumentFragment& aDocumentFragmentForContext) { + // The context is expected to contain text nodes only in block level + // elements. Hence, if they contain only whitespace, they're invisible. + const nsresult rv = FragmentFromPasteCreator:: + RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces( + aDocumentFragmentForContext, NodesToRemove::eAll); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" + "RemoveNonPreWhiteSpaceOnlyTextNodesForIgnoringInvisibleWhiteSpaces() " + "failed"); + return rv; + } + + FragmentFromPasteCreator::RemoveHeadChildAndStealBodyChildsChildren( + aDocumentFragmentForContext); + + return rv; +} + +nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: + CreateDocumentFragmentAndGetParentOfPastedHTMLInContext( + const Document& aDocument, const nsAString& aInputString, + const nsAString& aContextStr, SafeToInsertData aSafeToInsertData, + nsCOMPtr& aParentNodeOfPastedHTMLInContext, + RefPtr& aDocumentFragmentToInsert) const { + // if we have context info, create a fragment for that + RefPtr documentFragmentForContext; + + FragmentParser fragmentParser{aDocument, aSafeToInsertData}; + if (!aContextStr.IsEmpty()) { + nsresult rv = fragmentParser.ParseContext( + aContextStr, getter_AddRefs(documentFragmentForContext)); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext() " + "failed"); + return rv; + } + if (!documentFragmentForContext) { + NS_WARNING( + "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseContext() " + "returned nullptr"); + return NS_ERROR_FAILURE; + } + + rv = FragmentFromPasteCreator::PreProcessContextDocumentFragmentForMerging( + *documentFragmentForContext); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" + "PreProcessContextDocumentFragmentForMerging() failed."); + return rv; + } + + FragmentFromPasteCreator:: + FindTargetNodeOfContextForPastedHTMLAndRemoveInsertionCookie( + *documentFragmentForContext, aParentNodeOfPastedHTMLInContext); + MOZ_ASSERT(aParentNodeOfPastedHTMLInContext); + } + + nsCOMPtr parentContentOfPastedHTMLInContext = + nsIContent::FromNodeOrNull(aParentNodeOfPastedHTMLInContext); + MOZ_ASSERT_IF(aParentNodeOfPastedHTMLInContext, + parentContentOfPastedHTMLInContext); + + nsAtom* contextLocalNameAtom = + FragmentFromPasteCreator::DetermineContextLocalNameForParsingPastedHTML( + parentContentOfPastedHTMLInContext); + RefPtr documentFragmentForPastedHTML; + nsresult rv = fragmentParser.ParsePastedHTML( + aInputString, contextLocalNameAtom, + getter_AddRefs(documentFragmentForPastedHTML)); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML()" + " failed"); + return rv; + } + if (!documentFragmentForPastedHTML) { + NS_WARNING( + "HTMLEditor::HTMLWithContextInserter::FragmentParser::ParsePastedHTML()" + " returned nullptr"); + return NS_ERROR_FAILURE; + } + + if (aParentNodeOfPastedHTMLInContext) { + const nsresult rv = FragmentFromPasteCreator:: + MergeAndPostProcessFragmentsForPastedHTMLAndContext( + *documentFragmentForPastedHTML, *documentFragmentForContext, + *parentContentOfPastedHTMLInContext); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" + "MergeAndPostProcessFragmentsForPastedHTMLAndContext() failed."); + return rv; + } + aDocumentFragmentToInsert = std::move(documentFragmentForContext); + } else { + const nsresult rv = PostProcessFragmentForPastedHTMLWithoutContext( + *documentFragmentForPastedHTML); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" + "PostProcessFragmentForPastedHTMLWithoutContext() failed."); + return rv; + } + + aDocumentFragmentToInsert = std::move(documentFragmentForPastedHTML); + } + + return rv; +} + +nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::Run( + const Document& aDocument, const nsAString& aInputString, + const nsAString& aContextStr, const nsAString& aInfoStr, + nsCOMPtr* aOutFragNode, nsCOMPtr* aOutStartNode, + nsCOMPtr* aOutEndNode, SafeToInsertData aSafeToInsertData) const { + MOZ_ASSERT(aOutFragNode); + MOZ_ASSERT(aOutStartNode); + MOZ_ASSERT(aOutEndNode); + + nsCOMPtr parentNodeOfPastedHTMLInContext; + RefPtr documentFragmentToInsert; + nsresult rv = CreateDocumentFragmentAndGetParentOfPastedHTMLInContext( + aDocument, aInputString, aContextStr, aSafeToInsertData, + parentNodeOfPastedHTMLInContext, documentFragmentToInsert); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" + "CreateDocumentFragmentAndGetParentOfPastedHTMLInContext() failed."); + return rv; + } + + // If there was no context, then treat all of the data we did get as the + // pasted data. + if (parentNodeOfPastedHTMLInContext) { + *aOutEndNode = *aOutStartNode = parentNodeOfPastedHTMLInContext; + } else { + *aOutEndNode = *aOutStartNode = documentFragmentToInsert; + } + + *aOutFragNode = std::move(documentFragmentToInsert); + + if (!aInfoStr.IsEmpty()) { + const nsresult rv = + FragmentFromPasteCreator::MoveStartAndEndAccordingToHTMLInfo( + aInfoStr, aOutStartNode, aOutEndNode); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator::" + "MoveStartAndEndAccordingToHTMLInfo() failed"); + return rv; + } + } + + return NS_OK; +} + +// static +nsresult HTMLEditor::HTMLWithContextInserter::FragmentFromPasteCreator:: + MoveStartAndEndAccordingToHTMLInfo(const nsAString& aInfoStr, + nsCOMPtr* aOutStartNode, + nsCOMPtr* aOutEndNode) { + int32_t sep = aInfoStr.FindChar((char16_t)','); + nsAutoString numstr1(Substring(aInfoStr, 0, sep)); + nsAutoString numstr2( + Substring(aInfoStr, sep + 1, aInfoStr.Length() - (sep + 1))); + + // Move the start and end children. + nsresult rvIgnored; + int32_t num = numstr1.ToInteger(&rvIgnored); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "nsAString::ToInteger() failed, but ignored"); + while (num--) { + nsINode* tmp = (*aOutStartNode)->GetFirstChild(); + if (!tmp) { + NS_WARNING("aOutStartNode did not have children"); + return NS_ERROR_FAILURE; + } + *aOutStartNode = tmp; + } + + num = numstr2.ToInteger(&rvIgnored); + while (num--) { + nsINode* tmp = (*aOutEndNode)->GetLastChild(); + if (!tmp) { + NS_WARNING("aOutEndNode did not have children"); + return NS_ERROR_FAILURE; + } + *aOutEndNode = tmp; + } + + return NS_OK; +} + +// static +nsresult HTMLEditor::HTMLWithContextInserter::FragmentParser::ParseFragment( + const nsAString& aFragStr, nsAtom* aContextLocalName, + const Document* aTargetDocument, DocumentFragment** aFragment, + SafeToInsertData aSafeToInsertData) { + nsAutoScriptBlockerSuppressNodeRemoved autoBlocker; + + nsCOMPtr doc = + nsContentUtils::CreateInertHTMLDocument(aTargetDocument); + if (!doc) { + return NS_ERROR_FAILURE; + } + RefPtr fragment = + new (doc->NodeInfoManager()) DocumentFragment(doc->NodeInfoManager()); + nsresult rv = nsContentUtils::ParseFragmentHTML( + aFragStr, fragment, + aContextLocalName ? aContextLocalName : nsGkAtoms::body, + kNameSpaceID_XHTML, false, true); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "nsContentUtils::ParseFragmentHTML() failed"); + if (aSafeToInsertData == SafeToInsertData::No) { + nsTreeSanitizer sanitizer(aContextLocalName + ? nsIParserUtils::SanitizerAllowStyle + : nsIParserUtils::SanitizerAllowComments); + sanitizer.Sanitize(fragment); + } + fragment.forget(aFragment); + return rv; +} + +// static +void HTMLEditor::HTMLWithContextInserter:: + CollectTopMostChildContentsCompletelyInRange( + const EditorRawDOMPoint& aStartPoint, + const EditorRawDOMPoint& aEndPoint, + nsTArray>& aOutArrayOfContents) { + MOZ_ASSERT(aStartPoint.IsSetAndValid()); + MOZ_ASSERT(aEndPoint.IsSetAndValid()); + + RefPtr range = + nsRange::Create(aStartPoint.ToRawRangeBoundary(), + aEndPoint.ToRawRangeBoundary(), IgnoreErrors()); + if (!range) { + NS_WARNING("nsRange::Create() failed"); + return; + } + DOMSubtreeIterator iter; + if (NS_FAILED(iter.Init(*range))) { + NS_WARNING("DOMSubtreeIterator::Init() failed, but ignored"); + return; + } + + iter.AppendAllNodesToArray(aOutArrayOfContents); +} + +/****************************************************************************** + * HTMLEditor::AutoHTMLFragmentBoundariesFixer + ******************************************************************************/ + +HTMLEditor::AutoHTMLFragmentBoundariesFixer::AutoHTMLFragmentBoundariesFixer( + nsTArray>& aArrayOfTopMostChildContents) { + EnsureBeginsOrEndsWithValidContent(StartOrEnd::start, + aArrayOfTopMostChildContents); + EnsureBeginsOrEndsWithValidContent(StartOrEnd::end, + aArrayOfTopMostChildContents); +} + +// static +void HTMLEditor::AutoHTMLFragmentBoundariesFixer:: + CollectTableAndAnyListElementsOfInclusiveAncestorsAt( + nsIContent& aContent, + nsTArray>& aOutArrayOfListAndTableElements) { + for (Element* element = aContent.GetAsElementOrParentElement(); element; + element = element->GetParentElement()) { + if (HTMLEditUtils::IsAnyListElement(element) || + HTMLEditUtils::IsTable(element)) { + aOutArrayOfListAndTableElements.AppendElement(*element); + } + } +} + +// static +Element* HTMLEditor::AutoHTMLFragmentBoundariesFixer:: + GetMostDistantAncestorListOrTableElement( + const nsTArray>& aArrayOfTopMostChildContents, + const nsTArray>& + aInclusiveAncestorsTableOrListElements) { + Element* lastFoundAncestorListOrTableElement = nullptr; + for (auto& content : aArrayOfTopMostChildContents) { + if (HTMLEditUtils::IsAnyTableElementButNotTable(content)) { + Element* tableElement = + HTMLEditUtils::GetClosestAncestorTableElement(*content); + if (!tableElement) { + continue; + } + // If we find a `` element which is an ancestor of a table + // related element and is not an acestor of first nor last of + // aArrayOfNodes, return the last found list or `
` element. + // XXX Is that really expected that this returns a list element in this + // case? + if (!aInclusiveAncestorsTableOrListElements.Contains(tableElement)) { + return lastFoundAncestorListOrTableElement; + } + // If we find a `
` element which is topmost list or `
` + // element at first or last of aArrayOfNodes, return it. + if (aInclusiveAncestorsTableOrListElements.LastElement().get() == + tableElement) { + return tableElement; + } + // Otherwise, store the `
` element which is an ancestor but + // not topmost ancestor of first or last of aArrayOfNodes. + lastFoundAncestorListOrTableElement = tableElement; + continue; + } + + if (!HTMLEditUtils::IsListItem(content)) { + continue; + } + Element* listElement = + HTMLEditUtils::GetClosestAncestorAnyListElement(*content); + if (!listElement) { + continue; + } + // If we find a list element which is ancestor of a list item element and + // is not an acestor of first nor last of aArrayOfNodes, return the last + // found list or `
` element. + // XXX Is that really expected that this returns a `
` element in + // this case? + if (!aInclusiveAncestorsTableOrListElements.Contains(listElement)) { + return lastFoundAncestorListOrTableElement; + } + // If we find a list element which is topmost list or `
` element at + // first or last of aArrayOfNodes, return it. + if (aInclusiveAncestorsTableOrListElements.LastElement().get() == + listElement) { + return listElement; + } + // Otherwise, store the list element which is an ancestor but not topmost + // ancestor of first or last of aArrayOfNodes. + lastFoundAncestorListOrTableElement = listElement; + } + + // If we find only non-topmost list or `
` element, returns the last + // found one (meaning bottommost one). Otherwise, nullptr. + return lastFoundAncestorListOrTableElement; +} + +Element* +HTMLEditor::AutoHTMLFragmentBoundariesFixer::FindReplaceableTableElement( + Element& aTableElement, nsIContent& aContentMaybeInTableElement) const { + MOZ_ASSERT(aTableElement.IsHTMLElement(nsGkAtoms::table)); + // Perhaps, this is designed for climbing up the DOM tree from + // aContentMaybeInTableElement to aTableElement and making sure that + // aContentMaybeInTableElement itself or its ancestor is a ``, ``, ``, `` or `
`, ``, + // `
`. + // But this looks really buggy because this loop may skip aTableElement + // as the following NS_ASSERTION. We should write automated tests and + // check right behavior. + for (Element* element = + aContentMaybeInTableElement.GetAsElementOrParentElement(); + element; element = element->GetParentElement()) { + if (!HTMLEditUtils::IsAnyTableElement(element) || + element->IsHTMLElement(nsGkAtoms::table)) { + // XXX Perhaps, the original developer of this method assumed that + // aTableElement won't be skipped because if it's assumed, we can + // stop climbing up the tree in that case. + NS_ASSERTION(element != &aTableElement, + "The table element which is looking for is ignored"); + continue; + } + Element* tableElement = nullptr; + for (Element* maybeTableElement = element->GetParentElement(); + maybeTableElement; + maybeTableElement = maybeTableElement->GetParentElement()) { + if (maybeTableElement->IsHTMLElement(nsGkAtoms::table)) { + tableElement = maybeTableElement; + break; + } + } + if (tableElement == &aTableElement) { + return element; + } + // XXX If we find another `` element, why don't we keep searching + // from its parent? + } + return nullptr; +} + +bool HTMLEditor::AutoHTMLFragmentBoundariesFixer::IsReplaceableListElement( + Element& aListElement, nsIContent& aContentMaybeInListElement) const { + MOZ_ASSERT(HTMLEditUtils::IsAnyListElement(&aListElement)); + // Perhaps, this is designed for climbing up the DOM tree from + // aContentMaybeInListElement to aListElement and making sure that + // aContentMaybeInListElement itself or its ancestor is an list item. + // But this looks really buggy because this loop may skip aListElement + // as the following NS_ASSERTION. We should write automated tests and + // check right behavior. + for (Element* element = + aContentMaybeInListElement.GetAsElementOrParentElement(); + element; element = element->GetParentElement()) { + if (!HTMLEditUtils::IsListItem(element)) { + // XXX Perhaps, the original developer of this method assumed that + // aListElement won't be skipped because if it's assumed, we can + // stop climbing up the tree in that case. + NS_ASSERTION(element != &aListElement, + "The list element which is looking for is ignored"); + continue; + } + Element* listElement = + HTMLEditUtils::GetClosestAncestorAnyListElement(*element); + if (listElement == &aListElement) { + return true; + } + // XXX If we find another list element, why don't we keep searching + // from its parent? + } + return false; +} + +void HTMLEditor::AutoHTMLFragmentBoundariesFixer:: + EnsureBeginsOrEndsWithValidContent(StartOrEnd aStartOrEnd, + nsTArray>& + aArrayOfTopMostChildContents) const { + MOZ_ASSERT(!aArrayOfTopMostChildContents.IsEmpty()); + + // Collect list elements and table related elements at first or last node + // in aArrayOfTopMostChildContents. + AutoTArray, 4> inclusiveAncestorsListOrTableElements; + CollectTableAndAnyListElementsOfInclusiveAncestorsAt( + aStartOrEnd == StartOrEnd::end + ? aArrayOfTopMostChildContents.LastElement() + : aArrayOfTopMostChildContents[0], + inclusiveAncestorsListOrTableElements); + if (inclusiveAncestorsListOrTableElements.IsEmpty()) { + return; + } + + // Get most ancestor list or `
` element in + // inclusiveAncestorsListOrTableElements which contains earlier + // node in aArrayOfTopMostChildContents as far as possible. + // XXX With inclusiveAncestorsListOrTableElements, this returns a + // list or `
` element which contains first or last node of + // aArrayOfTopMostChildContents. However, this seems slow when + // aStartOrEnd is StartOrEnd::end and only the last node is in + // different list or `
`. But I'm not sure whether it's + // possible case or not. We need to add tests to + // test_content_iterator_subtree.html for checking how + // SubtreeContentIterator works. + Element* listOrTableElement = GetMostDistantAncestorListOrTableElement( + aArrayOfTopMostChildContents, inclusiveAncestorsListOrTableElements); + if (!listOrTableElement) { + return; + } + + // If we have pieces of tables or lists to be inserted, let's force the + // insertion to deal with table elements right away, so that it doesn't + // orphan some table or list contents outside the table or list. + + OwningNonNull& firstOrLastChildContent = + aStartOrEnd == StartOrEnd::end + ? aArrayOfTopMostChildContents.LastElement() + : aArrayOfTopMostChildContents[0]; + + // Find substructure of list or table that must be included in paste. + Element* replaceElement; + if (HTMLEditUtils::IsAnyListElement(listOrTableElement)) { + if (!IsReplaceableListElement(*listOrTableElement, + firstOrLastChildContent)) { + return; + } + replaceElement = listOrTableElement; + } else { + MOZ_ASSERT(listOrTableElement->IsHTMLElement(nsGkAtoms::table)); + replaceElement = FindReplaceableTableElement(*listOrTableElement, + firstOrLastChildContent); + if (!replaceElement) { + return; + } + } + + // If we can replace the given list element or found a table related element + // in the `
` element, insert it into aArrayOfTopMostChildContents which + // is tompost children to be inserted instead of descendants of them in + // aArrayOfTopMostChildContents. + for (size_t i = 0; i < aArrayOfTopMostChildContents.Length();) { + OwningNonNull& content = aArrayOfTopMostChildContents[i]; + if (content == replaceElement) { + // If the element is n aArrayOfTopMostChildContents, its descendants must + // not be in the array. Therefore, we don't need to optimize this case. + // XXX Perhaps, we can break this loop right now. + aArrayOfTopMostChildContents.RemoveElementAt(i); + continue; + } + if (!EditorUtils::IsDescendantOf(content, *replaceElement)) { + i++; + continue; + } + // For saving number of calls of EditorUtils::IsDescendantOf(), we should + // remove its siblings in the array. + nsIContent* parent = content->GetParent(); + aArrayOfTopMostChildContents.RemoveElementAt(i); + while (i < aArrayOfTopMostChildContents.Length() && + aArrayOfTopMostChildContents[i]->GetParent() == parent) { + aArrayOfTopMostChildContents.RemoveElementAt(i); + } + } + + // Now replace the removed nodes with the structural parent + if (aStartOrEnd == StartOrEnd::end) { + aArrayOfTopMostChildContents.AppendElement(*replaceElement); + } else { + aArrayOfTopMostChildContents.InsertElementAt(0, *replaceElement); + } +} + +} // namespace mozilla -- cgit v1.2.3