tor-browser

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

commit 71a528f9219fe8e29a619fbd891410f7855f6cee
parent c207c4aa7c74becb16954380ecef8edf9e26c174
Author: Masayuki Nakano <masayuki@d-toybox.com>
Date:   Fri, 14 Nov 2025 22:23:04 +0000

Bug 1998288 - Make `HTMLWithContextInserter::InsertContents` stop touching `Selection` r=m_kato

The method touches `Selection` in its `for` loops and that appear in
the reported profile in bug 1997834.  Therefore, it should stop touching
the `Selection`, and its only caller should update `Selection` instead.

Additionally, this rewrites entire the method to make it simpler and
not use variables in the wide scopes.  That makes it easier to
understand.  However, except in some edge cases, this does not change
the behavior.

Differential Revision: https://phabricator.services.mozilla.com/D271362

Diffstat:
Meditor/libeditor/EditorBase.cpp | 4++--
Meditor/libeditor/EditorDOMPoint.h | 84++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Meditor/libeditor/EditorUtils.h | 17+++++++++++------
Meditor/libeditor/HTMLEditSubActionHandler.cpp | 2+-
Meditor/libeditor/HTMLEditorDataTransfer.cpp | 649+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Meditor/libeditor/HTMLEditorInsertParagraphHandler.cpp | 8++++----
6 files changed, 428 insertions(+), 336 deletions(-)

diff --git a/editor/libeditor/EditorBase.cpp b/editor/libeditor/EditorBase.cpp @@ -2535,7 +2535,7 @@ EditorBase::InsertNodeWithTransaction(ContentNodeType& aContentToInsert, } return CreateNodeResultBase<ContentNodeType>( - &aContentToInsert, transaction->SuggestPointToPutCaret<EditorDOMPoint>()); + aContentToInsert, transaction->SuggestPointToPutCaret<EditorDOMPoint>()); } Result<CreateElementResult, nsresult> @@ -2687,7 +2687,7 @@ Result<CreateElementResult, nsresult> EditorBase::InsertBRElement( return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } return CreateElementResult( - newBRElement, + *newBRElement, EditorDOMPoint(newBRElement, aBRElementType == BRElementType::Normal ? InterlinePosition::StartOfNextLine : InterlinePosition::EndOfLine)); diff --git a/editor/libeditor/EditorDOMPoint.h b/editor/libeditor/EditorDOMPoint.h @@ -126,6 +126,22 @@ class EditorDOMPointBase final { } } + template <typename PT, template <typename> typename StrongPtr> + EditorDOMPointBase( + StrongPtr<PT>&& aContainer, uint32_t aOffset, + InterlinePosition aInterlinePosition = InterlinePosition::Undefined) + : mParent(std::forward<StrongPtr<PT>>(aContainer)), + mChild(nullptr), + mOffset(Some(aOffset)), + mInterlinePosition(aInterlinePosition) { + NS_WARNING_ASSERTION( + !mParent || mOffset.value() <= mParent->Length(), + "The offset is larger than the length of aContainer or negative"); + if (!mParent) { + mOffset.reset(); + } + } + template <typename ContainerType, template <typename> typename StrongPtr> EditorDOMPointBase( const StrongPtr<ContainerType>& aContainer, uint32_t aOffset, @@ -143,7 +159,7 @@ class EditorDOMPointBase final { * which you want to refer. */ explicit EditorDOMPointBase( - const nsINode* aPointedNode, + const nsINode* aPointedNode, // FIXME: This should const nsIContent& InterlinePosition aInterlinePosition = InterlinePosition::Undefined) : mParent(aPointedNode && aPointedNode->IsContent() ? aPointedNode->GetParentNode() @@ -152,7 +168,21 @@ class EditorDOMPointBase final { ? const_cast<nsIContent*>(aPointedNode->AsContent()) : nullptr), mInterlinePosition(aInterlinePosition) { - mIsChildInitialized = aPointedNode && mChild; + mIsChildInitialized = mChild; + NS_WARNING_ASSERTION(IsSet(), + "The child is nullptr or doesn't have its parent"); + NS_WARNING_ASSERTION(mChild && mChild->GetParentNode() == mParent, + "Initializing RangeBoundary with invalid value"); + } + + template <typename CT, template <typename> typename StrongPtr> + explicit EditorDOMPointBase( + StrongPtr<CT>&& aChild, + InterlinePosition aInterlinePosition = InterlinePosition::Undefined) + : mParent(aChild ? aChild->GetParentNode() : nullptr), + mChild(std::forward<StrongPtr<CT>>(aChild)), + mInterlinePosition(aInterlinePosition) { + mIsChildInitialized = !!mChild; NS_WARNING_ASSERTION(IsSet(), "The child is nullptr or doesn't have its parent"); NS_WARNING_ASSERTION(mChild && mChild->GetParentNode() == mParent, @@ -608,6 +638,16 @@ class EditorDOMPointBase final { void Set(const StrongPtr<ContainerType>& aContainer, uint32_t aOffset) { Set(aContainer.get(), aOffset); } + template <typename ContainerType, template <typename> typename StrongPtr> + void Set(StrongPtr<ContainerType>&& aContainer, uint32_t aOffset) { + mParent = std::forward<StrongPtr<ContainerType>>(aContainer); + mChild = nullptr; + mOffset = mozilla::Some(aOffset); + mIsChildInitialized = false; + mInterlinePosition = InterlinePosition::Undefined; + NS_ASSERTION(!mParent || mOffset.value() <= mParent->Length(), + "The offset is out of bounds"); + } void Set(const nsINode* aChild) { MOZ_ASSERT(aChild); if (NS_WARN_IF(!aChild->IsContent())) { @@ -620,13 +660,26 @@ class EditorDOMPointBase final { mIsChildInitialized = true; mInterlinePosition = InterlinePosition::Undefined; } + template <typename CT, template <typename> typename StrongPtr> + void Set(StrongPtr<CT>&& aChild) { + MOZ_ASSERT(aChild); + if (NS_WARN_IF(!aChild->IsContent())) { + Clear(); + return; + } + mParent = aChild->GetParentNode(); + mChild = std::forward<StrongPtr<CT>>(aChild); + mOffset.reset(); + mIsChildInitialized = true; + mInterlinePosition = InterlinePosition::Undefined; + } /** * SetToEndOf() sets this to the end of aContainer. Then, mChild is always * nullptr but marked as initialized and mOffset is always set. */ template <typename ContainerType> - MOZ_NEVER_INLINE_DEBUG void SetToEndOf(const ContainerType* aContainer) { + void SetToEndOf(const ContainerType* aContainer) { MOZ_ASSERT(aContainer); mParent = const_cast<ContainerType*>(aContainer); mChild = nullptr; @@ -635,12 +688,19 @@ class EditorDOMPointBase final { mInterlinePosition = InterlinePosition::Undefined; } template <typename ContainerType, template <typename> typename StrongPtr> - MOZ_NEVER_INLINE_DEBUG void SetToEndOf( - const StrongPtr<ContainerType>& aContainer) { + void SetToEndOf(const StrongPtr<ContainerType>& aContainer) { SetToEndOf(aContainer.get()); } + template <typename ContainerType, template <typename> typename StrongPtr> + void SetToEndOf(StrongPtr<ContainerType>&& aContainer) { + mParent = std::forward<StrongPtr<ContainerType>>(aContainer); + mChild = nullptr; + mOffset = mozilla::Some(mParent->Length()); + mIsChildInitialized = true; + mInterlinePosition = InterlinePosition::Undefined; + } template <typename ContainerType> - MOZ_NEVER_INLINE_DEBUG static SelfType AtEndOf( + [[nodiscard]] static SelfType AtEndOf( const ContainerType& aContainer, InterlinePosition aInterlinePosition = InterlinePosition::Undefined) { SelfType point; @@ -649,12 +709,22 @@ class EditorDOMPointBase final { return point; } template <typename ContainerType, template <typename> typename StrongPtr> - MOZ_NEVER_INLINE_DEBUG static SelfType AtEndOf( + [[nodiscard]] static SelfType AtEndOf( const StrongPtr<ContainerType>& aContainer, InterlinePosition aInterlinePosition = InterlinePosition::Undefined) { MOZ_ASSERT(aContainer.get()); return AtEndOf(*aContainer.get(), aInterlinePosition); } + template <typename ContainerType, template <typename> typename StrongPtr> + [[nodiscard]] static SelfType AtEndOf( + StrongPtr<ContainerType>&& aContainer, + InterlinePosition aInterlinePosition = InterlinePosition::Undefined) { + MOZ_ASSERT(aContainer.get()); + SelfType result; + result.SetToEndOf(std::forward<StrongPtr<ContainerType>>(aContainer)); + result.mInterlinePosition = aInterlinePosition; + return result; + } /** * SetToLastContentOf() sets this to the last child of aContainer or the last diff --git a/editor/libeditor/EditorUtils.h b/editor/libeditor/EditorUtils.h @@ -220,16 +220,21 @@ class MOZ_STACK_CLASS CreateNodeResultBase final : public CaretPoint { EditorDOMPoint&& aCandidateCaretPoint) : CaretPoint(std::move(aCandidateCaretPoint)), mNode(&aNode) {} - explicit CreateNodeResultBase(RefPtr<NodeType>&& aNode) - : mNode(std::move(aNode)) {} - explicit CreateNodeResultBase(RefPtr<NodeType>&& aNode, + template <typename NT> + explicit CreateNodeResultBase(RefPtr<NT>&& aNode) + : mNode(std::forward<RefPtr<NT>>(aNode)) {} + template <typename NT> + explicit CreateNodeResultBase(RefPtr<NT>&& aNode, const EditorDOMPoint& aCandidateCaretPoint) - : CaretPoint(aCandidateCaretPoint), mNode(std::move(aNode)) { + : CaretPoint(aCandidateCaretPoint), + mNode(std::forward<RefPtr<NT>>(aNode)) { MOZ_ASSERT(mNode); } - explicit CreateNodeResultBase(RefPtr<NodeType>&& aNode, + template <typename NT> + explicit CreateNodeResultBase(RefPtr<NT>&& aNode, EditorDOMPoint&& aCandidateCaretPoint) - : CaretPoint(std::move(aCandidateCaretPoint)), mNode(std::move(aNode)) { + : CaretPoint(std::move(aCandidateCaretPoint)), + mNode(std::forward<RefPtr<NT>>(aNode)) { MOZ_ASSERT(mNode); } diff --git a/editor/libeditor/HTMLEditSubActionHandler.cpp b/editor/libeditor/HTMLEditSubActionHandler.cpp @@ -6224,7 +6224,7 @@ Result<CreateElementResult, nsresult> HTMLEditor::ChangeListElementType( } if (aListElement.IsHTMLElement(&aNewListTag)) { - return CreateElementResult(&aListElement, std::move(pointToPutCaret)); + return CreateElementResult(aListElement, std::move(pointToPutCaret)); } // XXX If we replace the list element, shouldn't we create it first and then, diff --git a/editor/libeditor/HTMLEditorDataTransfer.cpp b/editor/libeditor/HTMLEditorDataTransfer.cpp @@ -395,10 +395,11 @@ class MOZ_STACK_CLASS HTMLEditor::HTMLWithContextInserter final { const EditorDOMPoint& aLastInsertedPoint) const; /** - * @return error result or the last inserted point. The latter is only set, if - * content was inserted. + * Insert nodes in aArrayOfTopMostChildContents or their children to + * aPointToInsert (if the container is not a proper parent of inserting node, + * this splits the ancestors). */ - [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> + [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateContentResult, nsresult> InsertContents( const EditorDOMPoint& aPointToInsert, nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents, @@ -872,23 +873,30 @@ Result<EditActionResult, nsresult> HTMLEditor::HTMLWithContextInserter::Run( MOZ_ASSERT(pointToInsert.GetContainer()->GetChildAt_Deprecated( pointToInsert.Offset()) == pointToInsert.GetChild()); - Result<EditorDOMPoint, nsresult> lastInsertedPoint = InsertContents( - pointToInsert, arrayOfTopMostChildContents, fragmentAsNode); - if (lastInsertedPoint.isErr()) { + Result<CreateContentResult, nsresult> insertNodeResultOrError = + InsertContents(pointToInsert, arrayOfTopMostChildContents, + fragmentAsNode); + if (MOZ_UNLIKELY(insertNodeResultOrError.isErr())) { NS_WARNING("HTMLWithContextInserter::InsertContents() failed."); - return lastInsertedPoint.propagateErr(); + return insertNodeResultOrError.propagateErr(); } + // The inserting content may contain empty container elements. However, it's + // intended. Therefore, we should not clean up them in the post-processing. mHTMLEditor.TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements = false; - if (MOZ_UNLIKELY(!lastInsertedPoint.inspect().IsInComposedDoc())) { + CreateContentResult insertNodeResult = insertNodeResultOrError.unwrap(); + if (MOZ_UNLIKELY(!insertNodeResult.Handled())) { + // Even if we haven't inserted new content nodes, we "handled" to insert + // them so that return "handled" state. return EditActionResult::HandledResult(); } - if (MOZ_LIKELY(lastInsertedPoint.inspect().IsInContentNode())) { + if (MOZ_LIKELY(insertNodeResult.GetNewNode()->IsInComposedDoc())) { const auto afterLastInsertedContent = - lastInsertedPoint.inspect().NextPointOrAfterContainer(); + EditorRawDOMPoint(insertNodeResult.GetNewNode()) + .NextPointOrAfterContainer<EditorDOMPoint>(); if (MOZ_LIKELY(afterLastInsertedContent.IsInContentNode())) { nsresult rv = mHTMLEditor.EnsureNoFollowingUnnecessaryLineBreak( afterLastInsertedContent); @@ -900,60 +908,59 @@ Result<EditActionResult, nsresult> HTMLEditor::HTMLWithContextInserter::Run( } } - 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); + MOZ_ASSERT(insertNodeResult.HasCaretPointSuggestion()); + rv = insertNodeResult.SuggestCaretPointTo( + mHTMLEditor, {SuggestCaret::AndIgnoreTrivialError}); + if (NS_FAILED(rv)) { + NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); + return Err(rv); } - NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), - "EditorBase::CollapseSelectionTo() failed, but ignored"); + NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, + "CaretPoint::SuggestCaretPointTo() failed, but ignored"); // If we didn't start from an `<a href>` element, we should not keep // caret in the link to make users type something outside the link. if (insertionPointWasInLink) { return EditActionResult::HandledResult(); } - RefPtr<Element> linkElement = GetLinkElement(pointToPutCaret.GetContainer()); - - if (!linkElement) { - return EditActionResult::HandledResult(); - } + if (Element* const parentElement = + insertNodeResult.GetNewNode()->GetParentElement()) { + const RefPtr<Element> linkElement = GetLinkElement(parentElement); + if (MOZ_LIKELY(!linkElement)) { + return EditActionResult::HandledResult(); + } - rv = MoveCaretOutsideOfLink(*linkElement, pointToPutCaret); - if (NS_FAILED(rv)) { - NS_WARNING( - "HTMLEditor::HTMLWithContextInserter::MoveCaretOutsideOfLink " - "failed."); - return Err(rv); + nsresult rv = + MoveCaretOutsideOfLink(*linkElement, insertNodeResult.CaretPointRef()); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::HTMLWithContextInserter::MoveCaretOutsideOfLink() " + "failed"); + return Err(rv); + } } return EditActionResult::HandledResult(); } -Result<EditorDOMPoint, nsresult> +Result<CreateContentResult, nsresult> HTMLEditor::HTMLWithContextInserter::InsertContents( const EditorDOMPoint& aPointToInsert, nsTArray<OwningNonNull<nsIContent>>& aArrayOfTopMostChildContents, const nsINode* aFragmentAsNode) { MOZ_ASSERT(aPointToInsert.IsSetAndValidInComposedDoc()); - EditorDOMPoint pointToInsert{aPointToInsert}; - // Loop over the node list and paste the nodes: const RefPtr<const Element> maybeNonEditableBlockElement = - pointToInsert.IsInContentNode() + aPointToInsert.IsInContentNode() ? HTMLEditUtils::GetInclusiveAncestorElement( - *pointToInsert.ContainerAs<nsIContent>(), + *aPointToInsert.ContainerAs<nsIContent>(), HTMLEditUtils::ClosestBlockElement, BlockInlineCheck::UseComputedDisplayOutsideStyle) : nullptr; - EditorDOMPoint lastInsertedPoint; nsCOMPtr<nsIContent> insertedContextParentContent; + RefPtr<nsIContent> lastInsertedContent; for (const OwningNonNull<nsIContent>& content : aArrayOfTopMostChildContents) { if (NS_WARN_IF(content == aFragmentAsNode) || @@ -967,88 +974,197 @@ HTMLEditor::HTMLWithContextInserter::InsertContents( // 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; } + // Okay, now, we finished moving nodes in insertedContextParentContent. + // We can forget it now to skip the expensive check. + insertedContextParentContent = nullptr; } - // If a `<table>` or `<tr>` element on the clipboard, and pasting it into - // a `<table>` or `<tr>` element, insert only the appropriate children - // instead. - bool inserted = false; - if (HTMLEditUtils::IsTableRowElement(*content) && - HTMLEditUtils::IsTableRowElement( - pointToInsert.GetContainerAs<nsIContent>()) && - (content->IsHTMLElement(nsGkAtoms::table) || - pointToInsert.IsContainerHTMLElement(nsGkAtoms::table))) { - // Move children of current node to the insertion point. - AutoTArray<OwningNonNull<nsIContent>, 24> children; - HTMLEditUtils::CollectAllChildren(*content, children); - EditorDOMPoint pointToPutCaret; - for (const OwningNonNull<nsIContent>& child : children) { - // MOZ_KnownLive(child) because of bug 1622253 - Result<CreateContentResult, nsresult> moveChildResult = + // In the most cases, we want to move `content` into the DOM as-is. However, + // in some cases, we don't want to insert content but do want to insert its + // children into the existing proper container. Therefore, we will check + // the `content` type and insertion point's container below. However, even + // in such case, we may not be able to move only its children. Then, we + // need to fall it back to the default behavior. Therefore, let's wrap the + // default behavior into this lambda. + const auto InsertCurrentContentToNextInsertionPoint = + [&](const EditorDOMPoint& aPointToInsertContent) + MOZ_CAN_RUN_SCRIPT MOZ_NEVER_INLINE_DEBUG -> Result<Ok, nsresult> { + // MOZ_KnownLive(content) because 'aArrayOfTopMostChildContents' + // guarantees its lifetime. + Result<CreateContentResult, nsresult> moveContentResult = + mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction<nsIContent>( + MOZ_KnownLive(content), aPointToInsertContent, + SplitAtEdges::eDoNotCreateEmptyContainer); + if (MOZ_LIKELY(moveContentResult.isOk())) { + moveContentResult.inspect().IgnoreCaretPointSuggestion(); + if (MOZ_UNLIKELY(!moveContentResult.inspect().Handled())) { + MOZ_ASSERT(aPointToInsertContent.IsSetAndValidInComposedDoc()); + MOZ_ASSERT_IF(lastInsertedContent, + lastInsertedContent->IsInComposedDoc()); + return Ok{}; + } + lastInsertedContent = content; + MOZ_ASSERT(lastInsertedContent->IsInComposedDoc()); + return Ok{}; + } + // If we got unexpected DOM tree, let's abort. + if (NS_WARN_IF(moveContentResult.inspectErr() == + NS_ERROR_EDITOR_DESTROYED) || + NS_WARN_IF(moveContentResult.inspectErr() == + NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE)) { + return moveContentResult.propagateErr(); + } + // If we the next insertion point becomes invalid, it means that we + // got unexpected DOM tree which couldn't be detected by + // InsertNodeIntoProperAncestorWithTransaction(). Let's abort to + // avoid to move the node into unexpected position/documents. + if (NS_WARN_IF(!aPointToInsertContent.IsSetAndValidInComposedDoc())) { + return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); + } + // 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()`. + EditorDOMPoint pointToInsert = aPointToInsertContent; + for (nsCOMPtr<nsIContent> parent = content->GetParent(); parent; + parent = parent->GetParent()) { + if (NS_WARN_IF(parent->IsHTMLElement(nsGkAtoms::body))) { + break; // for the inner `for` loop + } + Result<CreateContentResult, nsresult> moveParentResult = mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction<nsIContent>( - MOZ_KnownLive(child), pointToInsert, + *parent, 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 (MOZ_UNLIKELY(moveParentResult.isErr())) { + // If we got unexpected DOM tree, let's abort. + if (NS_WARN_IF(moveParentResult.inspectErr() == + NS_ERROR_EDITOR_DESTROYED) || + NS_WARN_IF(moveParentResult.inspectErr() == + NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE)) { + return moveParentResult.propagateErr(); } - NS_WARNING( - "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(" - "SplitAtEdges::eDoNotCreateEmptyContainer) failed, maybe " - "ignored"); - break; // from the inner `for` loop + // If we the next insertion point becomes invalid, it means that we + // got unexpected DOM tree which couldn't be detected by + // InsertNodeIntoProperAncestorWithTransaction(). Let's abort to + // avoid to move the node into unexpected position/documents. + if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) { + return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); + } + // If the parent cannot be inserted into the DOM tree, the node may be + // an element to make a specific structure like a table. Then, we can + // insert one of its ancestors to the inserting position. So, let's + // retry with its parent. + continue; // the inner `for` loop } - if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) { + moveParentResult.inspect().IgnoreCaretPointSuggestion(); + if (MOZ_UNLIKELY(!moveParentResult.inspect().Handled())) { + MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); + MOZ_ASSERT_IF(lastInsertedContent, + lastInsertedContent->IsInComposedDoc()); 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); + pointToInsert = EditorDOMPoint::After(*parent); + lastInsertedContent = parent; + MOZ_ASSERT(lastInsertedContent->IsInComposedDoc()); + insertedContextParentContent = std::move(parent); + break; // from the inner `for` loop + } // end of the inner `for` loop iterating ancestors of content + return Ok{}; + }; + + // If a `<table>` or `<tr>` element on the clipboard, and pasting it into + // a `<table>` or `<tr>` element, insert only the appropriate children + // instead. + if (HTMLEditUtils::IsTableRowElement(*content)) { + EditorDOMPoint pointToInsert = + lastInsertedContent ? EditorDOMPoint::After(*lastInsertedContent) + : aPointToInsert; + if (HTMLEditUtils::IsTableRowElement( + pointToInsert.GetContainerAs<nsIContent>()) && + (content->IsHTMLElement(nsGkAtoms::table) || + pointToInsert.IsContainerHTMLElement(nsGkAtoms::table))) { + MOZ_ASSERT(!content->IsInComposedDoc()); + bool inserted = false; + for (RefPtr<nsIContent> child = content->GetFirstChild(); child; + child = content->GetFirstChild()) { + Result<CreateContentResult, nsresult> moveChildResult = + mHTMLEditor + .InsertNodeIntoProperAncestorWithTransaction<nsIContent>( + *child, pointToInsert, + SplitAtEdges::eDoNotCreateEmptyContainer); + if (MOZ_UNLIKELY(moveChildResult.isErr())) { + // If we got unexpected DOM tree, let's abort. + if (NS_WARN_IF(moveChildResult.inspectErr() == + NS_ERROR_EDITOR_DESTROYED) || + NS_WARN_IF(moveChildResult.inspectErr() == + NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE)) { + return moveChildResult.propagateErr(); + } + // If we the next insertion point becomes invalid, it means that + // we got unexpected DOM tree which couldn't be detected by + // InsertNodeIntoProperAncestorWithTransaction(). Let's abort to + // avoid to move the node into unexpected position/documents. + if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) { + return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); + } + NS_WARNING( + "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(" + "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but maybe " + "ignored"); + break; // from the inner `for` loop + } + moveChildResult.inspect().IgnoreCaretPointSuggestion(); + if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) { + MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); + MOZ_ASSERT_IF(lastInsertedContent, + lastInsertedContent->IsInComposedDoc()); + continue; + } + inserted = true; + pointToInsert = EditorDOMPoint::After(*child); + MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); + lastInsertedContent = std::move(child); + MOZ_ASSERT(lastInsertedContent->IsInComposedDoc()); + } // end of the inner `for` loop iterating children of `content` + if (!inserted) { + Result<Ok, nsresult> moveContentOrParentResultOrError = + InsertCurrentContentToNextInsertionPoint(pointToInsert); + if (MOZ_UNLIKELY(moveContentOrParentResultOrError.isErr())) { + NS_WARNING("InsertCurrentContentToNextInsertionPoint() failed"); + return moveContentOrParentResultOrError.propagateErr(); + } } - NS_WARNING_ASSERTION( - NS_SUCCEEDED(rv), - "EditorBase::CollapseSelectionTo() failed, but ignored"); + continue; } - } + } // if <tr> + // 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::IsListElement(*content) && - (HTMLEditUtils::IsListElement( - pointToInsert.GetContainerAs<nsIContent>()) || - HTMLEditUtils::IsListItemElement( - pointToInsert.GetContainerAs<nsIContent>()))) { - AutoTArray<OwningNonNull<nsIContent>, 24> children; - HTMLEditUtils::CollectAllChildren(*content, children); - EditorDOMPoint pointToPutCaret; - for (const OwningNonNull<nsIContent>& child : children) { - if (HTMLEditUtils::IsListItemElement(*child) || - HTMLEditUtils::IsListElement(*child)) { + if (HTMLEditUtils::IsListElement(*content)) { + EditorDOMPoint pointToInsert = + lastInsertedContent ? EditorDOMPoint::After(*lastInsertedContent) + : aPointToInsert; + if (HTMLEditUtils::IsListElement( + pointToInsert.GetContainerAs<nsIContent>()) || + HTMLEditUtils::IsListItemElement( + pointToInsert.GetContainerAs<nsIContent>())) { + MOZ_ASSERT(!content->IsInComposedDoc()); + bool inserted = false; + for (RefPtr<nsIContent> child = content->GetFirstChild(); child; + child = content->GetFirstChild()) { + // Ignore invisible nodes like `Comment` or white-space only `Text` + // and invalid children of the list element. + // XXX Although we should not construct invalid structure, but + // shouldn't we preserve invalid children for avoiding dataloss? + if (!HTMLEditUtils::IsListItemElement(*child) && + !HTMLEditUtils::IsListElement(*child)) { + continue; + } // 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 @@ -1056,44 +1172,59 @@ HTMLEditor::HTMLWithContextInserter::InsertContents( // is a list element. if (HTMLEditUtils::IsListItemElement( pointToInsert.GetContainerAs<nsIContent>()) && + HTMLEditUtils::IsRemovableNode( + *pointToInsert.ContainerAs<Element>()) && HTMLEditUtils::IsEmptyNode( - *pointToInsert.GetContainer(), + *pointToInsert.ContainerAs<Element>(), {EmptyCheckOption::TreatNonEditableContentAsInvisible})) { - NS_WARNING_ASSERTION(pointToInsert.GetContainerParent(), - "Insertion point is out of the DOM tree"); - if (pointToInsert.GetContainerParent()) { - pointToInsert.Set(pointToInsert.GetContainer()); - MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); - AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert); - nsresult rv = mHTMLEditor.DeleteNodeWithTransaction( - MOZ_KnownLive(*pointToInsert.GetChild())); - if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { - NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); - return Err(NS_ERROR_EDITOR_DESTROYED); - } - NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), - "EditorBase::DeleteNodeWithTransaction() " - "failed, but ignored"); + const OwningNonNull<Element> emptyListItemElement = + *pointToInsert.ContainerAs<Element>(); + nsCOMPtr<nsINode> parentNode = + emptyListItemElement->GetParentNode(); + MOZ_ASSERT(parentNode); + nsCOMPtr<nsIContent> nextSibling = + emptyListItemElement->GetNextSibling(); + nsresult rv = + mHTMLEditor.DeleteNodeWithTransaction(*emptyListItemElement); + if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { + NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); + return Err(NS_ERROR_EDITOR_DESTROYED); } + if (NS_WARN_IF(!parentNode->IsInComposedDoc()) || + NS_WARN_IF(nextSibling && + nextSibling->GetParentNode() != parentNode)) { + return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::DeleteNodeWithTransaction() " + "failed, but ignored"); + pointToInsert = + nextSibling ? EditorDOMPoint(std::move(nextSibling)) + : EditorDOMPoint::AtEndOf(std::move(parentNode)); + MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); } - // MOZ_KnownLive(child) because of bug 1622253 + NS_WARNING(nsPrintfCString("%s into %s", ToString(*child).c_str(), + ToString(pointToInsert).c_str()) + .get()); Result<CreateContentResult, nsresult> moveChildResult = mHTMLEditor .InsertNodeIntoProperAncestorWithTransaction<nsIContent>( - MOZ_KnownLive(child), pointToInsert, + *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 we got unexpected DOM tree, let's abort. if (NS_WARN_IF(moveChildResult.inspectErr() == - NS_ERROR_EDITOR_DESTROYED)) { - return Err(NS_ERROR_EDITOR_DESTROYED); + NS_ERROR_EDITOR_DESTROYED) || + NS_WARN_IF(moveChildResult.inspectErr() == + NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE)) { + return moveChildResult.propagateErr(); + } + // If we the next insertion point becomes invalid, it means that + // we got unexpected DOM tree which couldn't be detected by + // InsertNodeIntoProperAncestorWithTransaction(). Let's abort to + // avoid to move the node into unexpected position/documents. + if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) { + return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } NS_WARNING( "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(" @@ -1101,224 +1232,110 @@ HTMLEditor::HTMLWithContextInserter::InsertContents( "ignored"); break; // from the inner `for` loop } + moveChildResult.inspect().IgnoreCaretPointSuggestion(); if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) { + MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); + MOZ_ASSERT_IF(lastInsertedContent, + lastInsertedContent->IsInComposedDoc()); continue; } inserted = true; - lastInsertedPoint.Set(child); - pointToInsert = lastInsertedPoint.NextPoint(); + pointToInsert = EditorDOMPoint::After(*child); 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); + lastInsertedContent = std::move(child); + MOZ_ASSERT(lastInsertedContent->IsInComposedDoc()); + } // end of the inner `for` loop iterating children of `content` + if (!inserted) { + Result<Ok, nsresult> moveContentOrParentResultOrError = + InsertCurrentContentToNextInsertionPoint(pointToInsert); + if (MOZ_UNLIKELY(moveContentOrParentResultOrError.isErr())) { + NS_WARNING("InsertCurrentContentToNextInsertionPoint() failed"); + return moveContentOrParentResultOrError.propagateErr(); } - 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"); + continue; } - } + } // if <ul>, <ol> or <dl> + // If pasting into a `<pre>` element and current node is a `<pre>` element, // move only its children. - else if (maybeNonEditableBlockElement && - maybeNonEditableBlockElement->IsHTMLElement(nsGkAtoms::pre) && - content->IsHTMLElement(nsGkAtoms::pre)) { - // Check for pre's going into pre's. - AutoTArray<OwningNonNull<nsIContent>, 24> children; - HTMLEditUtils::CollectAllChildren(*content, children); - EditorDOMPoint pointToPutCaret; - for (const OwningNonNull<nsIContent>& child : children) { - // MOZ_KnownLive(child) because of bug 1622253 + if (maybeNonEditableBlockElement && + maybeNonEditableBlockElement->IsHTMLElement(nsGkAtoms::pre) && + content->IsHTMLElement(nsGkAtoms::pre)) { + MOZ_ASSERT(!content->IsInComposedDoc()); + EditorDOMPoint pointToInsert = + lastInsertedContent ? EditorDOMPoint::After(*lastInsertedContent) + : aPointToInsert; + bool inserted = false; + for (RefPtr<nsIContent> child = content->GetFirstChild(); child; + child = content->GetFirstChild()) { Result<CreateContentResult, nsresult> moveChildResult = mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction<nsIContent>( - MOZ_KnownLive(child), pointToInsert, + *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 we got unexpected DOM tree, let's abort. if (NS_WARN_IF(moveChildResult.inspectErr() == - NS_ERROR_EDITOR_DESTROYED)) { + NS_ERROR_EDITOR_DESTROYED) || + NS_WARN_IF(moveChildResult.inspectErr() == + NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE)) { return moveChildResult.propagateErr(); } + // If we the next insertion point becomes invalid, it means that we + // got unexpected DOM tree which couldn't be detected by + // InsertNodeIntoProperAncestorWithTransaction(). Let's abort to + // avoid to move the node into unexpected position/documents. + if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) { + return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); + } NS_WARNING( "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(" "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but maybe " "ignored"); break; // from the inner `for` loop } + moveChildResult.inspect().IgnoreCaretPointSuggestion(); if (MOZ_UNLIKELY(!moveChildResult.inspect().Handled())) { + MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); + MOZ_ASSERT_IF(lastInsertedContent, + lastInsertedContent->IsInComposedDoc()); continue; } - CreateContentResult unwrappedMoveChildResult = moveChildResult.unwrap(); inserted = true; - lastInsertedPoint.Set(child); - pointToInsert = lastInsertedPoint.NextPoint(); + pointToInsert = EditorDOMPoint::After(*child); 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); + lastInsertedContent = std::move(child); + MOZ_ASSERT(lastInsertedContent->IsInComposedDoc()); + } // end of the inner `for` loop iterating children of `content` + if (!inserted) { + Result<Ok, nsresult> moveContentOrParentResultOrError = + InsertCurrentContentToNextInsertionPoint(pointToInsert); + if (MOZ_UNLIKELY(moveContentOrParentResultOrError.isErr())) { + NS_WARNING("InsertCurrentContentToNextInsertionPoint() failed"); + return moveContentOrParentResultOrError.propagateErr(); } - NS_WARNING_ASSERTION( - NS_SUCCEEDED(rv), - "EditorBase::CollapseSelectionTo() failed, but ignored"); } + continue; + } // if <pre> and inserting into a connected <pre> + + // By default, we should move `content` into the DOM. + Result<Ok, nsresult> moveContentOrParentResultOrError = + InsertCurrentContentToNextInsertionPoint( + lastInsertedContent ? EditorDOMPoint::After(*lastInsertedContent) + : aPointToInsert); + if (MOZ_UNLIKELY(moveContentOrParentResultOrError.isErr())) { + NS_WARNING("InsertCurrentContentToNextInsertionPoint() failed"); + return moveContentOrParentResultOrError.propagateErr(); } + } // end of the `for` loop iterating aArrayOfTopMostChildContents - // TODO: For making the above code clearer, we should move this fallback - // path into a lambda and call it in each if/else-if block. - // If we haven't inserted current node nor its children, move current node - // to the insertion point. - if (!inserted) { - // MOZ_KnownLive(content) because 'aArrayOfTopMostChildContents' is - // guaranteed to keep it alive. - Result<CreateContentResult, nsresult> moveContentResult = - mHTMLEditor.InsertNodeIntoProperAncestorWithTransaction<nsIContent>( - MOZ_KnownLive(content), pointToInsert, - SplitAtEdges::eDoNotCreateEmptyContainer); - if (MOZ_LIKELY(moveContentResult.isOk())) { - if (MOZ_UNLIKELY(!moveContentResult.inspect().Handled())) { - continue; - } - lastInsertedPoint.Set(content); - pointToInsert = lastInsertedPoint; - MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); - nsresult rv = moveContentResult.inspect().SuggestCaretPointTo( - mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, - SuggestCaret::OnlyIfTransactionsAllowedToDoIt, - SuggestCaret::AndIgnoreTrivialError}); - if (NS_FAILED(rv)) { - NS_WARNING("CreateContentResult::SuggestCaretPointTo() failed"); - return Err(rv); - } - NS_WARNING_ASSERTION( - rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, - "CreateContentResult::SuggestCaretPointTo() failed, but ignored"); - } else if (moveContentResult.inspectErr() == - NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) { - // Moving node is moved to different place, we should keep trying to - // insert the next content to same position. - } else { - NS_WARNING( - "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(" - "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but ignored"); - // Assume failure means no legal parent in the document hierarchy, - // try again with the parent of content in the paste hierarchy. - // FYI: We cannot use `InclusiveAncestorOfType` here because of - // calling `InsertNodeIntoProperAncestorWithTransaction()`. - for (nsCOMPtr<nsIContent> childContent = content; childContent; - childContent = childContent->GetParent()) { - if (NS_WARN_IF(!childContent->GetParent()) || - NS_WARN_IF( - childContent->GetParent()->IsHTMLElement(nsGkAtoms::body))) { - break; // for the inner `for` loop - } - const OwningNonNull<nsIContent> oldParentContent = - *childContent->GetParent(); - Result<CreateContentResult, nsresult> moveParentResult = - mHTMLEditor - .InsertNodeIntoProperAncestorWithTransaction<nsIContent>( - oldParentContent, pointToInsert, - SplitAtEdges::eDoNotCreateEmptyContainer); - if (MOZ_UNLIKELY(moveParentResult.isErr())) { - // Moving node is moved to different place, we should keep trying to - // insert the next content to same position. - if (moveParentResult.inspectErr() == - NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE) { - break; // from the inner `for` loop - } - if (NS_WARN_IF(moveParentResult.inspectErr() == - NS_ERROR_EDITOR_DESTROYED)) { - return Err(NS_ERROR_EDITOR_DESTROYED); - } - NS_WARNING( - "HTMLEditor::InsertNodeInToProperAncestorWithTransaction(" - "SplitAtEdges::eDoNotCreateEmptyContainer) failed, but " - "ignored"); - continue; // the inner `for` loop - } - if (MOZ_UNLIKELY(!moveParentResult.inspect().Handled())) { - continue; - } - insertedContextParentContent = oldParentContent; - pointToInsert.Set(oldParentContent); - MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); - nsresult rv = moveParentResult.inspect().SuggestCaretPointTo( - mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, - SuggestCaret::OnlyIfTransactionsAllowedToDoIt, - SuggestCaret::AndIgnoreTrivialError}); - if (NS_FAILED(rv)) { - NS_WARNING("CreateContentResult::SuggestCaretPointTo() failed"); - return Err(rv); - } - NS_WARNING_ASSERTION( - rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, - "CreateContentResult::SuggestCaretPointTo() failed, but ignored"); - break; // from the inner `for` loop - } // end of the inner `for` loop - } - } - if (lastInsertedPoint.IsSet()) { - if (MOZ_UNLIKELY(lastInsertedPoint.GetContainer() != - lastInsertedPoint.GetChild()->GetParentNode())) { - NS_WARNING( - "HTMLEditor::InsertHTMLWithContextAsSubAction() got lost insertion " - "point"); - return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); - } - pointToInsert = lastInsertedPoint.NextPoint(); - MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); - } - } // end of the `for` loop - - return lastInsertedPoint; + if (!lastInsertedContent) { + return CreateContentResult::NotHandled(); + } + EditorDOMPoint pointToPutCaret = + GetNewCaretPointAfterInsertingHTML(EditorDOMPoint(lastInsertedContent)); + return CreateContentResult(std::move(lastInsertedContent), + std::move(pointToPutCaret)); } nsresult HTMLEditor::HTMLWithContextInserter::MoveCaretOutsideOfLink( diff --git a/editor/libeditor/HTMLEditorInsertParagraphHandler.cpp b/editor/libeditor/HTMLEditorInsertParagraphHandler.cpp @@ -1146,7 +1146,7 @@ HTMLEditor::AutoInsertParagraphHandler::HandleInHeadingElement( MOZ_ASSERT(rightHeadingElement, "SplitNodeResult::GetNextContent() should return something if " "DidSplit() returns true"); - return InsertParagraphResult(rightHeadingElement, + return InsertParagraphResult(*rightHeadingElement, splitHeadingResult.UnwrapCaretPoint()); } @@ -1877,7 +1877,7 @@ HTMLEditor::AutoInsertParagraphHandler::HandleInListItemElement( return moveListItemElementResult.propagateErr(); } moveListItemElementResult.inspect().IgnoreCaretPointSuggestion(); - return InsertParagraphResult(&aListItemElement, + return InsertParagraphResult(aListItemElement, EditorDOMPoint(&aListItemElement, 0u)); } @@ -1906,7 +1906,7 @@ HTMLEditor::AutoInsertParagraphHandler::HandleInListItemElement( EditorDOMPoint pointToPutCaret( createNewParagraphElementResult.inspect().GetNewNode(), 0u); return InsertParagraphResult( - createNewParagraphElementResult.inspect().GetNewNode(), + *createNewParagraphElementResult.inspect().GetNewNode(), std::move(pointToPutCaret)); } @@ -1979,7 +1979,7 @@ HTMLEditor::AutoInsertParagraphHandler::HandleInListItemElement( } auto* const rightListItemElement = splitListItemElement.GetNextContentAs<Element>(); - return InsertParagraphResult(rightListItemElement, + return InsertParagraphResult(*rightListItemElement, std::move(pointToPutCaret)); }