From 59203c63bb777a3bacec32fb8830fba33540e809 Mon Sep 17 00:00:00 2001
From: Daniel Baumann <daniel.baumann@progress-linux.org>
Date: Wed, 12 Jun 2024 07:35:29 +0200
Subject: Adding upstream version 127.0.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
---
 editor/libeditor/HTMLEditorDeleteHandler.cpp | 1104 ++++++++++++++++++++++----
 1 file changed, 942 insertions(+), 162 deletions(-)

(limited to 'editor/libeditor/HTMLEditorDeleteHandler.cpp')

diff --git a/editor/libeditor/HTMLEditorDeleteHandler.cpp b/editor/libeditor/HTMLEditorDeleteHandler.cpp
index 39bb95151e..8c58979001 100644
--- a/editor/libeditor/HTMLEditorDeleteHandler.cpp
+++ b/editor/libeditor/HTMLEditorDeleteHandler.cpp
@@ -352,6 +352,28 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
       const HTMLEditor& aHTMLEditor, const nsFrameSelection* aFrameSelection,
       const EditorDOMRangeType& aRangeToDelete) const;
 
+  /**
+   * Extend the start boundary of aRangeToDelete to contain ancestor inline
+   * elements which will be empty once the content in aRangeToDelete is removed
+   * from the tree.
+   *
+   * NOTE: This is designed for deleting inline elements which become empty if
+   * aRangeToDelete which crosses a block boundary of right block child.
+   * Therefore, you may need to improve this method if you want to use this in
+   * the other cases.
+   *
+   * @param aRangeToDelete      [in/out] The range to delete.  This start
+   *                            boundary may be modified.
+   * @param aEditingHost        The editing host.
+   * @return                    true if aRangeToDelete is modified.
+   *                            false if aRangeToDelete is not modified.
+   *                            error if aRangeToDelete gets unexpected
+   *                            situation.
+   */
+  static Result<bool, nsresult>
+  ExtendRangeToContainAncestorInlineElementsAtStart(
+      nsRange& aRangeToDelete, const Element& aEditingHost);
+
   /**
    * A helper method for ExtendOrShrinkRangeToDelete().  This returns shrunken
    * range if aRangeToDelete selects all over list elements which have some list
@@ -529,13 +551,15 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
      * @param aCurrentBlockElement      The current block element.
      * @param aCaretPoint               The caret point (i.e., selection start
      *                                  or end).
+     * @param aEditingHost              The editing host.
      * @return                          true if can continue to handle the
      *                                  deletion.
      */
     bool PrepareToDeleteAtCurrentBlockBoundary(
         const HTMLEditor& aHTMLEditor,
         nsIEditor::EDirection aDirectionAndAmount,
-        Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint);
+        Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint,
+        const Element& aEditingHost);
 
     /**
      * PrepareToDeleteAtOtherBlockBoundary() considers left content and right
@@ -567,11 +591,13 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
      * @param aHTMLEditor               The HTML editor.
      * @param aRangeToDelete            The range to delete.  Must not be
      *                                  collapsed.
+     * @param aEditingHost              The editing host.
      * @return                          true if can continue to handle the
      *                                  deletion.
      */
     bool PrepareToDeleteNonCollapsedRange(const HTMLEditor& aHTMLEditor,
-                                          const nsRange& aRangeToDelete);
+                                          const nsRange& aRangeToDelete,
+                                          const Element& aEditingHost);
 
     /**
      * Run() executes the joining.
@@ -609,17 +635,20 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
                                "HandleDeleteAtOtherBlockBoundary() failed");
           return result;
         }
-        case Mode::DeleteBRElement: {
-          Result<EditActionResult, nsresult> result =
-              DeleteBRElement(aHTMLEditor, aDirectionAndAmount, aEditingHost);
+        case Mode::DeleteBRElement:
+        case Mode::DeletePrecedingBRElementOfBlock:
+        case Mode::DeletePrecedingPreformattedLineBreak: {
+          Result<EditActionResult, nsresult> result = HandleDeleteLineBreak(
+              aHTMLEditor, aDirectionAndAmount, aCaretPoint, aEditingHost);
           NS_WARNING_ASSERTION(
               result.isOk(),
-              "AutoBlockElementsJoiner::DeleteBRElement() failed");
+              "AutoBlockElementsJoiner::HandleDeleteLineBreak() failed");
           return result;
         }
         case Mode::JoinBlocksInSameParent:
         case Mode::DeleteContentInRange:
         case Mode::DeleteNonCollapsedRange:
+        case Mode::DeletePrecedingLinesAndContentInRange:
           MOZ_ASSERT_UNREACHABLE(
               "This mode should be handled in the other Run()");
           return Err(NS_ERROR_UNEXPECTED);
@@ -654,16 +683,21 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
               "ComputeRangeToDeleteAtOtherBlockBoundary() failed");
           return rv;
         }
-        case Mode::DeleteBRElement: {
-          nsresult rv = ComputeRangeToDeleteBRElement(aRangeToDelete);
+        case Mode::DeleteBRElement:
+        case Mode::DeletePrecedingBRElementOfBlock:
+        case Mode::DeletePrecedingPreformattedLineBreak: {
+          nsresult rv = ComputeRangeToDeleteLineBreak(
+              aHTMLEditor, aRangeToDelete, aEditingHost,
+              ComputeRangeFor::GetTargetRanges);
           NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                                "AutoBlockElementsJoiner::"
-                               "ComputeRangeToDeleteBRElement() failed");
+                               "ComputeRangeToDeleteLineBreak() failed");
           return rv;
         }
         case Mode::JoinBlocksInSameParent:
         case Mode::DeleteContentInRange:
         case Mode::DeleteNonCollapsedRange:
+        case Mode::DeletePrecedingLinesAndContentInRange:
           MOZ_ASSERT_UNREACHABLE(
               "This mode should be handled in the other "
               "ComputeRangesToDelete()");
@@ -696,6 +730,8 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
         case Mode::JoinCurrentBlock:
         case Mode::JoinOtherBlock:
         case Mode::DeleteBRElement:
+        case Mode::DeletePrecedingBRElementOfBlock:
+        case Mode::DeletePrecedingPreformattedLineBreak:
           MOZ_ASSERT_UNREACHABLE(
               "This mode should be handled in the other Run()");
           return Err(NS_ERROR_UNEXPECTED);
@@ -717,7 +753,8 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
               "AutoBlockElementsJoiner::DeleteContentInRange() failed");
           return result;
         }
-        case Mode::DeleteNonCollapsedRange: {
+        case Mode::DeleteNonCollapsedRange:
+        case Mode::DeletePrecedingLinesAndContentInRange: {
           Result<EditActionResult, nsresult> result =
               HandleDeleteNonCollapsedRange(
                   aHTMLEditor, aDirectionAndAmount, aStripWrappers,
@@ -744,6 +781,8 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
         case Mode::JoinCurrentBlock:
         case Mode::JoinOtherBlock:
         case Mode::DeleteBRElement:
+        case Mode::DeletePrecedingBRElementOfBlock:
+        case Mode::DeletePrecedingPreformattedLineBreak:
           MOZ_ASSERT_UNREACHABLE(
               "This mode should be handled in the other "
               "ComputeRangesToDelete()");
@@ -765,7 +804,8 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
                                "ComputeRangesToDeleteContentInRanges() failed");
           return rv;
         }
-        case Mode::DeleteNonCollapsedRange: {
+        case Mode::DeleteNonCollapsedRange:
+        case Mode::DeletePrecedingLinesAndContentInRange: {
           nsresult rv = ComputeRangeToDeleteNonCollapsedRange(
               aHTMLEditor, aDirectionAndAmount, aRangeToDelete,
               aSelectionWasCollapsed, aEditingHost);
@@ -825,10 +865,14 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
         nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete,
         const Element& aEditingHost) const;
     [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
-    DeleteBRElement(HTMLEditor& aHTMLEditor,
-                    nsIEditor::EDirection aDirectionAndAmount,
-                    const Element& aEditingHost);
-    nsresult ComputeRangeToDeleteBRElement(nsRange& aRangeToDelete) const;
+    HandleDeleteLineBreak(HTMLEditor& aHTMLEditor,
+                          nsIEditor::EDirection aDirectionAndAmount,
+                          const EditorDOMPoint& aCaretPoint,
+                          const Element& aEditingHost);
+    enum class ComputeRangeFor : bool { GetTargetRanges, ToDeleteTheRange };
+    nsresult ComputeRangeToDeleteLineBreak(
+        const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete,
+        const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const;
     [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
     DeleteContentInRange(HTMLEditor& aHTMLEditor,
                          nsIEditor::EDirection aDirectionAndAmount,
@@ -913,6 +957,27 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
     [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
     DeleteTextAtStartAndEndOfRange(HTMLEditor& aHTMLEditor, nsRange& aRange);
 
+    /**
+     * Return a block element which is an inclusive ancestor of the container of
+     * aPoint if aPoint is start of ancestor blocks.  For example, if `<div
+     * id=div1>abc<div id=div2><div id=div3>[]def</div></div></div>`, return
+     * #div2.
+     */
+    template <typename EditorDOMPointType>
+    static Result<Element*, nsresult>
+    GetMostDistantBlockAncestorIfPointIsStartAtBlock(
+        const EditorDOMPointType& aPoint, const Element& aEditingHost,
+        const Element* aAncestorLimiter = nullptr);
+
+    /**
+     * Extend aRangeToDelete to contain new empty inline ancestors and contain
+     * an invisible <br> element before right child block which causes an empty
+     * line but the range starts after it.
+     */
+    void ExtendRangeToDeleteNonCollapsedRange(
+        const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete,
+        const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const;
+
     class MOZ_STACK_CLASS AutoInclusiveAncestorBlockElementsJoiner final {
      public:
       AutoInclusiveAncestorBlockElementsJoiner() = delete;
@@ -1030,8 +1095,17 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
       JoinOtherBlock,
       JoinBlocksInSameParent,
       DeleteBRElement,
+      // The instance will handle only the <br> element immediately before a
+      // block.
+      DeletePrecedingBRElementOfBlock,
+      // The instance will handle only the preceding preformatted line break
+      // before a block.
+      DeletePrecedingPreformattedLineBreak,
       DeleteContentInRange,
       DeleteNonCollapsedRange,
+      // The instance will handle preceding lines of the right block and content
+      // in the range in the right block.
+      DeletePrecedingLinesAndContentInRange,
     };
     AutoDeleteRangesHandler* mDeleteRangesHandler;
     const AutoDeleteRangesHandler& mDeleteRangesHandlerConst;
@@ -1043,6 +1117,7 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
     // removed at deletion.
     AutoTArray<OwningNonNull<nsIContent>, 8> mSkippedInvisibleContents;
     RefPtr<dom::HTMLBRElement> mBRElement;
+    EditorDOMPointInText mPreformattedLineBreak;
     Mode mMode = Mode::NotInitialized;
   };  // HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner
 
@@ -1371,10 +1446,10 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
       WSRunScanner wsRunScannerAtCaret(
           editingHost, caretPoint,
           BlockInlineCheck::UseComputedDisplayOutsideStyle);
-      WSScanResult scanFromCaretPointResult =
+      const WSScanResult scanFromCaretPointResult =
           aDirectionAndAmount == nsIEditor::eNext
-              ? wsRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(
-                    caretPoint)
+              ? wsRunScannerAtCaret
+                    .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(caretPoint)
               : wsRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
                     caretPoint);
       if (scanFromCaretPointResult.Failed()) {
@@ -1383,26 +1458,23 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
             "failed");
         return NS_ERROR_FAILURE;
       }
-      if (!scanFromCaretPointResult.GetContent()) {
-        return NS_SUCCESS_DOM_NO_OPERATION;
-      }
+      MOZ_ASSERT(scanFromCaretPointResult.GetContent());
 
       if (scanFromCaretPointResult.ReachedBRElement()) {
         if (scanFromCaretPointResult.BRElementPtr() ==
             wsRunScannerAtCaret.GetEditingHost()) {
           return NS_OK;
         }
-        if (!EditorUtils::IsEditableContent(
-                *scanFromCaretPointResult.BRElementPtr(), EditorType::HTML)) {
+        if (!scanFromCaretPointResult.IsContentEditable()) {
           return NS_SUCCESS_DOM_NO_OPERATION;
         }
-        if (HTMLEditUtils::IsInvisibleBRElement(
-                *scanFromCaretPointResult.BRElementPtr())) {
+        if (scanFromCaretPointResult.ReachedInvisibleBRElement()) {
           EditorDOMPoint newCaretPosition =
               aDirectionAndAmount == nsIEditor::eNext
-                  ? EditorDOMPoint::After(
-                        *scanFromCaretPointResult.BRElementPtr())
-                  : EditorDOMPoint(scanFromCaretPointResult.BRElementPtr());
+                  ? scanFromCaretPointResult
+                        .PointAfterReachedContent<EditorDOMPoint>()
+                  : scanFromCaretPointResult
+                        .PointAtReachedContent<EditorDOMPoint>();
           if (NS_WARN_IF(!newCaretPosition.IsSet())) {
             return NS_ERROR_FAILURE;
           }
@@ -1450,7 +1522,8 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
 
           // Otherwise, extend the range to contain the invisible `<br>`
           // element.
-          if (EditorRawDOMPoint(scanFromCaretPointResult.BRElementPtr())
+          if (scanFromCaretPointResult
+                  .PointAtReachedContent<EditorRawDOMPoint>()
                   .IsBefore(
                       aRangesToDelete
                           .GetFirstRangeStartPoint<EditorRawDOMPoint>())) {
@@ -1463,12 +1536,13 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
             return rv;
           }
           if (aRangesToDelete.GetFirstRangeEndPoint<EditorRawDOMPoint>()
-                  .IsBefore(EditorRawDOMPoint::After(
-                      *scanFromCaretPointResult.BRElementPtr()))) {
+                  .IsBefore(
+                      scanFromCaretPointResult
+                          .PointAfterReachedContent<EditorRawDOMPoint>())) {
             nsresult rv = aRangesToDelete.FirstRangeRef()->SetStartAndEnd(
                 aRangesToDelete.FirstRangeRef()->StartRef(),
-                EditorRawDOMPoint::After(
-                    *scanFromCaretPointResult.BRElementPtr())
+                scanFromCaretPointResult
+                    .PointAfterReachedContent<EditorRawDOMPoint>()
                     .ToRawRangeBoundary());
             NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                                  "nsRange::SetStartAndEnd() failed");
@@ -1670,10 +1744,11 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run(
       WSRunScanner wsRunScannerAtCaret(
           &aEditingHost, caretPoint.ref(),
           BlockInlineCheck::UseComputedDisplayOutsideStyle);
-      WSScanResult scanFromCaretPointResult =
+      const WSScanResult scanFromCaretPointResult =
           aDirectionAndAmount == nsIEditor::eNext
-              ? wsRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(
-                    caretPoint.ref())
+              ? wsRunScannerAtCaret
+                    .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
+                        caretPoint.ref())
               : wsRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
                     caretPoint.ref());
       if (MOZ_UNLIKELY(scanFromCaretPointResult.Failed())) {
@@ -1682,20 +1757,17 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run(
             "failed");
         return Err(NS_ERROR_FAILURE);
       }
-      if (!scanFromCaretPointResult.GetContent()) {
-        return EditActionResult::CanceledResult();
-      }
+      MOZ_ASSERT(scanFromCaretPointResult.GetContent());
+
       // Short circuit for invisible breaks.  delete them and recurse.
       if (scanFromCaretPointResult.ReachedBRElement()) {
         if (scanFromCaretPointResult.BRElementPtr() == &aEditingHost) {
           return EditActionResult::HandledResult();
         }
-        if (!EditorUtils::IsEditableContent(
-                *scanFromCaretPointResult.BRElementPtr(), EditorType::HTML)) {
+        if (!scanFromCaretPointResult.IsContentEditable()) {
           return EditActionResult::CanceledResult();
         }
-        if (HTMLEditUtils::IsInvisibleBRElement(
-                *scanFromCaretPointResult.BRElementPtr())) {
+        if (scanFromCaretPointResult.ReachedInvisibleBRElement()) {
           // TODO: We should extend the range to delete again before/after
           //       the caret point and use `HandleDeleteNonCollapsedRanges()`
           //       instead after we would create delete range computation
@@ -1728,10 +1800,10 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run(
             WSRunScanner wsRunScannerAtCaret(
                 &aEditingHost, caretPoint.ref(),
                 BlockInlineCheck::UseComputedDisplayOutsideStyle);
-            WSScanResult scanFromCaretPointResult =
+            const WSScanResult scanFromCaretPointResult =
                 aDirectionAndAmount == nsIEditor::eNext
                     ? wsRunScannerAtCaret
-                          .ScanNextVisibleNodeOrBlockBoundaryFrom(
+                          .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
                               caretPoint.ref())
                     : wsRunScannerAtCaret
                           .ScanPreviousVisibleNodeOrBlockBoundaryFrom(
@@ -1742,7 +1814,7 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run(
                   "VisibleNodeOrBlockBoundaryFrom() failed");
               return Err(NS_ERROR_FAILURE);
             }
-            if (MOZ_UNLIKELY(
+            if (NS_WARN_IF(
                     scanFromCaretPointResult.ReachedInvisibleBRElement())) {
               return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
             }
@@ -1786,8 +1858,11 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
   if (aScanFromCaretPointResult.InCollapsibleWhiteSpaces() ||
       aScanFromCaretPointResult.InNonCollapsibleCharacters() ||
       aScanFromCaretPointResult.ReachedPreformattedLineBreak()) {
+    // This means that if aDirectionAndAmount == nsIEditor::eNext, collapse
+    // selection at the found character.  Otherwise, collapse selection after
+    // the found character.
     nsresult rv = aRangesToDelete.Collapse(
-        aScanFromCaretPointResult.Point<EditorRawDOMPoint>());
+        aScanFromCaretPointResult.Point_Deprecated<EditorRawDOMPoint>());
     if (MOZ_UNLIKELY(NS_FAILED(rv))) {
       NS_WARNING("AutoRangeArray::Collapse() failed");
       return NS_ERROR_FAILURE;
@@ -1826,7 +1901,7 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
   }
 
   if (aScanFromCaretPointResult.ReachedOtherBlockElement()) {
-    if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) {
+    if (NS_WARN_IF(!aScanFromCaretPointResult.ContentIsElement())) {
       return NS_ERROR_FAILURE;
     }
     MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
@@ -1854,10 +1929,9 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
     return handled ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
   }
 
-  if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary()) {
-    if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) {
-      return NS_ERROR_FAILURE;
-    }
+  if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary() ||
+      aScanFromCaretPointResult.ReachedInlineEditingHostBoundary()) {
+    MOZ_ASSERT(aScanFromCaretPointResult.ContentIsElement());
     MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
     bool handled = false;
     for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) {
@@ -1865,7 +1939,7 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
       if (!joiner.PrepareToDeleteAtCurrentBlockBoundary(
               aHTMLEditor, aDirectionAndAmount,
               *aScanFromCaretPointResult.ElementPtr(),
-              aWSRunScannerAtCaret.ScanStartRef())) {
+              aWSRunScannerAtCaret.ScanStartRef(), aEditingHost)) {
         continue;
       }
       handled = true;
@@ -1904,8 +1978,11 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
     if (aScanFromCaretPointResult.InCollapsibleWhiteSpaces() ||
         aScanFromCaretPointResult.InNonCollapsibleCharacters() ||
         aScanFromCaretPointResult.ReachedPreformattedLineBreak()) {
+      // This means that if aDirectionAndAmount == nsIEditor::eNext, collapse
+      // selection at the found character.  Otherwise, collapse selection after
+      // the found character.
       nsresult rv = aRangesToDelete.Collapse(
-          aScanFromCaretPointResult.Point<EditorRawDOMPoint>());
+          aScanFromCaretPointResult.Point_Deprecated<EditorRawDOMPoint>());
       if (NS_FAILED(rv)) {
         NS_WARNING("AutoRangeArray::Collapse() failed");
         return Err(NS_ERROR_FAILURE);
@@ -1959,13 +2036,16 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
   }
 
   if (aScanFromCaretPointResult.InNonCollapsibleCharacters()) {
-    if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsText())) {
+    if (NS_WARN_IF(!aScanFromCaretPointResult.ContentIsText())) {
       return Err(NS_ERROR_FAILURE);
     }
     Result<CaretPoint, nsresult> caretPointOrError =
         HandleDeleteCollapsedSelectionAtVisibleChar(
             aHTMLEditor, aDirectionAndAmount, aRangesToDelete,
-            aScanFromCaretPointResult.Point<EditorDOMPoint>(), aEditingHost);
+            // This means that if aDirectionAndAmount == nsIEditor::eNext,
+            // at the found character.  Otherwise, after the found character.
+            aScanFromCaretPointResult.Point_Deprecated<EditorDOMPoint>(),
+            aEditingHost);
     if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
       NS_WARNING(
           "AutoDeleteRangesHandler::"
@@ -2019,7 +2099,7 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
   }
 
   if (aScanFromCaretPointResult.ReachedOtherBlockElement()) {
-    if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) {
+    if (NS_WARN_IF(!aScanFromCaretPointResult.ContentIsElement())) {
       return Err(NS_ERROR_FAILURE);
     }
     MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
@@ -2049,10 +2129,9 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
                                : std::move(ret);
   }
 
-  if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary()) {
-    if (NS_WARN_IF(!aScanFromCaretPointResult.GetContent()->IsElement())) {
-      return Err(NS_ERROR_FAILURE);
-    }
+  if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary() ||
+      aScanFromCaretPointResult.ReachedInlineEditingHostBoundary()) {
+    MOZ_ASSERT(aScanFromCaretPointResult.ContentIsElement());
     MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
     bool allRangesNotHandled = true;
     auto ret = EditActionResult::IgnoredResult();
@@ -2061,7 +2140,7 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
       if (!joiner.PrepareToDeleteAtCurrentBlockBoundary(
               aHTMLEditor, aDirectionAndAmount,
               *aScanFromCaretPointResult.ElementPtr(),
-              aWSRunScannerAtCaret.ScanStartRef())) {
+              aWSRunScannerAtCaret.ScanStartRef(), aEditingHost)) {
         continue;
       }
       allRangesNotHandled = false;
@@ -2484,6 +2563,109 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAtomicContent(
   return CaretPoint(std::move(pointToPutCaret));
 }
 
+// static
+Result<bool, nsresult> HTMLEditor::AutoDeleteRangesHandler::
+    ExtendRangeToContainAncestorInlineElementsAtStart(
+        nsRange& aRangeToDelete, const Element& aEditingHost) {
+  MOZ_ASSERT(aRangeToDelete.IsPositioned());
+  MOZ_ASSERT(aRangeToDelete.GetCommonAncestorContainer(IgnoreErrors()));
+  MOZ_ASSERT(aRangeToDelete.GetCommonAncestorContainer(IgnoreErrors())
+                 ->IsInclusiveDescendantOf(&aEditingHost));
+
+  EditorRawDOMPoint startPoint(aRangeToDelete.StartRef());
+  if (startPoint.IsInTextNode()) {
+    if (!startPoint.IsStartOfContainer()) {
+      // FIXME: If before the point has only collapsible white-spaces and the
+      // text node follows a block boundary, we should treat the range start
+      // from start of the text node.
+      return true;
+    }
+    startPoint.Set(startPoint.ContainerAs<Text>());
+    if (NS_WARN_IF(!startPoint.IsSet())) {
+      return Err(NS_ERROR_FAILURE);
+    }
+    if (startPoint.GetContainer() == &aEditingHost) {
+      return false;
+    }
+  } else if (startPoint.IsInDataNode()) {
+    startPoint.Set(startPoint.ContainerAs<nsIContent>());
+    if (NS_WARN_IF(!startPoint.IsSet())) {
+      return Err(NS_ERROR_FAILURE);
+    }
+    if (startPoint.GetContainer() == &aEditingHost) {
+      return false;
+    }
+  } else if (startPoint.GetContainer() == &aEditingHost) {
+    return false;
+  }
+
+  // FYI: This method is designed for deleting inline elements which become
+  // empty if aRangeToDelete which crosses a block boundary of right block
+  // child.  Therefore, you may need to improve this method if you want to use
+  // this in the other cases.
+
+  nsINode* const commonAncestor =
+      nsContentUtils::GetClosestCommonInclusiveAncestor(
+          startPoint.GetContainer(), aRangeToDelete.GetEndContainer());
+  if (NS_WARN_IF(!commonAncestor)) {
+    return Err(NS_ERROR_FAILURE);
+  }
+  MOZ_ASSERT(commonAncestor->IsInclusiveDescendantOf(&aEditingHost));
+
+  EditorRawDOMPoint newStartPoint(startPoint);
+  while (newStartPoint.GetContainer() != &aEditingHost &&
+         newStartPoint.GetContainer() != commonAncestor) {
+    if (NS_WARN_IF(!newStartPoint.IsInContentNode())) {
+      return Err(NS_ERROR_FAILURE);
+    }
+    if (!HTMLEditUtils::IsInlineContent(
+            *newStartPoint.ContainerAs<nsIContent>(),
+            BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
+      break;
+    }
+    // The container is inline, check whether the point is first visible point
+    // or not to consider whether climbing up the tree.
+    bool foundVisiblePrevSibling = false;
+    for (nsIContent* content = newStartPoint.GetPreviousSiblingOfChild();
+         content; content = content->GetPreviousSibling()) {
+      if (Text* text = Text::FromNode(content)) {
+        if (HTMLEditUtils::IsVisibleTextNode(*text)) {
+          foundVisiblePrevSibling = true;
+          break;
+        }
+        // The text node is invisible.
+      } else if (content->IsComment()) {
+        // Ignore the comment node.
+      } else if (!HTMLEditUtils::IsInlineContent(
+                     *content,
+                     BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
+                 !HTMLEditUtils::IsEmptyNode(
+                     *content,
+                     {EmptyCheckOption::TreatSingleBRElementAsVisible})) {
+        foundVisiblePrevSibling = true;
+        break;
+      }
+    }
+    if (foundVisiblePrevSibling) {
+      break;
+    }
+    // the point can be treated as start of the parent inline now.
+    newStartPoint.Set(newStartPoint.ContainerAs<nsIContent>());
+    if (NS_WARN_IF(!newStartPoint.IsSet())) {
+      return Err(NS_ERROR_FAILURE);
+    }
+  }
+  if (newStartPoint == startPoint) {
+    return false;  // Don't need to modify the range
+  }
+  IgnoredErrorResult error;
+  aRangeToDelete.SetStart(newStartPoint.ToRawRangeBoundary(), error);
+  if (MOZ_UNLIKELY(error.Failed())) {
+    return Err(NS_ERROR_FAILURE);
+  }
+  return true;
+}
+
 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
     PrepareToDeleteAtOtherBlockBoundary(
         const HTMLEditor& aHTMLEditor,
@@ -2519,12 +2701,12 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   // Next to a block.  See if we are between the block and a `<br>`.
   // If so, we really want to delete the `<br>`.  Else join content at
   // selection to the block.
-  WSScanResult scanFromCaretResult =
+  const WSScanResult scanFromCaretResult =
       aDirectionAndAmount == nsIEditor::eNext
           ? aWSRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
                 aCaretPoint)
-          : aWSRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(
-                aCaretPoint);
+          : aWSRunScannerAtCaret
+                .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aCaretPoint);
   // If we found a `<br>` element, we need to delete it instead of joining the
   // contents.
   if (scanFromCaretResult.ReachedBRElement()) {
@@ -2537,58 +2719,159 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
 }
 
 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
-    ComputeRangeToDeleteBRElement(nsRange& aRangeToDelete) const {
-  MOZ_ASSERT(mBRElement);
-  // XXX Why don't we scan invisible leading white-spaces which follows the
-  //     `<br>` element?
+    ComputeRangeToDeleteLineBreak(const HTMLEditor& aHTMLEditor,
+                                  nsRange& aRangeToDelete,
+                                  const Element& aEditingHost,
+                                  ComputeRangeFor aComputeRangeFor) const {
+  // FIXME: Scan invisible leading white-spaces after the <br>.
+  MOZ_ASSERT_IF(mMode == Mode::DeleteBRElement, mBRElement);
+  MOZ_ASSERT_IF(mMode == Mode::DeletePrecedingBRElementOfBlock, mBRElement);
+  MOZ_ASSERT_IF(mMode == Mode::DeletePrecedingPreformattedLineBreak,
+                mPreformattedLineBreak.IsSetAndValid());
+  MOZ_ASSERT_IF(mMode == Mode::DeletePrecedingPreformattedLineBreak,
+                mPreformattedLineBreak.IsCharPreformattedNewLine());
+  MOZ_ASSERT_IF(aComputeRangeFor == ComputeRangeFor::GetTargetRanges,
+                aRangeToDelete.IsPositioned());
+
+  // If we're computing for beforeinput.getTargetRanges() and the inputType
+  // is not a simple deletion like replacing selected content with new
+  // content, the range should end at the original end boundary of the given
+  // range.
+  const bool preserveEndBoundary =
+      (mMode == Mode::DeletePrecedingBRElementOfBlock ||
+       mMode == Mode::DeletePrecedingPreformattedLineBreak) &&
+      aComputeRangeFor == ComputeRangeFor::GetTargetRanges &&
+      !MayEditActionDeleteAroundCollapsedSelection(aHTMLEditor.GetEditAction());
+
+  if (mMode != Mode::DeletePrecedingPreformattedLineBreak) {
+    Element* const mostDistantInlineAncestor =
+        HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
+            *mBRElement, BlockInlineCheck::UseComputedDisplayOutsideStyle,
+            &aEditingHost);
+    if (preserveEndBoundary) {
+      // FIXME: If the range ends at end of an inline element, we may need to
+      // extend the range.
+      IgnoredErrorResult error;
+      aRangeToDelete.SetStart(EditorRawDOMPoint(mostDistantInlineAncestor
+                                                    ? mostDistantInlineAncestor
+                                                    : mBRElement)
+                                  .ToRawRangeBoundary(),
+                              error);
+      NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SetStart() failed");
+      MOZ_ASSERT_IF(!error.Failed(), !aRangeToDelete.Collapsed());
+      return error.StealNSResult();
+    }
+    IgnoredErrorResult error;
+    aRangeToDelete.SelectNode(
+        mostDistantInlineAncestor ? *mostDistantInlineAncestor : *mBRElement,
+        error);
+    NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SelectNode() failed");
+    return error.StealNSResult();
+  }
+
+  Element* const mostDistantInlineAncestor =
+      mPreformattedLineBreak.ContainerAs<Text>()->TextDataLength() == 1
+          ? HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
+                *mPreformattedLineBreak.ContainerAs<Text>(),
+                BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)
+          : nullptr;
+
+  if (!mostDistantInlineAncestor) {
+    if (preserveEndBoundary) {
+      // FIXME: If the range ends at end of an inline element, we may need to
+      // extend the range.
+      IgnoredErrorResult error;
+      aRangeToDelete.SetStart(mPreformattedLineBreak.ToRawRangeBoundary(),
+                              error);
+      MOZ_ASSERT_IF(!error.Failed(), !aRangeToDelete.Collapsed());
+      NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SetStart() failed");
+      return error.StealNSResult();
+    }
+    nsresult rv = aRangeToDelete.SetStartAndEnd(
+        mPreformattedLineBreak.ToRawRangeBoundary(),
+        mPreformattedLineBreak.NextPoint().ToRawRangeBoundary());
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
+    return rv;
+  }
+
+  if (preserveEndBoundary) {
+    // FIXME: If the range ends at end of an inline element, we may need to
+    // extend the range.
+    IgnoredErrorResult error;
+    aRangeToDelete.SetStart(
+        EditorRawDOMPoint(mostDistantInlineAncestor).ToRawRangeBoundary(),
+        error);
+    MOZ_ASSERT_IF(!error.Failed(), !aRangeToDelete.Collapsed());
+    NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SetStart() failed");
+    return error.StealNSResult();
+  }
+
   IgnoredErrorResult error;
-  aRangeToDelete.SelectNode(*mBRElement, error);
+  aRangeToDelete.SelectNode(*mostDistantInlineAncestor, error);
   NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SelectNode() failed");
   return error.StealNSResult();
 }
 
-Result<EditActionResult, nsresult>
-HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::DeleteBRElement(
-    HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
-    const Element& aEditingHost) {
+Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
+    AutoBlockElementsJoiner::HandleDeleteLineBreak(
+        HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
+        const EditorDOMPoint& aCaretPoint, const Element& aEditingHost) {
   MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
-  MOZ_ASSERT(mBRElement);
+  MOZ_ASSERT(mBRElement || mPreformattedLineBreak.IsSet());
 
   // If we're deleting selection (not replacing with new content), we should
   // put caret to end of preceding text node if there is.  Then, users can type
   // text in it like the other browsers.
   EditorDOMPoint pointToPutCaret = [&]() {
+    // but when we're deleting a preceding line break of current block, we
+    // should keep the caret position in the current block.
+    if (mMode == Mode::DeletePrecedingBRElementOfBlock ||
+        mMode == Mode::DeletePrecedingPreformattedLineBreak) {
+      return aCaretPoint;
+    }
     if (!MayEditActionDeleteAroundCollapsedSelection(
             aHTMLEditor.GetEditAction())) {
       return EditorDOMPoint();
     }
     WSRunScanner scanner(&aEditingHost, EditorRawDOMPoint(mBRElement),
                          BlockInlineCheck::UseComputedDisplayOutsideStyle);
-    WSScanResult maybePreviousText =
+    const WSScanResult maybePreviousText =
         scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
             EditorRawDOMPoint(mBRElement));
     if (maybePreviousText.IsContentEditable() &&
         maybePreviousText.InVisibleOrCollapsibleCharacters() &&
         !HTMLEditor::GetLinkElement(maybePreviousText.TextPtr())) {
-      return maybePreviousText.Point<EditorDOMPoint>();
+      return maybePreviousText.PointAfterReachedContent<EditorDOMPoint>();
     }
-    WSScanResult maybeNextText = scanner.ScanNextVisibleNodeOrBlockBoundaryFrom(
-        EditorRawDOMPoint::After(*mBRElement));
+    const WSScanResult maybeNextText =
+        scanner.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
+            EditorRawDOMPoint::After(*mBRElement));
     if (maybeNextText.IsContentEditable() &&
         maybeNextText.InVisibleOrCollapsibleCharacters()) {
-      return maybeNextText.Point<EditorDOMPoint>();
+      return maybeNextText.PointAtReachedContent<EditorDOMPoint>();
     }
     return EditorDOMPoint();
   }();
 
-  // If we found a `<br>` element, we should delete it instead of joining the
-  // contents.
+  RefPtr<nsRange> rangeToDelete =
+      nsRange::Create(const_cast<Element*>(&aEditingHost));
+  MOZ_ASSERT(rangeToDelete);
   nsresult rv =
-      aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(*mBRElement));
+      ComputeRangeToDeleteLineBreak(aHTMLEditor, *rangeToDelete, aEditingHost,
+                                    ComputeRangeFor::ToDeleteTheRange);
   if (NS_FAILED(rv)) {
-    NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
+    NS_WARNING(
+        "AutoBlockElementsJoiner::ComputeRangeToDeleteLineBreak() failed");
     return Err(rv);
   }
+  Result<EditActionResult, nsresult> result = HandleDeleteNonCollapsedRange(
+      aHTMLEditor, aDirectionAndAmount, nsIEditor::eNoStrip, *rangeToDelete,
+      SelectionWasCollapsed::Yes, aEditingHost);
+  if (MOZ_UNLIKELY(result.isErr())) {
+    NS_WARNING(
+        "AutoBlockElementsJoiner::HandleDeleteNonCollapsedRange() failed");
+    return result;
+  }
 
   if (mLeftContent && mRightContent &&
       HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent) !=
@@ -2597,7 +2880,7 @@ HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::DeleteBRElement(
   }
 
   // Put selection at edge of block and we are done.
-  if (NS_WARN_IF(!mLeafContentInOtherBlock)) {
+  if (NS_WARN_IF(mMode == Mode::DeleteBRElement && !mLeafContentInOtherBlock)) {
     // XXX This must be odd case.  The other block can be empty.
     return Err(NS_ERROR_FAILURE);
   }
@@ -2607,7 +2890,7 @@ HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::DeleteBRElement(
     if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
       return Err(NS_ERROR_EDITOR_DESTROYED);
     }
-    if (NS_SUCCEEDED(rv)) {
+    if (mMode == Mode::DeleteBRElement && NS_SUCCEEDED(rv)) {
       // If we prefer to use style in the previous line, we should forget
       // previous styles since the caret position has all styles which we want
       // to use with new content.
@@ -2622,7 +2905,9 @@ HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::DeleteBRElement(
             ->ClearLinkAndItsSpecifiedStyle();
       }
     } else {
-      NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored");
+      NS_WARNING_ASSERTION(
+          NS_SUCCEEDED(rv),
+          "EditorBase::CollapseSelectionTo() failed, but ignored");
     }
     return EditActionResult::HandledResult();
   }
@@ -2937,7 +3222,8 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
     PrepareToDeleteAtCurrentBlockBoundary(
         const HTMLEditor& aHTMLEditor,
         nsIEditor::EDirection aDirectionAndAmount,
-        Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint) {
+        Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint,
+        const Element& aEditingHost) {
   MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
 
   // At edge of our block.  Look beside it and see if we can join to an
@@ -2956,20 +3242,15 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
     return false;
   }
 
-  Element* editingHost = aHTMLEditor.ComputeEditingHost();
-  if (NS_WARN_IF(!editingHost)) {
-    return false;
-  }
-
   auto ScanJoinTarget = [&]() -> nsIContent* {
     nsIContent* targetContent =
         aDirectionAndAmount == nsIEditor::ePrevious
             ? HTMLEditUtils::GetPreviousContent(
                   aCurrentBlockElement, {WalkTreeOption::IgnoreNonEditableNode},
-                  BlockInlineCheck::Unused, editingHost)
+                  BlockInlineCheck::Unused, &aEditingHost)
             : HTMLEditUtils::GetNextContent(
                   aCurrentBlockElement, {WalkTreeOption::IgnoreNonEditableNode},
-                  BlockInlineCheck::Unused, editingHost);
+                  BlockInlineCheck::Unused, &aEditingHost);
     // If found content is an invisible text node, let's scan visible things.
     auto IsIgnorableDataNode = [](nsIContent* aContent) {
       return aContent && HTMLEditUtils::IsRemovableNode(*aContent) &&
@@ -2987,22 +3268,22 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
                  ? HTMLEditUtils::GetPreviousContent(
                        *targetContent, {WalkTreeOption::StopAtBlockBoundary},
                        BlockInlineCheck::UseComputedDisplayOutsideStyle,
-                       editingHost)
+                       &aEditingHost)
                  : HTMLEditUtils::GetNextContent(
                        *targetContent, {WalkTreeOption::StopAtBlockBoundary},
                        BlockInlineCheck::UseComputedDisplayOutsideStyle,
-                       editingHost);
+                       &aEditingHost);
          adjacentContent;
          adjacentContent =
              aDirectionAndAmount == nsIEditor::ePrevious
                  ? HTMLEditUtils::GetPreviousContent(
                        *adjacentContent, {WalkTreeOption::StopAtBlockBoundary},
                        BlockInlineCheck::UseComputedDisplayOutsideStyle,
-                       editingHost)
+                       &aEditingHost)
                  : HTMLEditUtils::GetNextContent(
                        *adjacentContent, {WalkTreeOption::StopAtBlockBoundary},
                        BlockInlineCheck::UseComputedDisplayOutsideStyle,
-                       editingHost)) {
+                       &aEditingHost)) {
       // If non-editable element is found, we should not skip it to avoid
       // joining too far nodes.
       if (!HTMLEditUtils::IsSimplyEditableNode(*adjacentContent)) {
@@ -3036,6 +3317,77 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   };
 
   if (aDirectionAndAmount == nsIEditor::ePrevious) {
+    const WSScanResult prevVisibleThing = [&]() {
+      // When Backspace at start of a block, we need to delete only a preceding
+      // <br> element if there is.
+      const Result<Element*, nsresult>
+          inclusiveAncestorOfRightChildBlockOrError = AutoBlockElementsJoiner::
+              GetMostDistantBlockAncestorIfPointIsStartAtBlock(aCaretPoint,
+                                                               aEditingHost);
+      if (NS_WARN_IF(inclusiveAncestorOfRightChildBlockOrError.isErr()) ||
+          !inclusiveAncestorOfRightChildBlockOrError.inspect()) {
+        return WSScanResult::Error();
+      }
+      const WSScanResult prevVisibleThingBeforeCurrentBlock =
+          WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+              &aEditingHost,
+              EditorRawDOMPoint(
+                  inclusiveAncestorOfRightChildBlockOrError.inspect()),
+              BlockInlineCheck::UseComputedDisplayOutsideStyle);
+      if (!prevVisibleThingBeforeCurrentBlock.ReachedBRElement() &&
+          !prevVisibleThingBeforeCurrentBlock.ReachedPreformattedLineBreak()) {
+        return WSScanResult::Error();
+      }
+      // There is a preceding line break, but it may be invisible.  Then, users
+      // want to delete its preceding content not only the line break.
+      // Therefore, let's check whether the line break follows another line
+      // break or a block boundary. In these cases, the line break causes an
+      // empty line which users may want to delete.
+      const auto atPrecedingLineBreak =
+          prevVisibleThingBeforeCurrentBlock
+              .PointAtReachedContent<EditorRawDOMPoint>();
+      MOZ_ASSERT(atPrecedingLineBreak.IsSet());
+      const WSScanResult prevVisibleThingBeforeLineBreak =
+          WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+              &aEditingHost, atPrecedingLineBreak,
+              BlockInlineCheck::UseComputedDisplayOutsideStyle);
+      if (prevVisibleThingBeforeLineBreak.ReachedBRElement() ||
+          prevVisibleThingBeforeLineBreak.ReachedPreformattedLineBreak() ||
+          prevVisibleThingBeforeLineBreak.ReachedCurrentBlockBoundary()) {
+        // Target the latter line break for things simpler.  It's easier to
+        // compute the target range.
+        MOZ_ASSERT_IF(
+            prevVisibleThingBeforeCurrentBlock.ReachedPreformattedLineBreak() &&
+                prevVisibleThingBeforeLineBreak.ReachedPreformattedLineBreak(),
+            prevVisibleThingBeforeCurrentBlock
+                    .PointAtReachedContent<EditorRawDOMPoint>() !=
+                prevVisibleThingBeforeLineBreak
+                    .PointAtReachedContent<EditorRawDOMPoint>());
+        return prevVisibleThingBeforeCurrentBlock;
+      }
+      return WSScanResult::Error();
+    }();
+
+    // If previous visible thing is a <br>, we should just delete it without
+    // unwrapping the first line of the right child block.  Note that the <br>
+    // is always treated as invisible by HTMLEditUtils because it's immediately
+    // preceding <br> of the block boundary.  However, deleting it is fine
+    // because the above checks whether it causes empty line or not.
+    if (prevVisibleThing.ReachedBRElement()) {
+      mMode = Mode::DeletePrecedingBRElementOfBlock;
+      mBRElement = prevVisibleThing.BRElementPtr();
+      return true;
+    }
+
+    // Same for a preformatted line break.
+    if (prevVisibleThing.ReachedPreformattedLineBreak()) {
+      mMode = Mode::DeletePrecedingPreformattedLineBreak;
+      mPreformattedLineBreak =
+          prevVisibleThing.PointAtReachedContent<EditorRawDOMPoint>()
+              .AsInText();
+      return true;
+    }
+
     mLeftContent = ScanJoinTarget();
     mRightContent = aCaretPoint.GetContainerAs<nsIContent>();
   } else {
@@ -3283,7 +3635,8 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteNonCollapsedRanges(
       continue;
     }
     AutoBlockElementsJoiner joiner(*this);
-    if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range)) {
+    if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range,
+                                                 aEditingHost)) {
       return NS_ERROR_FAILURE;
     }
     nsresult rv =
@@ -3473,7 +3826,8 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges(
       continue;
     }
     AutoBlockElementsJoiner joiner(*this);
-    if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range)) {
+    if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range,
+                                                 aEditingHost)) {
       return Err(NS_ERROR_FAILURE);
     }
     Result<EditActionResult, nsresult> result =
@@ -3490,7 +3844,8 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges(
 
 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
     PrepareToDeleteNonCollapsedRange(const HTMLEditor& aHTMLEditor,
-                                     const nsRange& aRangeToDelete) {
+                                     const nsRange& aRangeToDelete,
+                                     const Element& aEditingHost) {
   MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   MOZ_ASSERT(!aRangeToDelete.Collapsed());
 
@@ -3528,6 +3883,125 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
     return true;
   }
 
+  // If the range starts immediately after a line end and ends in a
+  // child right block, we should not unwrap the right block unless the
+  // right block will have no nodes.
+  if (mRightContent->IsInclusiveDescendantOf(mLeftContent)) {
+    // FYI: Chrome does not remove the right child block even if there will be
+    // only single <br> or a comment node in it.  Therefore, we should use this
+    // rough check.
+    const WSScanResult nextVisibleThingOfEndBoundary =
+        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+            &aEditingHost, EditorRawDOMPoint(aRangeToDelete.EndRef()),
+            BlockInlineCheck::UseComputedDisplayOutsideStyle);
+    if (!nextVisibleThingOfEndBoundary.ReachedCurrentBlockBoundary()) {
+      MOZ_ASSERT(mLeftContent->IsElement());
+      Result<Element*, nsresult> mostDistantBlockOrError =
+          AutoBlockElementsJoiner::
+              GetMostDistantBlockAncestorIfPointIsStartAtBlock(
+                  EditorRawDOMPoint(mRightContent, 0), aEditingHost,
+                  mLeftContent->AsElement());
+      MOZ_ASSERT(mostDistantBlockOrError.isOk());
+      if (MOZ_LIKELY(mostDistantBlockOrError.inspect())) {
+        const WSScanResult prevVisibleThingOfStartBoundary =
+            WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+                &aEditingHost, EditorRawDOMPoint(aRangeToDelete.StartRef()),
+                BlockInlineCheck::UseComputedDisplayOutsideStyle);
+        if (prevVisibleThingOfStartBoundary.ReachedBRElement()) {
+          // If the range start after a <br> followed by the block boundary,
+          // we want to delete the <br> or following <br> element unless it's
+          // not a part of empty line like `<div>abc<br>{<div>]def`.
+          const WSScanResult nextVisibleThingOfBR =
+              WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+                  &aEditingHost,
+                  EditorRawDOMPoint::After(
+                      *prevVisibleThingOfStartBoundary.GetContent()),
+                  BlockInlineCheck::UseComputedDisplayOutsideStyle);
+          MOZ_ASSERT(!nextVisibleThingOfBR.ReachedCurrentBlockBoundary());
+          if (!nextVisibleThingOfBR.ReachedOtherBlockElement() ||
+              nextVisibleThingOfBR.GetContent() !=
+                  mostDistantBlockOrError.inspect()) {
+            // The range selects a non-empty line or a child block at least.
+            mMode = Mode::DeletePrecedingLinesAndContentInRange;
+            return true;
+          }
+          const WSScanResult prevVisibleThingOfBR =
+              WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+                  &aEditingHost,
+                  EditorRawDOMPoint(
+                      prevVisibleThingOfStartBoundary.GetContent()),
+                  BlockInlineCheck::UseComputedDisplayOutsideStyle);
+          if (prevVisibleThingOfBR.ReachedBRElement() ||
+              prevVisibleThingOfBR.ReachedPreformattedLineBreak() ||
+              prevVisibleThingOfBR.ReachedBlockBoundary()) {
+            // The preceding <br> causes an empty line.
+            mMode = Mode::DeletePrecedingLinesAndContentInRange;
+            return true;
+          }
+        } else if (prevVisibleThingOfStartBoundary
+                       .ReachedPreformattedLineBreak()) {
+          const WSScanResult nextVisibleThingOfLineBreak =
+              WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+                  &aEditingHost,
+                  prevVisibleThingOfStartBoundary
+                      .PointAfterReachedContent<EditorRawDOMPoint>(),
+                  BlockInlineCheck::UseComputedDisplayOutsideStyle);
+          MOZ_ASSERT(
+              !nextVisibleThingOfLineBreak.ReachedCurrentBlockBoundary());
+          if (!nextVisibleThingOfLineBreak.ReachedOtherBlockElement() ||
+              nextVisibleThingOfLineBreak.GetContent() !=
+                  mostDistantBlockOrError.inspect()) {
+            // The range selects a non-empty line or a child block at least.
+            mMode = Mode::DeletePrecedingLinesAndContentInRange;
+            return true;
+          }
+          const WSScanResult prevVisibleThingOfLineBreak =
+              WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+                  &aEditingHost,
+                  prevVisibleThingOfStartBoundary
+                      .PointAtReachedContent<EditorRawDOMPoint>(),
+                  BlockInlineCheck::UseComputedDisplayOutsideStyle);
+          if (prevVisibleThingOfLineBreak.ReachedBRElement() ||
+              prevVisibleThingOfLineBreak.ReachedPreformattedLineBreak() ||
+              prevVisibleThingOfLineBreak.ReachedBlockBoundary()) {
+            // The preceding line break causes an empty line.
+            mMode = Mode::DeletePrecedingLinesAndContentInRange;
+            return true;
+          }
+        } else if (prevVisibleThingOfStartBoundary
+                       .ReachedCurrentBlockBoundary()) {
+          MOZ_ASSERT(prevVisibleThingOfStartBoundary.ElementPtr() ==
+                     mLeftContent);
+          const WSScanResult firstVisibleThingInBlock =
+              WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+                  &aEditingHost,
+                  EditorRawDOMPoint(
+                      prevVisibleThingOfStartBoundary.ElementPtr(), 0),
+                  BlockInlineCheck::UseComputedDisplayOutsideStyle);
+          if (!firstVisibleThingInBlock.ReachedOtherBlockElement() ||
+              firstVisibleThingInBlock.ElementPtr() !=
+                  mostDistantBlockOrError.inspect()) {
+            mMode = Mode::DeletePrecedingLinesAndContentInRange;
+            return true;
+          }
+        } else if (prevVisibleThingOfStartBoundary.ReachedOtherBlockElement()) {
+          const WSScanResult firstVisibleThingAfterBlock =
+              WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+                  &aEditingHost,
+                  EditorRawDOMPoint::After(
+                      *prevVisibleThingOfStartBoundary.ElementPtr()),
+                  BlockInlineCheck::UseComputedDisplayOutsideStyle);
+          if (!firstVisibleThingAfterBlock.ReachedOtherBlockElement() ||
+              firstVisibleThingAfterBlock.ElementPtr() !=
+                  mostDistantBlockOrError.inspect()) {
+            mMode = Mode::DeletePrecedingLinesAndContentInRange;
+            return true;
+          }
+        }
+      }
+    }
+  }
+
   mMode = Mode::DeleteNonCollapsedRange;
   return true;
 }
@@ -3744,12 +4218,12 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
           aHTMLEditor.GetEditAction())) {
     WSRunScanner scanner(&aEditingHost, startOfRightContent,
                          BlockInlineCheck::UseComputedDisplayOutsideStyle);
-    WSScanResult maybePreviousText =
+    const WSScanResult maybePreviousText =
         scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(startOfRightContent);
     if (maybePreviousText.IsContentEditable() &&
         maybePreviousText.InVisibleOrCollapsibleCharacters()) {
       nsresult rv = aHTMLEditor.CollapseSelectionTo(
-          maybePreviousText.Point<EditorRawDOMPoint>());
+          maybePreviousText.PointAfterReachedContent<EditorRawDOMPoint>());
       if (NS_FAILED(rv)) {
         NS_WARNING("EditorBase::CollapseSelectionTo() failed");
         return Err(rv);
@@ -3851,6 +4325,16 @@ bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
         const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
         AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed)
         const {
+  switch (mMode) {
+    case Mode::DeletePrecedingLinesAndContentInRange:
+    case Mode::DeleteBRElement:
+    case Mode::DeletePrecedingBRElementOfBlock:
+    case Mode::DeletePrecedingPreformattedLineBreak:
+      return false;
+    default:
+      break;
+  }
+
   // If original selection was collapsed, we need always to join the nodes.
   // XXX Why?
   if (aSelectionWasCollapsed ==
@@ -3890,41 +4374,103 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
     DeleteTextAtStartAndEndOfRange(HTMLEditor& aHTMLEditor, nsRange& aRange) {
   EditorDOMPoint rangeStart(aRange.StartRef());
   EditorDOMPoint rangeEnd(aRange.EndRef());
-  if (rangeStart.IsInTextNode() && !rangeStart.IsEndOfContainer()) {
-    // Delete to last character
+  if (MOZ_UNLIKELY(aRange.Collapsed())) {
+    return NS_OK;
+  }
+
+  EditorDOMPoint pointToPutCaret;
+  // If the range is in a text node, delete middle of the text or the text node
+  // itself.
+  if (rangeStart.IsInTextNode() &&
+      rangeStart.ContainerAs<Text>() == rangeEnd.GetContainer()) {
     OwningNonNull<Text> textNode = *rangeStart.ContainerAs<Text>();
-    Result<CaretPoint, nsresult> caretPointOrError =
-        aHTMLEditor.DeleteTextWithTransaction(
-            textNode, rangeStart.Offset(),
-            rangeStart.GetContainer()->Length() - rangeStart.Offset());
-    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
-      NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
-      return caretPointOrError.unwrapErr();
+    if (rangeStart.IsStartOfContainer() && rangeEnd.IsEndOfContainer()) {
+      EditorDOMPoint pointToPutCaret(textNode);
+      AutoTrackDOMPoint trackTextNodePoint(aHTMLEditor.RangeUpdaterRef(),
+                                           &pointToPutCaret);
+      nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(textNode);
+      if (NS_FAILED(rv)) {
+        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
+        return rv;
+      }
+    } else {
+      MOZ_ASSERT(rangeEnd.Offset() - rangeStart.Offset() > 0);
+      Result<CaretPoint, nsresult> caretPointOrError =
+          aHTMLEditor.DeleteTextWithTransaction(
+              textNode, rangeStart.Offset(),
+              rangeEnd.Offset() - rangeStart.Offset());
+      if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
+        NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
+        return caretPointOrError.unwrapErr();
+      }
+      caretPointOrError.unwrap().MoveCaretPointTo(
+          pointToPutCaret, aHTMLEditor,
+          {SuggestCaret::OnlyIfHasSuggestion,
+           SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
     }
-    nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo(
-        aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
-                      SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
-                      SuggestCaret::AndIgnoreTrivialError});
-    if (NS_FAILED(rv)) {
-      NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
-      return rv;
+  } else {
+    // If the range starts in a text node and ends in a different node, delete
+    // the text after the start boundary.
+    if (rangeStart.IsInTextNode() && !rangeStart.IsEndOfContainer()) {
+      OwningNonNull<Text> textNode = *rangeStart.ContainerAs<Text>();
+      if (rangeStart.IsStartOfContainer()) {
+        pointToPutCaret.Set(textNode);
+        AutoTrackDOMPoint trackTextNodePoint(aHTMLEditor.RangeUpdaterRef(),
+                                             &pointToPutCaret);
+        nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(textNode);
+        if (NS_FAILED(rv)) {
+          NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
+          return rv;
+        }
+      } else {
+        Result<CaretPoint, nsresult> caretPointOrError =
+            aHTMLEditor.DeleteTextWithTransaction(
+                textNode, rangeStart.Offset(),
+                rangeStart.GetContainer()->Length() - rangeStart.Offset());
+        if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
+          NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
+          return caretPointOrError.unwrapErr();
+        }
+        caretPointOrError.unwrap().MoveCaretPointTo(
+            pointToPutCaret, aHTMLEditor,
+            {SuggestCaret::OnlyIfHasSuggestion,
+             SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
+      }
     }
-    NS_WARNING_ASSERTION(
-        rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
-        "CaretPoint::SuggestCaretPointTo() failed, but ignored");
-  }
-  if (rangeEnd.IsInTextNode() && !rangeEnd.IsStartOfContainer()) {
-    // Delete to first character
-    OwningNonNull<Text> textNode = *rangeEnd.ContainerAs<Text>();
-    Result<CaretPoint, nsresult> caretPointOrError =
-        aHTMLEditor.DeleteTextWithTransaction(textNode, 0, rangeEnd.Offset());
-    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
-      NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
-      return caretPointOrError.unwrapErr();
+
+    // If the range ends in a text node and starts from a different node, delete
+    // the text before the end boundary.
+    if (rangeEnd.IsInTextNode() && !rangeEnd.IsStartOfContainer()) {
+      OwningNonNull<Text> textNode = *rangeEnd.ContainerAs<Text>();
+      if (rangeEnd.IsEndOfContainer()) {
+        pointToPutCaret.Set(textNode);
+        AutoTrackDOMPoint trackTextNodePoint(aHTMLEditor.RangeUpdaterRef(),
+                                             &pointToPutCaret);
+        nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(textNode);
+        if (NS_FAILED(rv)) {
+          NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
+          return rv;
+        }
+      } else {
+        Result<CaretPoint, nsresult> caretPointOrError =
+            aHTMLEditor.DeleteTextWithTransaction(textNode, 0,
+                                                  rangeEnd.Offset());
+        if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
+          NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
+          return caretPointOrError.unwrapErr();
+        }
+        caretPointOrError.unwrap().MoveCaretPointTo(
+            pointToPutCaret, aHTMLEditor,
+            {SuggestCaret::OnlyIfHasSuggestion,
+             SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
+      }
     }
-    nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo(
-        aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
-                      SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
+  }
+
+  if (pointToPutCaret.IsSet()) {
+    CaretPoint caretPoint(std::move(pointToPutCaret));
+    nsresult rv = caretPoint.SuggestCaretPointTo(
+        aHTMLEditor, {SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
                       SuggestCaret::AndIgnoreTrivialError});
     if (NS_FAILED(rv)) {
       NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
@@ -3937,6 +4483,201 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   return NS_OK;
 }
 
+// static
+template <typename EditorDOMPointType>
+Result<Element*, nsresult> HTMLEditor::AutoDeleteRangesHandler::
+    AutoBlockElementsJoiner::GetMostDistantBlockAncestorIfPointIsStartAtBlock(
+        const EditorDOMPointType& aPoint, const Element& aEditingHost,
+        const Element* aAncestorLimiter /* = nullptr */) {
+  MOZ_ASSERT(aPoint.IsSetAndValid());
+  MOZ_ASSERT(aPoint.IsInComposedDoc());
+
+  if (!aAncestorLimiter) {
+    aAncestorLimiter = &aEditingHost;
+  }
+
+  const auto ReachedCurrentBlockBoundaryWhichWeCanCross =
+      [&aEditingHost, aAncestorLimiter](const WSScanResult& aScanResult) {
+        // When the scan result is "reached current block boundary", it may not
+        // be so literally.
+        return aScanResult.ReachedCurrentBlockBoundary() &&
+               HTMLEditUtils::IsRemovableFromParentNode(
+                   *aScanResult.ElementPtr()) &&
+               aScanResult.ElementPtr() != &aEditingHost &&
+               aScanResult.ElementPtr() != aAncestorLimiter &&
+               // Don't cross <body>, <head> and <html>
+               !aScanResult.ElementPtr()->IsAnyOfHTMLElements(
+                   nsGkAtoms::body, nsGkAtoms::head, nsGkAtoms::html) &&
+               // Don't cross table elements
+               !HTMLEditUtils::IsAnyTableElement(aScanResult.ElementPtr());
+      };
+
+  const WSScanResult prevVisibleThing =
+      WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+          aAncestorLimiter, aPoint,
+          BlockInlineCheck::UseComputedDisplayOutsideStyle);
+  if (!ReachedCurrentBlockBoundaryWhichWeCanCross(prevVisibleThing)) {
+    return nullptr;
+  }
+  MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
+      *prevVisibleThing.ElementPtr(),
+      BlockInlineCheck::UseComputedDisplayOutsideStyle));
+  for (Element* ancestorBlock = prevVisibleThing.ElementPtr(); ancestorBlock;) {
+    const WSScanResult prevVisibleThing =
+        WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+            aAncestorLimiter, EditorRawDOMPoint(ancestorBlock),
+            BlockInlineCheck::UseComputedDisplayOutsideStyle);
+    if (!ReachedCurrentBlockBoundaryWhichWeCanCross(prevVisibleThing)) {
+      return ancestorBlock;
+    }
+    MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
+        *prevVisibleThing.ElementPtr(),
+        BlockInlineCheck::UseComputedDisplayOutsideStyle));
+    ancestorBlock = prevVisibleThing.ElementPtr();
+  }
+  return Err(NS_ERROR_FAILURE);
+}
+
+void HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
+    ExtendRangeToDeleteNonCollapsedRange(
+        const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete,
+        const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const {
+  MOZ_ASSERT_IF(aComputeRangeFor == ComputeRangeFor::GetTargetRanges,
+                aRangeToDelete.IsPositioned());
+  MOZ_ASSERT(!aRangeToDelete.Collapsed());
+  MOZ_ASSERT(mLeftContent);
+  MOZ_ASSERT(mLeftContent->IsElement());
+  MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf(
+      mLeftContent));
+  MOZ_ASSERT(mRightContent);
+  MOZ_ASSERT(mRightContent->IsElement());
+  MOZ_ASSERT(
+      aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent));
+
+  const DebugOnly<Result<bool, nsresult>> extendRangeResult =
+      AutoDeleteRangesHandler::
+          ExtendRangeToContainAncestorInlineElementsAtStart(aRangeToDelete,
+                                                            aEditingHost);
+  NS_WARNING_ASSERTION(extendRangeResult.value.isOk(),
+                       "AutoDeleteRangesHandler::"
+                       "ExtendRangeToContainAncestorInlineElementsAtStart() "
+                       "failed, but ignored");
+  if (mMode != Mode::DeletePrecedingLinesAndContentInRange) {
+    return;
+  }
+
+  // If we're computing for beforeinput.getTargetRanges() and the inputType
+  // is not a simple deletion like replacing selected content with new
+  // content, the range should end at the original end boundary of the given
+  // range even if we're deleting only preceding lines of the right child
+  // block.
+  const bool preserveEndBoundary =
+      aComputeRangeFor == ComputeRangeFor::GetTargetRanges &&
+      !MayEditActionDeleteAroundCollapsedSelection(aHTMLEditor.GetEditAction());
+  // We need to delete only the preceding lines of the right block. Therefore,
+  // we need to shrink the range to ends before the right block if the range
+  // does not contain any meaningful content in the right block.
+  const Result<Element*, nsresult> inclusiveAncestorCurrentBlockOrError =
+      AutoBlockElementsJoiner::GetMostDistantBlockAncestorIfPointIsStartAtBlock(
+          EditorRawDOMPoint(aRangeToDelete.EndRef()), aEditingHost,
+          mLeftContent->AsElement());
+  MOZ_ASSERT(inclusiveAncestorCurrentBlockOrError.isOk());
+  MOZ_ASSERT_IF(inclusiveAncestorCurrentBlockOrError.inspect(),
+                mRightContent->IsInclusiveDescendantOf(
+                    inclusiveAncestorCurrentBlockOrError.inspect()));
+  if (MOZ_UNLIKELY(!inclusiveAncestorCurrentBlockOrError.isOk() ||
+                   !inclusiveAncestorCurrentBlockOrError.inspect())) {
+    return;
+  }
+
+  const WSScanResult prevVisibleThingOfStartBoundary =
+      WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+          &aEditingHost, EditorRawDOMPoint(aRangeToDelete.StartRef()),
+          BlockInlineCheck::UseComputedDisplayOutsideStyle);
+  // If the range starts after an invisible <br> of empty line immediately
+  // before the most distant inclusive ancestor of the right block like
+  // `<br><br>{<div>]abc`, we should delete the last empty line because
+  // users won't see any reaction of the builtin editor in this case.
+  if (prevVisibleThingOfStartBoundary.ReachedBRElement() ||
+      prevVisibleThingOfStartBoundary.ReachedPreformattedLineBreak()) {
+    const WSScanResult prevVisibleThingOfPreviousLineBreak =
+        WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+            &aEditingHost,
+            prevVisibleThingOfStartBoundary
+                .PointAtReachedContent<EditorRawDOMPoint>(),
+            BlockInlineCheck::UseComputedDisplayOutsideStyle);
+    const WSScanResult nextVisibleThingOfPreviousBR =
+        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+            &aEditingHost,
+            prevVisibleThingOfStartBoundary
+                .PointAfterReachedContent<EditorRawDOMPoint>(),
+            BlockInlineCheck::UseComputedDisplayOutsideStyle);
+    if ((prevVisibleThingOfPreviousLineBreak.ReachedBRElement() ||
+         prevVisibleThingOfPreviousLineBreak.ReachedPreformattedLineBreak()) &&
+        nextVisibleThingOfPreviousBR.ReachedOtherBlockElement() &&
+        nextVisibleThingOfPreviousBR.ElementPtr() ==
+            inclusiveAncestorCurrentBlockOrError.inspect()) {
+      aRangeToDelete.SetStart(prevVisibleThingOfStartBoundary
+                                  .PointAtReachedContent<EditorRawDOMPoint>()
+                                  .ToRawRangeBoundary(),
+                              IgnoreErrors());
+    }
+  }
+
+  if (preserveEndBoundary) {
+    return;
+  }
+
+  if (aComputeRangeFor == ComputeRangeFor::GetTargetRanges) {
+    // When we set the end boundary to around the right block, the new end
+    // boundary should not after inline ancestors of the line break which won't
+    // be deleted.
+    const WSScanResult lastVisibleThingBeforeRightChildBlock =
+        [&]() -> WSScanResult {
+      EditorRawDOMPoint scanStartPoint(aRangeToDelete.StartRef());
+      WSScanResult lastScanResult = WSScanResult::Error();
+      while (true) {
+        WSScanResult scanResult =
+            WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
+                mLeftContent->AsElement(), scanStartPoint,
+                BlockInlineCheck::UseComputedDisplayOutsideStyle);
+        if (scanResult.ReachedBlockBoundary() ||
+            scanResult.ReachedInlineEditingHostBoundary()) {
+          return lastScanResult;
+        }
+        scanStartPoint =
+            scanResult.PointAfterReachedContent<EditorRawDOMPoint>();
+        lastScanResult = scanResult;
+      }
+    }();
+    if (lastVisibleThingBeforeRightChildBlock.GetContent()) {
+      const nsIContent* commonAncestor = nsIContent::FromNode(
+          nsContentUtils::GetClosestCommonInclusiveAncestor(
+              aRangeToDelete.StartRef().Container(),
+              lastVisibleThingBeforeRightChildBlock.GetContent()));
+      MOZ_ASSERT(commonAncestor);
+      if (commonAncestor &&
+          !mRightContent->IsInclusiveDescendantOf(commonAncestor)) {
+        IgnoredErrorResult error;
+        aRangeToDelete.SetEnd(
+            EditorRawDOMPoint::AtEndOf(*commonAncestor).ToRawRangeBoundary(),
+            error);
+        NS_WARNING_ASSERTION(!error.Failed(),
+                             "nsRange::SetEnd() failed, but ignored");
+        return;
+      }
+    }
+  }
+
+  IgnoredErrorResult error;
+  aRangeToDelete.SetEnd(
+      EditorRawDOMPoint(inclusiveAncestorCurrentBlockOrError.inspect())
+          .ToRawRangeBoundary(),
+      error);
+  NS_WARNING_ASSERTION(!error.Failed(),
+                       "nsRange::SetEnd() failed, but ignored");
+}
+
 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
     ComputeRangeToDeleteNonCollapsedRange(
         const HTMLEditor& aHTMLEditor,
@@ -3954,6 +4695,10 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   MOZ_ASSERT(
       aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent));
 
+  ExtendRangeToDeleteNonCollapsedRange(aHTMLEditor, aRangeToDelete,
+                                       aEditingHost,
+                                       ComputeRangeFor::GetTargetRanges);
+
   Result<bool, nsresult> result =
       ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure(
           aHTMLEditor, aRangeToDelete, aSelectionWasCollapsed);
@@ -4003,14 +4748,20 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   MOZ_ASSERT(!aRangeToDelete.Collapsed());
   MOZ_ASSERT(mDeleteRangesHandler);
-  MOZ_ASSERT(mLeftContent);
-  MOZ_ASSERT(mLeftContent->IsElement());
-  MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf(
-      mLeftContent));
-  MOZ_ASSERT(mRightContent);
-  MOZ_ASSERT(mRightContent->IsElement());
-  MOZ_ASSERT(
-      aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent));
+
+  const bool isDeletingLineBreak =
+      mMode == Mode::DeleteBRElement ||
+      mMode == Mode::DeletePrecedingBRElementOfBlock ||
+      mMode == Mode::DeletePrecedingPreformattedLineBreak;
+  if (!isDeletingLineBreak) {
+    MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf(
+        mLeftContent));
+    MOZ_ASSERT(aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(
+        mRightContent));
+    ExtendRangeToDeleteNonCollapsedRange(aHTMLEditor, aRangeToDelete,
+                                         aEditingHost,
+                                         ComputeRangeFor::ToDeleteTheRange);
+  }
 
   const bool backspaceInRightBlock =
       aSelectionWasCollapsed == SelectionWasCollapsed::Yes &&
@@ -4034,7 +4785,8 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
       return deleteResult.propagateErr();
     }
 
-    const bool joinInclusiveAncestorBlockElements = deleteResult.unwrap();
+    const bool joinInclusiveAncestorBlockElements =
+        !isDeletingLineBreak && deleteResult.unwrap();
 
     // Check endpoints for possible text deletion.  We can assume that if
     // text node is found, we can delete to end or to beginning as
@@ -4048,9 +4800,24 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
     }
 
     if (!joinInclusiveAncestorBlockElements) {
+      // When we delete only preceding lines of the right child block, we should
+      // put caret into start of the right block.
+      if (mMode == Mode::DeletePrecedingLinesAndContentInRange) {
+        result.MarkAsHandled();
+        if (MOZ_LIKELY(mRightContent->IsInComposedDoc())) {
+          pointToPutCaret =
+              HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
+                  *mRightContent);
+        }
+      }
       break;
     }
 
+    MOZ_ASSERT(mLeftContent);
+    MOZ_ASSERT(mLeftContent->IsElement());
+    MOZ_ASSERT(mRightContent);
+    MOZ_ASSERT(mRightContent->IsElement());
+
     AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent,
                                                     *mRightContent);
     Result<bool, nsresult> canJoinThem =
@@ -4106,6 +4873,12 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
     break;
   }
 
+  // HandleDeleteLineBreak() should handle the new caret position by itself.
+  if (isDeletingLineBreak) {
+    result.MarkAsHandled();
+    return result;
+  }
+
   // If we're deleting selection (not replacing with new content) and
   // AutoInclusiveAncestorBlockElementsJoiner computed new caret position, we
   // should use it.  Otherwise, we should keep the traditional behavior.
@@ -4297,7 +5070,8 @@ HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
   RefPtr<Element> editingHost = aHTMLEditor.ComputeEditingHost();
   WSRunScanner wsScannerForPoint(
       editingHost, aPoint, BlockInlineCheck::UseComputedDisplayOutsideStyle);
-  if (!wsScannerForPoint.StartsFromCurrentBlockBoundary()) {
+  if (!wsScannerForPoint.StartsFromCurrentBlockBoundary() &&
+      !wsScannerForPoint.StartsFromInlineEditingHostBoundary()) {
     // If there is visible node before the point, we shouldn't remove the
     // parent block.
     return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
@@ -4319,8 +5093,8 @@ HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
   }
 
   // Next, check there is visible contents after the point in current block.
-  WSScanResult forwardScanFromPointResult =
-      wsScannerForPoint.ScanNextVisibleNodeOrBlockBoundaryFrom(aPoint);
+  const WSScanResult forwardScanFromPointResult =
+      wsScannerForPoint.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aPoint);
   if (forwardScanFromPointResult.Failed()) {
     NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
     return NS_ERROR_FAILURE;
@@ -4340,8 +5114,8 @@ HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
       return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
     }
     if (wsScannerForPoint.GetEndReasonContent()->GetNextSibling()) {
-      WSScanResult scanResult =
-          WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
+      const WSScanResult scanResult =
+          WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
               editingHost,
               EditorRawDOMPoint::After(
                   *wsScannerForPoint.GetEndReasonContent()),
@@ -4350,13 +5124,15 @@ HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
         NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
         return NS_ERROR_FAILURE;
       }
-      if (!scanResult.ReachedCurrentBlockBoundary()) {
+      if (!scanResult.ReachedCurrentBlockBoundary() &&
+          !scanResult.ReachedInlineEditingHostBoundary()) {
         // If we couldn't reach the block's end after the invisible <br>,
         // that means that there is visible content.
         return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
       }
     }
-  } else if (!forwardScanFromPointResult.ReachedCurrentBlockBoundary()) {
+  } else if (!forwardScanFromPointResult.ReachedCurrentBlockBoundary() &&
+             !forwardScanFromPointResult.ReachedInlineEditingHostBoundary()) {
     // If we couldn't reach the block's end, the block has visible content.
     return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
   }
@@ -5169,11 +5945,12 @@ Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
           aHTMLEditor.GetEditAction())) {
     WSRunScanner scanner(&aEditingHost, startOfRightContent,
                          BlockInlineCheck::UseComputedDisplayStyle);
-    WSScanResult maybePreviousText =
+    const WSScanResult maybePreviousText =
         scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(startOfRightContent);
     if (maybePreviousText.IsContentEditable() &&
         maybePreviousText.InVisibleOrCollapsibleCharacters()) {
-      mPointToPutCaret = maybePreviousText.Point<EditorDOMPoint>();
+      mPointToPutCaret =
+          maybePreviousText.PointAfterReachedContent<EditorDOMPoint>();
     }
   }
   return result;
@@ -6798,11 +7575,12 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
   if (rangeToDelete.StartRef().GetContainer() !=
       closestBlockAncestorOrInlineEditingHost) {
     for (;;) {
-      WSScanResult backwardScanFromStartResult =
+      const WSScanResult backwardScanFromStartResult =
           WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
               closestEditingHost, rangeToDelete.StartRef(),
               BlockInlineCheck::UseComputedDisplayOutsideStyle);
-      if (!backwardScanFromStartResult.ReachedCurrentBlockBoundary()) {
+      if (!backwardScanFromStartResult.ReachedCurrentBlockBoundary() &&
+          !backwardScanFromStartResult.ReachedInlineEditingHostBoundary()) {
         break;
       }
       MOZ_ASSERT(backwardScanFromStartResult.GetContent() ==
@@ -6834,8 +7612,8 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
               *backwardScanFromStartResult.ElementPtr())) {
         break;
       }
-      rangeToDelete.SetStart(
-          backwardScanFromStartResult.PointAtContent<EditorRawDOMPoint>());
+      rangeToDelete.SetStart(backwardScanFromStartResult
+                                 .PointAtReachedContent<EditorRawDOMPoint>());
     }
     if (aFrameSelection && !aFrameSelection->IsValidSelectionPoint(
                                rangeToDelete.StartRef().GetContainer())) {
@@ -6856,8 +7634,8 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
       WSRunScanner wsScannerAtEnd(
           closestEditingHost, rangeToDelete.EndRef(),
           BlockInlineCheck::UseComputedDisplayOutsideStyle);
-      WSScanResult forwardScanFromEndResult =
-          wsScannerAtEnd.ScanNextVisibleNodeOrBlockBoundaryFrom(
+      const WSScanResult forwardScanFromEndResult =
+          wsScannerAtEnd.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
               rangeToDelete.EndRef());
       if (forwardScanFromEndResult.ReachedBRElement()) {
         // XXX In my understanding, this is odd.  The end reason may not be
@@ -6881,7 +7659,9 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
         continue;
       }
 
-      if (forwardScanFromEndResult.ReachedCurrentBlockBoundary()) {
+      if (forwardScanFromEndResult.ReachedCurrentBlockBoundary() ||
+          forwardScanFromEndResult.ReachedInlineEditingHostBoundary()) {
+        MOZ_ASSERT(forwardScanFromEndResult.ContentIsElement());
         MOZ_ASSERT(forwardScanFromEndResult.GetContent() ==
                    wsScannerAtEnd.GetEndReasonContent());
         // We want to keep looking up.  But stop if we are crossing table
@@ -6895,13 +7675,13 @@ HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
         // Don't cross flex-item/grid-item boundary to make new content inserted
         // into it.
         if (StaticPrefs::editor_block_inline_check_use_computed_style() &&
-            forwardScanFromEndResult.ContentIsElement() &&
             HTMLEditUtils::IsFlexOrGridItem(
                 *forwardScanFromEndResult.ElementPtr())) {
           break;
         }
         rangeToDelete.SetEnd(
-            forwardScanFromEndResult.PointAfterContent<EditorRawDOMPoint>());
+            forwardScanFromEndResult
+                .PointAfterReachedContent<EditorRawDOMPoint>());
         continue;
       }
 
-- 
cgit v1.2.3