tor-browser

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

HTMLEditorDeleteHandler.cpp (347349B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=2 sw=2 et tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "HTMLEditor.h"
      8 #include "HTMLEditorNestedClasses.h"
      9 
     10 #include <utility>
     11 
     12 #include "AutoClonedRangeArray.h"
     13 #include "CSSEditUtils.h"
     14 #include "EditAction.h"
     15 #include "EditorDOMAPIWrapper.h"
     16 #include "EditorDOMPoint.h"
     17 #include "EditorLineBreak.h"
     18 #include "EditorUtils.h"
     19 #include "HTMLEditHelpers.h"
     20 #include "HTMLEditorInlines.h"
     21 #include "HTMLEditUtils.h"
     22 #include "WhiteSpaceVisibilityKeeper.h"
     23 #include "WSRunScanner.h"
     24 
     25 #include "ErrorList.h"
     26 #include "mozilla/Assertions.h"
     27 #include "mozilla/ContentIterator.h"
     28 #include "mozilla/Logging.h"
     29 #include "mozilla/Maybe.h"
     30 #include "mozilla/OwningNonNull.h"
     31 #include "mozilla/SelectionState.h"
     32 #include "mozilla/StaticPrefs_editor.h"  // for StaticPrefs::editor_*
     33 #include "mozilla/dom/AncestorIterator.h"
     34 #include "mozilla/dom/Element.h"
     35 #include "mozilla/dom/ElementInlines.h"  // for Element::IsContentEditablePlainTextOnly
     36 #include "mozilla/dom/HTMLBRElement.h"
     37 #include "mozilla/dom/Selection.h"
     38 #include "nsAtom.h"
     39 #include "nsComputedDOMStyle.h"  // for nsComputedDOMStyle
     40 #include "nsContentUtils.h"
     41 #include "nsDebug.h"
     42 #include "nsError.h"
     43 #include "nsFrameSelection.h"
     44 #include "nsGkAtoms.h"
     45 #include "nsIContent.h"
     46 #include "nsINode.h"
     47 #include "nsRange.h"
     48 #include "nsString.h"
     49 #include "nsStringFwd.h"
     50 #include "nsStyleConsts.h"  // for StyleWhiteSpace
     51 #include "nsTArray.h"
     52 
     53 // NOTE: This file was split from:
     54 //   https://searchfox.org/mozilla-central/rev/c409dd9235c133ab41eba635f906aa16e050c197/editor/libeditor/HTMLEditSubActionHandler.cpp
     55 
     56 namespace mozilla {
     57 
     58 using namespace dom;
     59 using EditablePointOption = HTMLEditUtils::EditablePointOption;
     60 using EditablePointOptions = HTMLEditUtils::EditablePointOptions;
     61 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
     62 using InvisibleWhiteSpaces = HTMLEditUtils::InvisibleWhiteSpaces;
     63 using LeafNodeType = HTMLEditUtils::LeafNodeType;
     64 using ScanLineBreak = HTMLEditUtils::ScanLineBreak;
     65 using TableBoundary = HTMLEditUtils::TableBoundary;
     66 using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
     67 
     68 static LazyLogModule gOneLineMoverLog("AutoMoveOneLineHandler");
     69 
     70 template Result<CaretPoint, nsresult>
     71 HTMLEditor::DeleteTextAndTextNodesWithTransaction(
     72    const EditorDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint,
     73    TreatEmptyTextNodes aTreatEmptyTextNodes);
     74 template Result<CaretPoint, nsresult>
     75 HTMLEditor::DeleteTextAndTextNodesWithTransaction(
     76    const EditorDOMPointInText& aStartPoint,
     77    const EditorDOMPointInText& aEndPoint,
     78    TreatEmptyTextNodes aTreatEmptyTextNodes);
     79 
     80 bool HTMLEditor::AutoDeleteRangesHandler::
     81    CanFallbackToDeleteRangeWithTransaction(
     82        const nsRange& aRangeToDelete) const {
     83  return !IsHandlingRecursively() &&
     84         (!aRangeToDelete.Collapsed() ||
     85          EditorBase::HowToHandleCollapsedRangeFor(
     86              mOriginalDirectionAndAmount) !=
     87              EditorBase::HowToHandleCollapsedRange::Ignore);
     88 }
     89 
     90 bool HTMLEditor::AutoDeleteRangesHandler::
     91    CanFallbackToDeleteRangesWithTransaction(
     92        const AutoClonedSelectionRangeArray& aRangesToDelete) const {
     93  return !IsHandlingRecursively() && !aRangesToDelete.Ranges().IsEmpty() &&
     94         (!aRangesToDelete.IsCollapsed() ||
     95          EditorBase::HowToHandleCollapsedRangeFor(
     96              mOriginalDirectionAndAmount) !=
     97              EditorBase::HowToHandleCollapsedRange::Ignore);
     98 }
     99 
    100 Result<CaretPoint, nsresult>
    101 HTMLEditor::AutoDeleteRangesHandler::FallbackToDeleteRangesWithTransaction(
    102    HTMLEditor& aHTMLEditor, AutoClonedSelectionRangeArray& aRangesToDelete,
    103    const Element& aEditingHost) const {
    104  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
    105  MOZ_ASSERT(CanFallbackToDeleteRangesWithTransaction(aRangesToDelete));
    106 
    107  const auto stripWrappers = [&]() -> nsIEditor::EStripWrappers {
    108    if (mOriginalStripWrappers == nsIEditor::eStrip &&
    109        aEditingHost.IsContentEditablePlainTextOnly()) {
    110      return nsIEditor::eNoStrip;
    111    }
    112    return mOriginalStripWrappers;
    113  }();
    114 
    115  {
    116    AutoTrackDOMRange firstRangeTracker(aHTMLEditor.RangeUpdaterRef(),
    117                                        &aRangesToDelete.FirstRangeRef());
    118    for (OwningNonNull<nsRange>& range : Reversed(aRangesToDelete.Ranges())) {
    119      if (MOZ_UNLIKELY(!range->IsPositioned() || range->Collapsed())) {
    120        continue;
    121      }
    122      Maybe<AutoTrackDOMRange> trackRange;
    123      if (range != aRangesToDelete.FirstRangeRef()) {
    124        trackRange.emplace(aHTMLEditor.RangeUpdaterRef(), &range);
    125      }
    126      Result<EditorDOMRange, nsresult> rangeToDeleteOrError =
    127          WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToJoin(
    128              aHTMLEditor, EditorDOMRange(range));
    129      if (MOZ_UNLIKELY(rangeToDeleteOrError.isErr())) {
    130        NS_WARNING(
    131            "WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToJoin("
    132            ") failed");
    133        return rangeToDeleteOrError.propagateErr();
    134      }
    135      trackRange.reset();
    136      EditorDOMRange rangeToDelete = rangeToDeleteOrError.unwrap();
    137      if (MOZ_LIKELY(rangeToDelete.IsPositionedAndValidInComposedDoc())) {
    138        nsresult rv =
    139            range->SetStartAndEnd(rangeToDelete.StartRef().ToRawRangeBoundary(),
    140                                  rangeToDelete.EndRef().ToRawRangeBoundary());
    141        if (NS_FAILED(rv)) {
    142          NS_WARNING("nsRange::SetStartAndEnd() failed");
    143          return Err(rv);
    144        }
    145      }
    146    }
    147  }
    148  aRangesToDelete.RemoveCollapsedRanges();
    149  if (MOZ_UNLIKELY(aRangesToDelete.IsCollapsed())) {
    150    return CaretPoint(
    151        EditorDOMPoint(aRangesToDelete.FirstRangeRef()->StartRef()));
    152  }
    153 
    154  Result<CaretPoint, nsresult> caretPointOrError =
    155      aHTMLEditor.DeleteRangesWithTransaction(mOriginalDirectionAndAmount,
    156                                              stripWrappers, aRangesToDelete);
    157  NS_WARNING_ASSERTION(caretPointOrError.isOk(),
    158                       "HTMLEditor::DeleteRangesWithTransaction() failed");
    159  return caretPointOrError;
    160 }
    161 
    162 nsresult
    163 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction(
    164    const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
    165    AutoClonedSelectionRangeArray& aRangesToDelete,
    166    const Element& aEditingHost) const {
    167  MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
    168  const EditorBase::HowToHandleCollapsedRange howToHandleCollapsedRange =
    169      EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount);
    170  if (NS_WARN_IF(aRangesToDelete.IsCollapsed() &&
    171                 howToHandleCollapsedRange ==
    172                     EditorBase::HowToHandleCollapsedRange::Ignore)) {
    173    return NS_ERROR_FAILURE;
    174  }
    175 
    176  const auto stripWrappers = [&]() -> nsIEditor::EStripWrappers {
    177    if (mOriginalStripWrappers == nsIEditor::eStrip &&
    178        aEditingHost.IsContentEditablePlainTextOnly()) {
    179      return nsIEditor::eNoStrip;
    180    }
    181    return mOriginalStripWrappers;
    182  }();
    183 
    184  aRangesToDelete.ExtendRangeToContainSurroundingInvisibleWhiteSpaces(
    185      stripWrappers);
    186  if (MOZ_UNLIKELY(aRangesToDelete.IsCollapsed() &&
    187                   howToHandleCollapsedRange ==
    188                       EditorBase::HowToHandleCollapsedRange::Ignore)) {
    189    return NS_OK;
    190  }
    191 
    192  for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) {
    193    if (range->Collapsed()) {
    194      continue;
    195    }
    196    nsresult rv = ComputeRangeToDeleteRangeWithTransaction(
    197        aHTMLEditor, aDirectionAndAmount, range, aEditingHost);
    198    if (NS_FAILED(rv)) {
    199      NS_WARNING(
    200          "AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction() "
    201          "failed");
    202      return rv;
    203    }
    204  }
    205  return NS_OK;
    206 }
    207 
    208 Result<EditActionResult, nsresult>
    209 HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::Run(
    210    HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
    211    nsIEditor::EStripWrappers aStripWrappers, const EditorDOMPoint& aCaretPoint,
    212    nsRange& aRangeToDelete, const Element& aEditingHost) {
    213  switch (mMode) {
    214    case Mode::JoinCurrentBlock: {
    215      Result<EditActionResult, nsresult> result =
    216          HandleDeleteAtCurrentBlockBoundary(aHTMLEditor, aDirectionAndAmount,
    217                                             aCaretPoint, aEditingHost);
    218      NS_WARNING_ASSERTION(result.isOk(),
    219                           "AutoBlockElementsJoiner::"
    220                           "HandleDeleteAtCurrentBlockBoundary() failed");
    221      return result;
    222    }
    223    case Mode::JoinOtherBlock: {
    224      Result<EditActionResult, nsresult> result =
    225          HandleDeleteAtOtherBlockBoundary(aHTMLEditor, aDirectionAndAmount,
    226                                           aStripWrappers, aCaretPoint,
    227                                           aRangeToDelete, aEditingHost);
    228      NS_WARNING_ASSERTION(
    229          result.isOk(),
    230          "AutoBlockElementsJoiner::HandleDeleteAtOtherBlockBoundary() failed");
    231      return result;
    232    }
    233    case Mode::DeleteBRElement:
    234    case Mode::DeletePrecedingBRElementOfBlock:
    235    case Mode::DeletePrecedingPreformattedLineBreak: {
    236      Result<EditActionResult, nsresult> result = HandleDeleteLineBreak(
    237          aHTMLEditor, aDirectionAndAmount, aCaretPoint, aEditingHost);
    238      NS_WARNING_ASSERTION(
    239          result.isOk(),
    240          "AutoBlockElementsJoiner::HandleDeleteLineBreak() failed");
    241      return result;
    242    }
    243    case Mode::JoinBlocksInSameParent:
    244    case Mode::DeleteContentInRange:
    245    case Mode::DeleteNonCollapsedRange:
    246    case Mode::DeletePrecedingLinesAndContentInRange:
    247      MOZ_ASSERT_UNREACHABLE("This mode should be handled in the other Run()");
    248      return Err(NS_ERROR_UNEXPECTED);
    249    case Mode::NotInitialized:
    250      return EditActionResult::IgnoredResult();
    251  }
    252  return Err(NS_ERROR_NOT_INITIALIZED);
    253 }
    254 
    255 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
    256    ComputeRangeToDelete(const HTMLEditor& aHTMLEditor,
    257                         nsIEditor::EDirection aDirectionAndAmount,
    258                         const EditorDOMPoint& aCaretPoint,
    259                         nsRange& aRangeToDelete,
    260                         const Element& aEditingHost) const {
    261  switch (mMode) {
    262    case Mode::JoinCurrentBlock: {
    263      nsresult rv = ComputeRangeToDeleteAtCurrentBlockBoundary(
    264          aHTMLEditor, aCaretPoint, aRangeToDelete, aEditingHost);
    265      NS_WARNING_ASSERTION(
    266          NS_SUCCEEDED(rv),
    267          "AutoBlockElementsJoiner::ComputeRangeToDeleteAtCurrentBlockBoundary("
    268          ") failed");
    269      return rv;
    270    }
    271    case Mode::JoinOtherBlock: {
    272      nsresult rv = ComputeRangeToDeleteAtOtherBlockBoundary(
    273          aHTMLEditor, aDirectionAndAmount, aCaretPoint, aRangeToDelete,
    274          aEditingHost);
    275      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    276                           "AutoBlockElementsJoiner::"
    277                           "ComputeRangeToDeleteAtOtherBlockBoundary() failed");
    278      return rv;
    279    }
    280    case Mode::DeleteBRElement:
    281    case Mode::DeletePrecedingBRElementOfBlock:
    282    case Mode::DeletePrecedingPreformattedLineBreak: {
    283      nsresult rv = ComputeRangeToDeleteLineBreak(
    284          aHTMLEditor, aRangeToDelete, aEditingHost,
    285          ComputeRangeFor::GetTargetRanges);
    286      NS_WARNING_ASSERTION(
    287          NS_SUCCEEDED(rv),
    288          "AutoBlockElementsJoiner::ComputeRangeToDeleteLineBreak() failed");
    289      return rv;
    290    }
    291    case Mode::JoinBlocksInSameParent:
    292    case Mode::DeleteContentInRange:
    293    case Mode::DeleteNonCollapsedRange:
    294    case Mode::DeletePrecedingLinesAndContentInRange:
    295      MOZ_ASSERT_UNREACHABLE(
    296          "This mode should be handled in the other ComputeRangesToDelete()");
    297      return NS_ERROR_UNEXPECTED;
    298    case Mode::NotInitialized:
    299      return NS_OK;
    300  }
    301  return NS_ERROR_NOT_IMPLEMENTED;
    302 }
    303 
    304 Result<EditActionResult, nsresult>
    305 HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::Run(
    306    HTMLEditor& aHTMLEditor, const LimitersAndCaretData& aLimitersAndCaretData,
    307    nsIEditor::EDirection aDirectionAndAmount,
    308    nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete,
    309    AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed,
    310    const Element& aEditingHost) {
    311  switch (mMode) {
    312    case Mode::JoinCurrentBlock:
    313    case Mode::JoinOtherBlock:
    314    case Mode::DeleteBRElement:
    315    case Mode::DeletePrecedingBRElementOfBlock:
    316    case Mode::DeletePrecedingPreformattedLineBreak:
    317      MOZ_ASSERT_UNREACHABLE("This mode should be handled in the other Run()");
    318      return Err(NS_ERROR_UNEXPECTED);
    319    case Mode::JoinBlocksInSameParent: {
    320      Result<EditActionResult, nsresult> result = JoinBlockElementsInSameParent(
    321          aHTMLEditor, aLimitersAndCaretData, aDirectionAndAmount,
    322          aStripWrappers, aRangeToDelete, aSelectionWasCollapsed, aEditingHost);
    323      NS_WARNING_ASSERTION(
    324          result.isOk(),
    325          "AutoBlockElementsJoiner::JoinBlockElementsInSameParent() failed");
    326      return result;
    327    }
    328    case Mode::DeleteContentInRange: {
    329      Result<EditActionResult, nsresult> result = DeleteContentInRange(
    330          aHTMLEditor, aLimitersAndCaretData, aDirectionAndAmount,
    331          aStripWrappers, aRangeToDelete, aEditingHost);
    332      NS_WARNING_ASSERTION(
    333          result.isOk(),
    334          "AutoBlockElementsJoiner::DeleteContentInRange() failed");
    335      return result;
    336    }
    337    case Mode::DeleteNonCollapsedRange:
    338    case Mode::DeletePrecedingLinesAndContentInRange: {
    339      Result<EditActionResult, nsresult> result = HandleDeleteNonCollapsedRange(
    340          aHTMLEditor, aDirectionAndAmount, aStripWrappers, aRangeToDelete,
    341          aSelectionWasCollapsed, aEditingHost);
    342      NS_WARNING_ASSERTION(
    343          result.isOk(),
    344          "AutoBlockElementsJoiner::HandleDeleteNonCollapsedRange() failed");
    345      return result;
    346    }
    347    case Mode::NotInitialized:
    348      MOZ_ASSERT_UNREACHABLE("Call Run() after calling a preparation method");
    349      return EditActionResult::IgnoredResult();
    350  }
    351  return Err(NS_ERROR_NOT_INITIALIZED);
    352 }
    353 
    354 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
    355    ComputeRangeToDelete(
    356        const HTMLEditor& aHTMLEditor,
    357        const AutoClonedSelectionRangeArray& aRangesToDelete,
    358        nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete,
    359        AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed,
    360        const Element& aEditingHost) const {
    361  switch (mMode) {
    362    case Mode::JoinCurrentBlock:
    363    case Mode::JoinOtherBlock:
    364    case Mode::DeleteBRElement:
    365    case Mode::DeletePrecedingBRElementOfBlock:
    366    case Mode::DeletePrecedingPreformattedLineBreak:
    367      MOZ_ASSERT_UNREACHABLE(
    368          "This mode should be handled in the other ComputeRangesToDelete()");
    369      return NS_ERROR_UNEXPECTED;
    370    case Mode::JoinBlocksInSameParent: {
    371      nsresult rv = ComputeRangeToJoinBlockElementsInSameParent(
    372          aHTMLEditor, aDirectionAndAmount, aRangeToDelete, aEditingHost);
    373      NS_WARNING_ASSERTION(
    374          NS_SUCCEEDED(rv),
    375          "AutoBlockElementsJoiner::"
    376          "ComputeRangesToJoinBlockElementsInSameParent() failed");
    377      return rv;
    378    }
    379    case Mode::DeleteContentInRange: {
    380      nsresult rv = ComputeRangeToDeleteContentInRange(
    381          aHTMLEditor, aDirectionAndAmount, aRangeToDelete, aEditingHost);
    382      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    383                           "AutoBlockElementsJoiner::"
    384                           "ComputeRangesToDeleteContentInRanges() failed");
    385      return rv;
    386    }
    387    case Mode::DeleteNonCollapsedRange:
    388    case Mode::DeletePrecedingLinesAndContentInRange: {
    389      nsresult rv = ComputeRangeToDeleteNonCollapsedRange(
    390          aHTMLEditor, aDirectionAndAmount, aRangeToDelete,
    391          aSelectionWasCollapsed, aEditingHost);
    392      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    393                           "AutoBlockElementsJoiner::"
    394                           "ComputeRangesToDeleteNonCollapsedRanges() failed");
    395      return rv;
    396    }
    397    case Mode::NotInitialized:
    398      MOZ_ASSERT_UNREACHABLE(
    399          "Call ComputeRangesToDelete() after calling a preparation method");
    400      return NS_ERROR_NOT_INITIALIZED;
    401  }
    402  return NS_ERROR_NOT_INITIALIZED;
    403 }
    404 
    405 nsresult HTMLEditor::ComputeTargetRanges(
    406    nsIEditor::EDirection aDirectionAndAmount,
    407    AutoClonedSelectionRangeArray& aRangesToDelete) const {
    408  MOZ_ASSERT(IsEditActionDataAvailable());
    409 
    410  Element* editingHost = ComputeEditingHost();
    411  if (!editingHost) {
    412    aRangesToDelete.RemoveAllRanges();
    413    return NS_ERROR_EDITOR_NO_EDITABLE_RANGE;
    414  }
    415 
    416  // First check for table selection mode.  If so, hand off to table editor.
    417  SelectedTableCellScanner scanner(aRangesToDelete);
    418  if (scanner.IsInTableCellSelectionMode()) {
    419    // If it's in table cell selection mode, we'll delete all childen in
    420    // the all selected table cell elements,
    421    if (scanner.ElementsRef().Length() == aRangesToDelete.Ranges().Length()) {
    422      return NS_OK;
    423    }
    424    // but will ignore all ranges which does not select a table cell.
    425    size_t removedRanges = 0;
    426    for (size_t i = 1; i < scanner.ElementsRef().Length(); i++) {
    427      if (HTMLEditUtils::GetTableCellElementIfOnlyOneSelected(
    428              aRangesToDelete.Ranges()[i - removedRanges]) !=
    429          scanner.ElementsRef()[i]) {
    430        // XXX Need to manage anchor-focus range too!
    431        aRangesToDelete.Ranges().RemoveElementAt(i - removedRanges);
    432        removedRanges++;
    433      }
    434    }
    435    return NS_OK;
    436  }
    437 
    438  aRangesToDelete.EnsureOnlyEditableRanges(*editingHost);
    439  if (aRangesToDelete.Ranges().IsEmpty()) {
    440    NS_WARNING(
    441        "There is no range which we can delete entire of or around the caret");
    442    return NS_ERROR_EDITOR_NO_EDITABLE_RANGE;
    443  }
    444  // Delete each range if completely in a replaced element or a void element
    445  // because collapsing the range outside may cause the surrounding content
    446  // which is outside the selection range will be deleted.
    447  if (aRangesToDelete.AdjustRangesNotInReplacedNorVoidElements(
    448          AutoClonedRangeArray::RangeInReplacedOrVoidElement::Delete,
    449          *editingHost) &&
    450      !aRangesToDelete.Ranges().Length()) {
    451    return NS_ERROR_EDITOR_NO_EDITABLE_RANGE;
    452  }
    453  AutoDeleteRangesHandler deleteHandler;
    454  // Should we delete target ranges which cannot delete actually?
    455  nsresult rv = deleteHandler.ComputeRangesToDelete(
    456      *this, aDirectionAndAmount, aRangesToDelete, *editingHost);
    457  NS_WARNING_ASSERTION(
    458      NS_SUCCEEDED(rv),
    459      "AutoDeleteRangesHandler::ComputeRangesToDelete() failed");
    460  return rv;
    461 }
    462 
    463 Result<EditActionResult, nsresult> HTMLEditor::HandleDeleteSelection(
    464    nsIEditor::EDirection aDirectionAndAmount,
    465    nsIEditor::EStripWrappers aStripWrappers) {
    466  MOZ_ASSERT(IsEditActionDataAvailable());
    467  MOZ_ASSERT(aStripWrappers == nsIEditor::eStrip ||
    468             aStripWrappers == nsIEditor::eNoStrip);
    469 
    470  if (MOZ_UNLIKELY(!SelectionRef().RangeCount())) {
    471    return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE);
    472  }
    473 
    474  const RefPtr<Element> editingHost = ComputeEditingHost();
    475  if (MOZ_UNLIKELY(!editingHost)) {
    476    return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE);
    477  }
    478 
    479  // Remember that we did a selection deletion.  Used by
    480  // CreateStyleForInsertText()
    481  TopLevelEditSubActionDataRef().mDidDeleteSelection = true;
    482 
    483  if (MOZ_UNLIKELY(IsEmpty())) {
    484    return EditActionResult::CanceledResult();
    485  }
    486 
    487  // First check for table selection mode.  If so, hand off to table editor.
    488  if (HTMLEditUtils::IsInTableCellSelectionMode(SelectionRef())) {
    489    nsresult rv = DeleteTableCellContentsWithTransaction();
    490    if (NS_WARN_IF(Destroyed())) {
    491      return Err(NS_ERROR_EDITOR_DESTROYED);
    492    }
    493    if (NS_FAILED(rv)) {
    494      NS_WARNING("HTMLEditor::DeleteTableCellContentsWithTransaction() failed");
    495      return Err(rv);
    496    }
    497    return EditActionResult::HandledResult();
    498  }
    499 
    500  AutoClonedSelectionRangeArray rangesToDelete(SelectionRef());
    501  rangesToDelete.EnsureOnlyEditableRanges(*editingHost);
    502  // AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() need to use
    503  // NodeIsInLimiters() to extend the range for deletion.  But if focus event
    504  // doesn't receive yet, ancestor hasn't been set yet.  So we need to set
    505  // ancestor limiter to editing host, <body> or something else in such case.
    506  if (!rangesToDelete.GetAncestorLimiter()) {
    507    rangesToDelete.SetAncestorLimiter(FindSelectionRoot(*editingHost));
    508  }
    509  if (MOZ_UNLIKELY(rangesToDelete.Ranges().IsEmpty())) {
    510    NS_WARNING(
    511        "There is no range which we can delete entire the ranges or around the "
    512        "caret");
    513    return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE);
    514  }
    515  // Delete each range if completely in a replaced element or a void element
    516  // because collapsing the range outside may cause the surrounding content
    517  // which is outside the selection range will be deleted.
    518  if (rangesToDelete.AdjustRangesNotInReplacedNorVoidElements(
    519          AutoClonedRangeArray::RangeInReplacedOrVoidElement::Delete,
    520          *editingHost) &&
    521      rangesToDelete.Ranges().IsEmpty()) {
    522    // Collapse Selection to the first editable range to avoid the toplevel edit
    523    // subaction handler to be confused at non-selection ranges.
    524    if (GetTopLevelEditSubAction() != EditSubAction::eDeleteSelectedContent) {
    525      AutoClonedSelectionRangeArray editableSelectionRanges(SelectionRef());
    526      editableSelectionRanges.EnsureOnlyEditableRanges(*editingHost);
    527      if (!editableSelectionRanges.GetAncestorLimiter()) {
    528        editableSelectionRanges.SetAncestorLimiter(
    529            FindSelectionRoot(*editingHost));
    530      }
    531      editableSelectionRanges.AdjustRangesNotInReplacedNorVoidElements(
    532          AutoClonedRangeArray::RangeInReplacedOrVoidElement::Collapse,
    533          *editingHost);
    534      if (NS_WARN_IF(editableSelectionRanges.Ranges().IsEmpty())) {
    535        return Err(NS_ERROR_FAILURE);
    536      }
    537      nsresult rv = editableSelectionRanges.Collapse(
    538          editableSelectionRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>());
    539      if (NS_WARN_IF(Destroyed())) {
    540        return Err(NS_ERROR_EDITOR_DESTROYED);
    541      }
    542      if (NS_FAILED(rv)) {
    543        return Err(rv);
    544      }
    545    }
    546    return Err(NS_ERROR_EDITOR_NO_DELETABLE_RANGE);
    547  }
    548  AutoDeleteRangesHandler deleteHandler;
    549  Result<EditActionResult, nsresult> result = deleteHandler.Run(
    550      *this, aDirectionAndAmount, aStripWrappers, rangesToDelete, *editingHost);
    551  if (MOZ_UNLIKELY(result.isErr()) || result.inspect().Canceled()) {
    552    NS_WARNING_ASSERTION(result.isOk(),
    553                         "AutoDeleteRangesHandler::Run() failed");
    554    return result;
    555  }
    556  return EditActionResult::HandledResult();
    557 }
    558 
    559 Result<EditorDOMPoint, nsresult> HTMLEditor::DeleteLineBreakWithTransaction(
    560    const EditorLineBreak& aLineBreak,
    561    nsIEditor::EStripWrappers aDeleteEmptyInlines,
    562    const Element& aEditingHost) {
    563  MOZ_ASSERT(aLineBreak.IsInComposedDoc());
    564  MOZ_ASSERT_IF(aLineBreak.IsPreformattedLineBreak(),
    565                aLineBreak.CharAtOffsetIsLineBreak());
    566 
    567  if (aLineBreak.IsHTMLBRElement() ||
    568      aLineBreak.TextIsOnlyPreformattedLineBreak()) {
    569    const OwningNonNull<nsIContent> nodeToDelete = [&]() -> nsIContent& {
    570      if (aDeleteEmptyInlines == nsIEditor::eNoStrip) {
    571        return aLineBreak.ContentRef();
    572      }
    573      Element* const newEmptyInlineElement =
    574          HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
    575              aLineBreak.ContentRef(),
    576              BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost);
    577      return newEmptyInlineElement ? *newEmptyInlineElement
    578                                   : aLineBreak.ContentRef();
    579    }();
    580    const nsCOMPtr<nsINode> parentNode = nodeToDelete->GetParentNode();
    581    if (NS_WARN_IF(!parentNode)) {
    582      return Err(NS_ERROR_FAILURE);
    583    }
    584    const nsCOMPtr<nsIContent> nextSibling = nodeToDelete->GetNextSibling();
    585    nsresult rv = DeleteNodeWithTransaction(nodeToDelete);
    586    if (NS_FAILED(rv)) {
    587      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
    588      return Err(rv);
    589    }
    590    if (NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parentNode) ||
    591        NS_WARN_IF(!parentNode->IsInComposedDoc())) {
    592      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
    593    }
    594    return nextSibling ? EditorDOMPoint(nextSibling)
    595                       : EditorDOMPoint::AtEndOf(*parentNode);
    596  }
    597 
    598  const OwningNonNull<Text> textNode(aLineBreak.TextRef());
    599  Result<CaretPoint, nsresult> caretPointOrError =
    600      DeleteTextWithTransaction(textNode, aLineBreak.Offset(), 1u);
    601  if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
    602    NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
    603    return caretPointOrError.propagateErr();
    604  }
    605  if (NS_WARN_IF(!caretPointOrError.inspect().HasCaretPointSuggestion())) {
    606    return Err(NS_ERROR_FAILURE);
    607  }
    608  return caretPointOrError.unwrap().UnwrapCaretPoint();
    609 }
    610 
    611 Result<CaretPoint, nsresult> HTMLEditor::DeleteRangesWithTransaction(
    612    nsIEditor::EDirection aDirectionAndAmount,
    613    nsIEditor::EStripWrappers aStripWrappers,
    614    AutoClonedRangeArray& aRangesToDelete) {
    615  const RefPtr<Element> editingHost =
    616      ComputeEditingHost(LimitInBodyElement::No);
    617  if (NS_WARN_IF(!editingHost)) {
    618    return Err(NS_ERROR_UNEXPECTED);
    619  }
    620 
    621  aRangesToDelete.ExtendRangeToContainSurroundingInvisibleWhiteSpaces(
    622      aStripWrappers);
    623  if (MOZ_UNLIKELY(aRangesToDelete.IsCollapsed())) {
    624    return CaretPoint(EditorDOMPoint(aRangesToDelete.FocusRef()));
    625  }
    626 
    627  Result<CaretPoint, nsresult> result = EditorBase::DeleteRangesWithTransaction(
    628      aDirectionAndAmount, aStripWrappers, aRangesToDelete);
    629  if (MOZ_UNLIKELY(result.isErr())) {
    630    return result;
    631  }
    632 
    633  const bool isDeleteSelection =
    634      GetTopLevelEditSubAction() == EditSubAction::eDeleteSelectedContent;
    635  EditorDOMPoint pointToPutCaret = result.unwrap().UnwrapCaretPoint();
    636  MOZ_ASSERT_IF(pointToPutCaret.IsSet(), HTMLEditUtils::IsSimplyEditableNode(
    637                                             *pointToPutCaret.GetContainer()));
    638  {
    639    AutoTrackDOMPoint trackCaretPoint(RangeUpdaterRef(), &pointToPutCaret);
    640    for (const auto& range : aRangesToDelete.Ranges()) {
    641      // Refer the start boundary of the range because it should be end of the
    642      // preceding content, but the end boundary may be in an ancestor when an
    643      // ancestor element of end boundary has already been deleted.
    644      if (MOZ_UNLIKELY(!range->IsPositioned() ||
    645                       !range->GetStartContainer()->IsContent())) {
    646        continue;
    647      }
    648      EditorDOMPoint pointToInsertLineBreak(range->StartRef());
    649      // Don't remove empty inline elements in the plaintext-only mode because
    650      // nobody can restore the style again.
    651      if (aStripWrappers == nsIEditor::eStrip) {
    652        const OwningNonNull<nsIContent> maybeEmptyContent =
    653            *pointToInsertLineBreak.ContainerAs<nsIContent>();
    654        if (MOZ_UNLIKELY(
    655                !HTMLEditUtils::IsRemovableFromParentNode(maybeEmptyContent))) {
    656          continue;
    657        }
    658        // If the `Text` becomes invisible but has collapsible white-spaces, we
    659        // shouldn't delete it because the deletion deletes only before or after
    660        // the white-space to keep the white-space visible.
    661        if (!maybeEmptyContent->IsText() ||
    662            !maybeEmptyContent->AsText()->TextDataLength()) {
    663          Result<CaretPoint, nsresult> caretPointOrError =
    664              DeleteEmptyInclusiveAncestorInlineElements(maybeEmptyContent,
    665                                                         *editingHost);
    666          if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
    667            NS_WARNING(
    668                "HTMLEditor::DeleteEmptyInclusiveAncestorInlineElements() "
    669                "failed");
    670            return caretPointOrError.propagateErr();
    671          }
    672          if (NS_WARN_IF(!range->IsPositioned() ||
    673                         !range->GetStartContainer()->IsContent())) {
    674            continue;
    675          }
    676          MOZ_ASSERT_IF(
    677              caretPointOrError.inspect().HasCaretPointSuggestion(),
    678              HTMLEditUtils::IsSimplyEditableNode(
    679                  *caretPointOrError.inspect().CaretPointRef().GetContainer()));
    680          caretPointOrError.unwrap().MoveCaretPointTo(
    681              pointToInsertLineBreak, {SuggestCaret::OnlyIfHasSuggestion});
    682          if (NS_WARN_IF(
    683                  !pointToInsertLineBreak.IsSetAndValidInComposedDoc())) {
    684            continue;
    685          }
    686        }
    687      }
    688 
    689      if ((IsMailEditor() || IsPlaintextMailComposer()) &&
    690          MOZ_LIKELY(pointToInsertLineBreak.IsInContentNode())) {
    691        AutoTrackDOMPoint trackPointToInsertLineBreak(RangeUpdaterRef(),
    692                                                      &pointToInsertLineBreak);
    693        nsresult rv = DeleteMostAncestorMailCiteElementIfEmpty(
    694            MOZ_KnownLive(*pointToInsertLineBreak.ContainerAs<nsIContent>()));
    695        if (NS_FAILED(rv)) {
    696          NS_WARNING(
    697              "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed");
    698          return Err(rv);
    699        }
    700        trackPointToInsertLineBreak.FlushAndStopTracking();
    701        if (NS_WARN_IF(!pointToInsertLineBreak.IsSetAndValidInComposedDoc())) {
    702          continue;
    703        }
    704        MOZ_ASSERT(HTMLEditUtils::IsSimplyEditableNode(
    705            *pointToInsertLineBreak.GetContainer()));
    706      }
    707 
    708      if (isDeleteSelection) {
    709        {
    710          AutoTrackDOMPoint trackPointToInsertLineBreak(
    711              RangeUpdaterRef(), &pointToInsertLineBreak);
    712          nsresult rv =
    713              EnsureNoFollowingUnnecessaryLineBreak(pointToInsertLineBreak);
    714          if (NS_FAILED(rv)) {
    715            NS_WARNING(
    716                "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
    717            return Err(rv);
    718          }
    719          trackPointToInsertLineBreak.FlushAndStopTracking();
    720          if (NS_WARN_IF(!pointToInsertLineBreak
    721                              .IsInContentNodeAndValidInComposedDoc())) {
    722            return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
    723          }
    724        }
    725        Result<CreateLineBreakResult, nsresult> insertPaddingBRElementOrError =
    726            InsertPaddingBRElementIfNeeded(
    727                pointToInsertLineBreak,
    728                editingHost->IsContentEditablePlainTextOnly()
    729                    ? nsIEditor::eNoStrip
    730                    : nsIEditor::eStrip,
    731                *editingHost);
    732        if (MOZ_UNLIKELY(insertPaddingBRElementOrError.isErr())) {
    733          NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed");
    734          return insertPaddingBRElementOrError.propagateErr();
    735        }
    736        insertPaddingBRElementOrError.unwrap().IgnoreCaretPointSuggestion();
    737      }
    738    }
    739  }
    740  return CaretPoint(std::move(pointToPutCaret));
    741 }
    742 
    743 nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
    744    const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
    745    AutoClonedSelectionRangeArray& aRangesToDelete,
    746    const Element& aEditingHost) {
    747  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
    748  MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
    749 
    750  mOriginalDirectionAndAmount = aDirectionAndAmount;
    751  mOriginalStripWrappers = nsIEditor::eNoStrip;
    752 
    753  if (aHTMLEditor.mPaddingBRElementForEmptyEditor) {
    754    nsresult rv = aRangesToDelete.Collapse(
    755        EditorRawDOMPoint(aHTMLEditor.mPaddingBRElementForEmptyEditor));
    756    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    757                         "AutoClonedRangeArray::Collapse() failed");
    758    return rv;
    759  }
    760 
    761  SelectionWasCollapsed selectionWasCollapsed = aRangesToDelete.IsCollapsed()
    762                                                    ? SelectionWasCollapsed::Yes
    763                                                    : SelectionWasCollapsed::No;
    764  if (selectionWasCollapsed == SelectionWasCollapsed::Yes) {
    765    const auto startPoint =
    766        aRangesToDelete.GetFirstRangeStartPoint<EditorDOMPoint>();
    767    if (NS_WARN_IF(!startPoint.IsSet())) {
    768      return NS_ERROR_FAILURE;
    769    }
    770    if (startPoint.IsInContentNode()) {
    771      AutoEmptyBlockAncestorDeleter deleter;
    772      if (deleter.ScanEmptyBlockInclusiveAncestor(
    773              aHTMLEditor, *startPoint.ContainerAs<nsIContent>())) {
    774        nsresult rv = deleter.ComputeTargetRanges(
    775            aHTMLEditor, aDirectionAndAmount, aEditingHost, aRangesToDelete);
    776        NS_WARNING_ASSERTION(
    777            NS_SUCCEEDED(rv),
    778            "AutoEmptyBlockAncestorDeleter::ComputeTargetRanges() failed");
    779        return rv;
    780      }
    781    }
    782 
    783    // We shouldn't update caret bidi level right now, but we need to check
    784    // whether the deletion will be canceled or not.
    785    AutoCaretBidiLevelManager bidiLevelManager(aHTMLEditor, aDirectionAndAmount,
    786                                               startPoint);
    787    if (bidiLevelManager.Failed()) {
    788      NS_WARNING(
    789          "EditorBase::AutoCaretBidiLevelManager failed to initialize itself");
    790      return NS_ERROR_FAILURE;
    791    }
    792    if (bidiLevelManager.Canceled()) {
    793      return NS_SUCCESS_DOM_NO_OPERATION;
    794    }
    795 
    796    Result<nsIEditor::EDirection, nsresult> extendResult =
    797        aRangesToDelete.ExtendAnchorFocusRangeFor(aHTMLEditor,
    798                                                  aDirectionAndAmount);
    799    if (extendResult.isErr()) {
    800      NS_WARNING(
    801          "AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() failed");
    802      return extendResult.unwrapErr();
    803    }
    804 
    805    // For compatibility with other browsers, we should set target ranges
    806    // to start from and/or end after an atomic content rather than start
    807    // from preceding text node end nor end at following text node start.
    808    Result<bool, nsresult> shrunkenResult =
    809        aRangesToDelete.ShrinkRangesIfStartFromOrEndAfterAtomicContent(
    810            aHTMLEditor, aDirectionAndAmount,
    811            AutoClonedRangeArray::IfSelectingOnlyOneAtomicContent::Collapse);
    812    if (shrunkenResult.isErr()) {
    813      NS_WARNING(
    814          "AutoClonedRangeArray::"
    815          "ShrinkRangesIfStartFromOrEndAfterAtomicContent() "
    816          "failed");
    817      return shrunkenResult.unwrapErr();
    818    }
    819 
    820    if (!shrunkenResult.inspect() || !aRangesToDelete.IsCollapsed()) {
    821      aDirectionAndAmount = extendResult.unwrap();
    822    }
    823 
    824    if (aDirectionAndAmount == nsIEditor::eNone) {
    825      MOZ_ASSERT(aRangesToDelete.Ranges().Length() == 1);
    826      if (!CanFallbackToDeleteRangesWithTransaction(aRangesToDelete)) {
    827        // XXX In this case, do we need to modify the range again?
    828        return NS_SUCCESS_DOM_NO_OPERATION;
    829      }
    830      nsresult rv = FallbackToComputeRangesToDeleteRangesWithTransaction(
    831          aHTMLEditor, aRangesToDelete, aEditingHost);
    832      NS_WARNING_ASSERTION(
    833          NS_SUCCEEDED(rv),
    834          "AutoDeleteRangesHandler::"
    835          "FallbackToComputeRangesToDeleteRangesWithTransaction() failed");
    836      return rv;
    837    }
    838 
    839    if (aRangesToDelete.IsCollapsed()) {
    840      const auto caretPoint =
    841          aRangesToDelete.GetFirstRangeStartPoint<EditorDOMPoint>();
    842      if (MOZ_UNLIKELY(NS_WARN_IF(!caretPoint.IsInContentNode()))) {
    843        return NS_ERROR_FAILURE;
    844      }
    845      if (!EditorUtils::IsEditableContent(*caretPoint.ContainerAs<nsIContent>(),
    846                                          EditorType::HTML)) {
    847        return NS_SUCCESS_DOM_NO_OPERATION;
    848      }
    849      const WSRunScanner wsRunScannerAtCaret(
    850          {WSRunScanner::Option::OnlyEditableNodes}, caretPoint);
    851      const WSScanResult scanFromCaretPointResult =
    852          aDirectionAndAmount == nsIEditor::eNext
    853              ? wsRunScannerAtCaret
    854                    .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(caretPoint)
    855              : wsRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
    856                    caretPoint);
    857      if (scanFromCaretPointResult.Failed()) {
    858        NS_WARNING(
    859            "WSRunScanner::Scan(Next|Previous)VisibleNodeOrBlockBoundaryFrom() "
    860            "failed");
    861        return NS_ERROR_FAILURE;
    862      }
    863      MOZ_ASSERT(scanFromCaretPointResult.GetContent());
    864 
    865      if (scanFromCaretPointResult.ReachedBRElement()) {
    866        if (scanFromCaretPointResult.BRElementPtr() == &aEditingHost) {
    867          return NS_OK;
    868        }
    869        if (!scanFromCaretPointResult.IsContentEditable()) {
    870          return NS_SUCCESS_DOM_NO_OPERATION;
    871        }
    872        if (scanFromCaretPointResult.ReachedInvisibleBRElement()) {
    873          EditorDOMPoint newCaretPosition =
    874              aDirectionAndAmount == nsIEditor::eNext
    875                  ? scanFromCaretPointResult
    876                        .PointAfterReachedContent<EditorDOMPoint>()
    877                  : scanFromCaretPointResult
    878                        .PointAtReachedContent<EditorDOMPoint>();
    879          if (NS_WARN_IF(!newCaretPosition.IsSet())) {
    880            return NS_ERROR_FAILURE;
    881          }
    882          AutoHideSelectionChanges blockSelectionListeners(
    883              aHTMLEditor.SelectionRef());
    884          nsresult rv = aHTMLEditor.CollapseSelectionTo(newCaretPosition);
    885          if (MOZ_UNLIKELY(NS_FAILED(rv))) {
    886            NS_WARNING("EditorBase::CollapseSelectionTo() failed");
    887            return NS_ERROR_FAILURE;
    888          }
    889          if (NS_WARN_IF(!aHTMLEditor.SelectionRef().RangeCount())) {
    890            return NS_ERROR_UNEXPECTED;
    891          }
    892          aRangesToDelete.Initialize(aHTMLEditor.SelectionRef());
    893          AutoDeleteRangesHandler anotherHandler(this);
    894          rv = anotherHandler.ComputeRangesToDelete(
    895              aHTMLEditor, aDirectionAndAmount, aRangesToDelete, aEditingHost);
    896          NS_WARNING_ASSERTION(
    897              NS_SUCCEEDED(rv),
    898              "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() "
    899              "failed");
    900 
    901          rv = aHTMLEditor.CollapseSelectionTo(caretPoint);
    902          if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
    903            NS_WARNING(
    904                "EditorBase::CollapseSelectionTo() caused destroying the "
    905                "editor");
    906            return NS_ERROR_EDITOR_DESTROYED;
    907          }
    908          NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    909                               "EditorBase::CollapseSelectionTo() failed to "
    910                               "restore original selection, but ignored");
    911 
    912          MOZ_ASSERT(aRangesToDelete.Ranges().Length() == 1);
    913          // If the range is collapsed, there is no content which should
    914          // be removed together.  In this case, only the invisible `<br>`
    915          // element should be selected.
    916          if (aRangesToDelete.IsCollapsed()) {
    917            nsresult rv = aRangesToDelete.SelectNode(
    918                *scanFromCaretPointResult.BRElementPtr());
    919            NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    920                                 "AutoClonedRangeArray::SelectNode() failed");
    921            return rv;
    922          }
    923 
    924          // Otherwise, extend the range to contain the invisible `<br>`
    925          // element.
    926          if (scanFromCaretPointResult
    927                  .PointAtReachedContent<EditorRawDOMPoint>()
    928                  .IsBefore(
    929                      aRangesToDelete
    930                          .GetFirstRangeStartPoint<EditorRawDOMPoint>())) {
    931            nsresult rv = aRangesToDelete.FirstRangeRef()->SetStartAndEnd(
    932                EditorRawDOMPoint(scanFromCaretPointResult.BRElementPtr())
    933                    .ToRawRangeBoundary(),
    934                aRangesToDelete.FirstRangeRef()->EndRef());
    935            NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    936                                 "nsRange::SetStartAndEnd() failed");
    937            return rv;
    938          }
    939          if (aRangesToDelete.GetFirstRangeEndPoint<EditorRawDOMPoint>()
    940                  .IsBefore(
    941                      scanFromCaretPointResult
    942                          .PointAfterReachedContent<EditorRawDOMPoint>())) {
    943            nsresult rv = aRangesToDelete.FirstRangeRef()->SetStartAndEnd(
    944                aRangesToDelete.FirstRangeRef()->StartRef(),
    945                scanFromCaretPointResult
    946                    .PointAfterReachedContent<EditorRawDOMPoint>()
    947                    .ToRawRangeBoundary());
    948            NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    949                                 "nsRange::SetStartAndEnd() failed");
    950            return rv;
    951          }
    952          NS_WARNING("Was the invisible `<br>` element selected?");
    953          return NS_OK;
    954        }
    955      }
    956 
    957      nsresult rv = ComputeRangesToDeleteAroundCollapsedRanges(
    958          aHTMLEditor, aDirectionAndAmount, aRangesToDelete,
    959          wsRunScannerAtCaret, scanFromCaretPointResult, aEditingHost);
    960      NS_WARNING_ASSERTION(
    961          NS_SUCCEEDED(rv),
    962          "AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges("
    963          ") failed");
    964      return rv;
    965    }
    966  }
    967 
    968  nsresult rv = ComputeRangesToDeleteNonCollapsedRanges(
    969      aHTMLEditor, aDirectionAndAmount, aRangesToDelete, selectionWasCollapsed,
    970      aEditingHost);
    971  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    972                       "AutoDeleteRangesHandler::"
    973                       "ComputeRangesToDeleteNonCollapsedRanges() failed");
    974  return rv;
    975 }
    976 
    977 Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run(
    978    HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
    979    nsIEditor::EStripWrappers aStripWrappers,
    980    AutoClonedSelectionRangeArray& aRangesToDelete,
    981    const Element& aEditingHost) {
    982  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
    983  MOZ_ASSERT(aStripWrappers == nsIEditor::eStrip ||
    984             aStripWrappers == nsIEditor::eNoStrip);
    985  MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
    986 
    987  mOriginalDirectionAndAmount = aDirectionAndAmount;
    988  mOriginalStripWrappers = aStripWrappers;
    989 
    990  if (MOZ_UNLIKELY(aHTMLEditor.IsEmpty())) {
    991    return EditActionResult::CanceledResult();
    992  }
    993 
    994  // selectionWasCollapsed is used later to determine whether we should join
    995  // blocks in HandleDeleteNonCollapsedRanges(). We don't really care about
    996  // collapsed because it will be modified by
    997  // AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() later.
    998  // AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner should
    999  // happen if the original selection is collapsed and the cursor is at the end
   1000  // of a block element, in which case
   1001  // AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() would always
   1002  // make the selection not collapsed.
   1003  SelectionWasCollapsed selectionWasCollapsed = aRangesToDelete.IsCollapsed()
   1004                                                    ? SelectionWasCollapsed::Yes
   1005                                                    : SelectionWasCollapsed::No;
   1006 
   1007  if (selectionWasCollapsed == SelectionWasCollapsed::Yes) {
   1008    const auto startPoint =
   1009        aRangesToDelete.GetFirstRangeStartPoint<EditorDOMPoint>();
   1010    if (NS_WARN_IF(!startPoint.IsSet())) {
   1011      return Err(NS_ERROR_FAILURE);
   1012    }
   1013 
   1014    // If we are inside an empty block, delete it.
   1015    if (startPoint.IsInContentNode()) {
   1016 #ifdef DEBUG
   1017      nsMutationGuard debugMutation;
   1018 #endif  // #ifdef DEBUG
   1019      AutoEmptyBlockAncestorDeleter deleter;
   1020      if (deleter.ScanEmptyBlockInclusiveAncestor(
   1021              aHTMLEditor, *startPoint.ContainerAs<nsIContent>())) {
   1022        Result<DeleteRangeResult, nsresult> deleteResultOrError =
   1023            deleter.Run(aHTMLEditor, aDirectionAndAmount, aEditingHost);
   1024        if (MOZ_UNLIKELY(deleteResultOrError.isErr())) {
   1025          NS_WARNING("AutoEmptyBlockAncestorDeleter::Run() failed");
   1026          return deleteResultOrError.propagateErr();
   1027        }
   1028        DeleteRangeResult deleteResult = deleteResultOrError.unwrap();
   1029        if (deleteResult.Handled()) {
   1030          nsresult rv = deleteResult.SuggestCaretPointTo(
   1031              aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion});
   1032          if (NS_FAILED(rv)) {
   1033            NS_WARNING("CaretPoint::SuggestCaretPoint() failed");
   1034            return Err(rv);
   1035          }
   1036          return EditActionResult::HandledResult();
   1037        }
   1038      }
   1039      MOZ_ASSERT(!debugMutation.Mutated(0),
   1040                 "AutoEmptyBlockAncestorDeleter shouldn't modify the DOM tree "
   1041                 "if it returns not handled nor error");
   1042    }
   1043 
   1044    // Test for distance between caret and text that will be deleted.
   1045    AutoCaretBidiLevelManager bidiLevelManager(aHTMLEditor, aDirectionAndAmount,
   1046                                               startPoint);
   1047    if (MOZ_UNLIKELY(bidiLevelManager.Failed())) {
   1048      NS_WARNING(
   1049          "EditorBase::AutoCaretBidiLevelManager failed to initialize itself");
   1050      return Err(NS_ERROR_FAILURE);
   1051    }
   1052    bidiLevelManager.MaybeUpdateCaretBidiLevel(aHTMLEditor);
   1053    if (bidiLevelManager.Canceled()) {
   1054      return EditActionResult::CanceledResult();
   1055    }
   1056 
   1057    // Calling `ExtendAnchorFocusRangeFor()` and
   1058    // `ShrinkRangesIfStartFromOrEndAfterAtomicContent()` may move caret to
   1059    // the container of deleting atomic content.  However, it may be different
   1060    // from the original caret's container.  The original caret container may
   1061    // be important to put caret after deletion so that let's cache the
   1062    // original position.
   1063    Maybe<EditorDOMPoint> caretPoint;
   1064    if (aRangesToDelete.IsCollapsed() && !aRangesToDelete.Ranges().IsEmpty()) {
   1065      caretPoint =
   1066          Some(aRangesToDelete.GetFirstRangeStartPoint<EditorDOMPoint>());
   1067      if (NS_WARN_IF(!caretPoint.ref().IsInContentNode())) {
   1068        return Err(NS_ERROR_FAILURE);
   1069      }
   1070    }
   1071 
   1072    Result<nsIEditor::EDirection, nsresult> extendResult =
   1073        aRangesToDelete.ExtendAnchorFocusRangeFor(aHTMLEditor,
   1074                                                  aDirectionAndAmount);
   1075    if (MOZ_UNLIKELY(extendResult.isErr())) {
   1076      NS_WARNING(
   1077          "AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() failed");
   1078      return extendResult.propagateErr();
   1079    }
   1080    if (caretPoint.isSome() &&
   1081        MOZ_UNLIKELY(!caretPoint.ref().IsSetAndValid())) {
   1082      NS_WARNING("The caret position became invalid");
   1083      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1084    }
   1085 
   1086    // If there is only one range and it selects an atomic content, we should
   1087    // delete it with collapsed range path for making consistent behavior
   1088    // between both cases, the content is selected case and caret is at it or
   1089    // after it case.
   1090    Result<bool, nsresult> shrunkenResult =
   1091        aRangesToDelete.ShrinkRangesIfStartFromOrEndAfterAtomicContent(
   1092            aHTMLEditor, aDirectionAndAmount,
   1093            AutoClonedRangeArray::IfSelectingOnlyOneAtomicContent::Collapse);
   1094    if (MOZ_UNLIKELY(shrunkenResult.isErr())) {
   1095      NS_WARNING(
   1096          "AutoClonedRangeArray::"
   1097          "ShrinkRangesIfStartFromOrEndAfterAtomicContent() "
   1098          "failed");
   1099      return shrunkenResult.propagateErr();
   1100    }
   1101 
   1102    if (!shrunkenResult.inspect() || !aRangesToDelete.IsCollapsed()) {
   1103      aDirectionAndAmount = extendResult.unwrap();
   1104    }
   1105 
   1106    if (aDirectionAndAmount == nsIEditor::eNone) {
   1107      MOZ_ASSERT(aRangesToDelete.Ranges().Length() == 1);
   1108      if (!CanFallbackToDeleteRangesWithTransaction(aRangesToDelete)) {
   1109        return EditActionResult::IgnoredResult();
   1110      }
   1111      Result<CaretPoint, nsresult> caretPointOrError =
   1112          FallbackToDeleteRangesWithTransaction(aHTMLEditor, aRangesToDelete,
   1113                                                aEditingHost);
   1114      if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   1115        NS_WARNING(
   1116            "AutoDeleteRangesHandler::FallbackToDeleteRangesWithTransaction() "
   1117            "failed");
   1118      }
   1119      nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo(
   1120          aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
   1121                        SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   1122                        SuggestCaret::AndIgnoreTrivialError});
   1123      if (NS_FAILED(rv)) {
   1124        NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
   1125        return Err(rv);
   1126      }
   1127      NS_WARNING_ASSERTION(
   1128          rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   1129          "CaretPoint::SuggestCaretPointTo() failed, but ignored");
   1130      // Don't return "ignored" to avoid to fall it back to delete ranges
   1131      // recursively.
   1132      return EditActionResult::HandledResult();
   1133    }
   1134 
   1135    if (aRangesToDelete.IsCollapsed()) {
   1136      // Use the original caret position for handling the deletion around
   1137      // collapsed range because the container may be different from the
   1138      // new collapsed position's container.
   1139      if (!EditorUtils::IsEditableContent(
   1140              *caretPoint.ref().ContainerAs<nsIContent>(), EditorType::HTML)) {
   1141        return EditActionResult::CanceledResult();
   1142      }
   1143      const WSRunScanner wsRunScannerAtCaret(
   1144          {WSRunScanner::Option::OnlyEditableNodes}, caretPoint.ref());
   1145      const WSScanResult scanFromCaretPointResult =
   1146          aDirectionAndAmount == nsIEditor::eNext
   1147              ? wsRunScannerAtCaret
   1148                    .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
   1149                        caretPoint.ref())
   1150              : wsRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
   1151                    caretPoint.ref());
   1152      if (MOZ_UNLIKELY(scanFromCaretPointResult.Failed())) {
   1153        NS_WARNING(
   1154            "WSRunScanner::Scan(Next|Previous)VisibleNodeOrBlockBoundaryFrom() "
   1155            "failed");
   1156        return Err(NS_ERROR_FAILURE);
   1157      }
   1158      MOZ_ASSERT(scanFromCaretPointResult.GetContent());
   1159 
   1160      // Short circuit for invisible breaks.  delete them and recurse.
   1161      if (scanFromCaretPointResult.ReachedBRElement()) {
   1162        if (scanFromCaretPointResult.BRElementPtr() == &aEditingHost) {
   1163          return EditActionResult::HandledResult();
   1164        }
   1165        if (!scanFromCaretPointResult.IsContentEditable()) {
   1166          return EditActionResult::CanceledResult();
   1167        }
   1168        if (scanFromCaretPointResult.ReachedInvisibleBRElement()) {
   1169          // TODO: We should extend the range to delete again before/after
   1170          //       the caret point and use `HandleDeleteNonCollapsedRanges()`
   1171          //       instead after we would create delete range computation
   1172          //       method at switching to the new white-space normalizer.
   1173          Result<CaretPoint, nsresult> caretPointOrError =
   1174              WhiteSpaceVisibilityKeeper::
   1175                  DeleteContentNodeAndJoinTextNodesAroundIt(
   1176                      aHTMLEditor,
   1177                      MOZ_KnownLive(*scanFromCaretPointResult.BRElementPtr()),
   1178                      caretPoint.ref(), aEditingHost);
   1179          if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   1180            NS_WARNING(
   1181                "WhiteSpaceVisibilityKeeper::"
   1182                "DeleteContentNodeAndJoinTextNodesAroundIt() failed");
   1183            return caretPointOrError.propagateErr();
   1184          }
   1185          if (caretPointOrError.inspect().HasCaretPointSuggestion()) {
   1186            caretPoint = Some(caretPointOrError.unwrap().UnwrapCaretPoint());
   1187          }
   1188          if (NS_WARN_IF(!caretPoint->IsSetAndValid())) {
   1189            return Err(NS_ERROR_FAILURE);
   1190          }
   1191          AutoClonedSelectionRangeArray rangesToDelete(
   1192              caretPoint.ref(), aRangesToDelete.LimitersAndCaretDataRef());
   1193          if (NS_WARN_IF(rangesToDelete.Ranges().IsEmpty())) {
   1194            return Err(NS_ERROR_FAILURE);
   1195          }
   1196          if (aHTMLEditor.MaybeNodeRemovalsObservedByDevTools()) {
   1197            // Let's check whether there is new invisible `<br>` element
   1198            // for avoiding infinite recursive calls.
   1199            const WSRunScanner wsRunScannerAtCaret(
   1200                {WSRunScanner::Option::OnlyEditableNodes}, caretPoint.ref());
   1201            const WSScanResult scanFromCaretPointResult =
   1202                aDirectionAndAmount == nsIEditor::eNext
   1203                    ? wsRunScannerAtCaret
   1204                          .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
   1205                              caretPoint.ref())
   1206                    : wsRunScannerAtCaret
   1207                          .ScanPreviousVisibleNodeOrBlockBoundaryFrom(
   1208                              caretPoint.ref());
   1209            if (MOZ_UNLIKELY(scanFromCaretPointResult.Failed())) {
   1210              NS_WARNING(
   1211                  "WSRunScanner::Scan(Next|Previous)"
   1212                  "VisibleNodeOrBlockBoundaryFrom() failed");
   1213              return Err(NS_ERROR_FAILURE);
   1214            }
   1215            if (NS_WARN_IF(
   1216                    scanFromCaretPointResult.ReachedInvisibleBRElement())) {
   1217              return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1218            }
   1219          }
   1220          AutoDeleteRangesHandler anotherHandler(this);
   1221          Result<EditActionResult, nsresult> result =
   1222              anotherHandler.Run(aHTMLEditor, aDirectionAndAmount,
   1223                                 aStripWrappers, rangesToDelete, aEditingHost);
   1224          NS_WARNING_ASSERTION(
   1225              result.isOk(), "Recursive AutoDeleteRangesHandler::Run() failed");
   1226          return result;
   1227        }
   1228      }
   1229 
   1230      Result<EditActionResult, nsresult> result =
   1231          HandleDeleteAroundCollapsedRanges(
   1232              aHTMLEditor, aDirectionAndAmount, aStripWrappers, aRangesToDelete,
   1233              wsRunScannerAtCaret, scanFromCaretPointResult, aEditingHost);
   1234      NS_WARNING_ASSERTION(result.isOk(),
   1235                           "AutoDeleteRangesHandler::"
   1236                           "HandleDeleteAroundCollapsedRanges() failed");
   1237      return result;
   1238    }
   1239  }
   1240 
   1241  Result<EditActionResult, nsresult> result = HandleDeleteNonCollapsedRanges(
   1242      aHTMLEditor, aDirectionAndAmount, aStripWrappers, aRangesToDelete,
   1243      selectionWasCollapsed, aEditingHost);
   1244  NS_WARNING_ASSERTION(
   1245      result.isOk(),
   1246      "AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges() failed");
   1247  return result;
   1248 }
   1249 
   1250 nsresult
   1251 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(
   1252    const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1253    AutoClonedSelectionRangeArray& aRangesToDelete,
   1254    const WSRunScanner& aWSRunScannerAtCaret,
   1255    const WSScanResult& aScanFromCaretPointResult,
   1256    const Element& aEditingHost) const {
   1257  if (aScanFromCaretPointResult.InCollapsibleWhiteSpaces() ||
   1258      aScanFromCaretPointResult.InNonCollapsibleCharacters() ||
   1259      aScanFromCaretPointResult.ReachedPreformattedLineBreak()) {
   1260    // This means that if aDirectionAndAmount == nsIEditor::eNext, collapse
   1261    // selection at the found character.  Otherwise, collapse selection after
   1262    // the found character.
   1263    nsresult rv = aRangesToDelete.Collapse(
   1264        aScanFromCaretPointResult.Point_Deprecated<EditorRawDOMPoint>());
   1265    if (MOZ_UNLIKELY(NS_FAILED(rv))) {
   1266      NS_WARNING("AutoClonedRangeArray::Collapse() failed");
   1267      return NS_ERROR_FAILURE;
   1268    }
   1269    rv = ComputeRangesToDeleteTextAroundCollapsedRanges(aDirectionAndAmount,
   1270                                                        aRangesToDelete);
   1271    NS_WARNING_ASSERTION(
   1272        NS_SUCCEEDED(rv),
   1273        "AutoDeleteRangesHandler::"
   1274        "ComputeRangesToDeleteTextAroundCollapsedRanges() failed");
   1275    return rv;
   1276  }
   1277 
   1278  if (aScanFromCaretPointResult.ReachedSpecialContent() ||
   1279      aScanFromCaretPointResult.ReachedBRElement() ||
   1280      aScanFromCaretPointResult.ReachedHRElement() ||
   1281      aScanFromCaretPointResult.ReachedNonEditableOtherBlockElement()) {
   1282    if (aScanFromCaretPointResult.GetContent() == &aEditingHost) {
   1283      return NS_OK;
   1284    }
   1285    nsIContent* atomicContent = GetAtomicContentToDelete(
   1286        aDirectionAndAmount, aWSRunScannerAtCaret, aScanFromCaretPointResult);
   1287    if (!HTMLEditUtils::IsRemovableNode(*atomicContent)) {
   1288      NS_WARNING(
   1289          "AutoDeleteRangesHandler::GetAtomicContentToDelete() cannot find "
   1290          "removable atomic content");
   1291      return NS_ERROR_FAILURE;
   1292    }
   1293    nsresult rv =
   1294        ComputeRangesToDeleteAtomicContent(*atomicContent, aRangesToDelete);
   1295    NS_WARNING_ASSERTION(
   1296        NS_SUCCEEDED(rv),
   1297        "AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent() failed");
   1298    return rv;
   1299  }
   1300 
   1301  if (aScanFromCaretPointResult.ReachedOtherBlockElement()) {
   1302    if (NS_WARN_IF(!aScanFromCaretPointResult.ContentIsElement())) {
   1303      return NS_ERROR_FAILURE;
   1304    }
   1305    MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
   1306    bool handled = false;
   1307    for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) {
   1308      MOZ_ASSERT(range->IsPositioned());
   1309      AutoBlockElementsJoiner joiner(*this);
   1310      if (!joiner.PrepareToDeleteAtOtherBlockBoundary(
   1311              aHTMLEditor, aDirectionAndAmount,
   1312              *aScanFromCaretPointResult.ElementPtr(),
   1313              aWSRunScannerAtCaret.ScanStartRef(), aWSRunScannerAtCaret)) {
   1314        continue;
   1315      }
   1316      handled = true;
   1317      nsresult rv = joiner.ComputeRangeToDelete(
   1318          aHTMLEditor, aDirectionAndAmount, aWSRunScannerAtCaret.ScanStartRef(),
   1319          range, aEditingHost);
   1320      if (NS_FAILED(rv)) {
   1321        NS_WARNING(
   1322            "AutoBlockElementsJoiner::ComputeRangeToDelete() failed (other "
   1323            "block boundary)");
   1324        return rv;
   1325      }
   1326    }
   1327    return handled ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
   1328  }
   1329 
   1330  if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary() ||
   1331      aScanFromCaretPointResult.ReachedInlineEditingHostBoundary()) {
   1332    MOZ_ASSERT(aScanFromCaretPointResult.ContentIsElement());
   1333    MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
   1334    bool handled = false;
   1335    for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) {
   1336      AutoBlockElementsJoiner joiner(*this);
   1337      if (!joiner.PrepareToDeleteAtCurrentBlockBoundary(
   1338              aHTMLEditor, aDirectionAndAmount,
   1339              *aScanFromCaretPointResult.ElementPtr(),
   1340              aWSRunScannerAtCaret.ScanStartRef(), aEditingHost)) {
   1341        continue;
   1342      }
   1343      handled = true;
   1344      nsresult rv = joiner.ComputeRangeToDelete(
   1345          aHTMLEditor, aDirectionAndAmount, aWSRunScannerAtCaret.ScanStartRef(),
   1346          range, aEditingHost);
   1347      if (NS_FAILED(rv)) {
   1348        NS_WARNING(
   1349            "AutoBlockElementsJoiner::ComputeRangeToDelete() failed (current "
   1350            "block boundary)");
   1351        return rv;
   1352      }
   1353    }
   1354    return handled ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
   1355  }
   1356 
   1357  return NS_OK;
   1358 }
   1359 
   1360 Result<EditActionResult, nsresult>
   1361 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges(
   1362    HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1363    nsIEditor::EStripWrappers aStripWrappers,
   1364    AutoClonedSelectionRangeArray& aRangesToDelete,
   1365    const WSRunScanner& aWSRunScannerAtCaret,
   1366    const WSScanResult& aScanFromCaretPointResult,
   1367    const Element& aEditingHost) {
   1368  MOZ_ASSERT(aHTMLEditor.IsTopLevelEditSubActionDataAvailable());
   1369  MOZ_ASSERT(aRangesToDelete.IsCollapsed());
   1370  MOZ_ASSERT(aDirectionAndAmount != nsIEditor::eNone);
   1371  MOZ_ASSERT(aWSRunScannerAtCaret.ScanStartRef().IsInContentNode());
   1372  MOZ_ASSERT(EditorUtils::IsEditableContent(
   1373      *aWSRunScannerAtCaret.ScanStartRef().ContainerAs<nsIContent>(),
   1374      EditorType::HTML));
   1375 
   1376  if (aScanFromCaretPointResult.InCollapsibleWhiteSpaces() ||
   1377      aScanFromCaretPointResult.InNonCollapsibleCharacters() ||
   1378      aScanFromCaretPointResult.ReachedPreformattedLineBreak()) {
   1379    // This means that if aDirectionAndAmount == nsIEditor::eNext, collapse
   1380    // selection at the found character.  Otherwise, collapse selection after
   1381    // the found character.
   1382    nsresult rv = aRangesToDelete.Collapse(
   1383        aScanFromCaretPointResult.Point_Deprecated<EditorRawDOMPoint>());
   1384    if (NS_FAILED(rv)) {
   1385      NS_WARNING("AutoClonedRangeArray::Collapse() failed");
   1386      return Err(NS_ERROR_FAILURE);
   1387    }
   1388    Result<CaretPoint, nsresult> caretPointOrError =
   1389        HandleDeleteTextAroundCollapsedRanges(aHTMLEditor, aDirectionAndAmount,
   1390                                              aRangesToDelete, aEditingHost);
   1391    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   1392      NS_WARNING(
   1393          "AutoDeleteRangesHandler::HandleDeleteTextAroundCollapsedRanges() "
   1394          "failed");
   1395      return caretPointOrError.propagateErr();
   1396    }
   1397    rv = caretPointOrError.unwrap().SuggestCaretPointTo(
   1398        aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
   1399                      SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   1400                      SuggestCaret::AndIgnoreTrivialError});
   1401    if (NS_FAILED(rv)) {
   1402      NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
   1403      return Err(rv);
   1404    }
   1405    NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   1406                         "CaretPoint::SuggestCaretPoint() failed, but ignored");
   1407    return EditActionResult::HandledResult();
   1408  }
   1409 
   1410  if (aScanFromCaretPointResult.ReachedSpecialContent() ||
   1411      aScanFromCaretPointResult.ReachedBRElement() ||
   1412      aScanFromCaretPointResult.ReachedHRElement() ||
   1413      aScanFromCaretPointResult.ReachedNonEditableOtherBlockElement()) {
   1414    if (aScanFromCaretPointResult.GetContent() == &aEditingHost) {
   1415      return EditActionResult::HandledResult();
   1416    }
   1417    nsCOMPtr<nsIContent> atomicContent = GetAtomicContentToDelete(
   1418        aDirectionAndAmount, aWSRunScannerAtCaret, aScanFromCaretPointResult);
   1419    if (MOZ_UNLIKELY(!HTMLEditUtils::IsRemovableNode(*atomicContent))) {
   1420      NS_WARNING(
   1421          "AutoDeleteRangesHandler::GetAtomicContentToDelete() cannot find "
   1422          "removable atomic content");
   1423      return Err(NS_ERROR_FAILURE);
   1424    }
   1425    Result<CaretPoint, nsresult> caretPointOrError = HandleDeleteAtomicContent(
   1426        aHTMLEditor, *atomicContent, aWSRunScannerAtCaret.ScanStartRef(),
   1427        aWSRunScannerAtCaret, aEditingHost);
   1428    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   1429      NS_WARNING("AutoDeleteRangesHandler::HandleDeleteAtomicContent() failed");
   1430      return caretPointOrError.propagateErr();
   1431    }
   1432    nsresult rv = caretPointOrError.unwrap().SuggestCaretPointTo(
   1433        aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion});
   1434    if (NS_FAILED(rv)) {
   1435      NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
   1436      return Err(rv);
   1437    }
   1438    NS_WARNING_ASSERTION(
   1439        rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   1440        "CaretPoint::SuggestCaretPointTo() failed, but ignored");
   1441    return EditActionResult::HandledResult();
   1442  }
   1443 
   1444  if (aScanFromCaretPointResult.ReachedOtherBlockElement()) {
   1445    if (NS_WARN_IF(!aScanFromCaretPointResult.ContentIsElement())) {
   1446      return Err(NS_ERROR_FAILURE);
   1447    }
   1448    MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
   1449    bool allRangesNotHandled = true;
   1450    auto ret = EditActionResult::IgnoredResult();
   1451    for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) {
   1452      AutoBlockElementsJoiner joiner(*this);
   1453      if (!joiner.PrepareToDeleteAtOtherBlockBoundary(
   1454              aHTMLEditor, aDirectionAndAmount,
   1455              *aScanFromCaretPointResult.ElementPtr(),
   1456              aWSRunScannerAtCaret.ScanStartRef(), aWSRunScannerAtCaret)) {
   1457        continue;
   1458      }
   1459      allRangesNotHandled = false;
   1460      Result<EditActionResult, nsresult> result =
   1461          joiner.Run(aHTMLEditor, aDirectionAndAmount, aStripWrappers,
   1462                     aWSRunScannerAtCaret.ScanStartRef(), MOZ_KnownLive(range),
   1463                     aEditingHost);
   1464      if (MOZ_UNLIKELY(result.isErr())) {
   1465        NS_WARNING(
   1466            "AutoBlockElementsJoiner::Run() failed (other block boundary)");
   1467        return result;
   1468      }
   1469      ret |= result.inspect();
   1470    }
   1471    return allRangesNotHandled ? EditActionResult::CanceledResult()
   1472                               : std::move(ret);
   1473  }
   1474 
   1475  if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary() ||
   1476      aScanFromCaretPointResult.ReachedInlineEditingHostBoundary()) {
   1477    MOZ_ASSERT(aScanFromCaretPointResult.ContentIsElement());
   1478    MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
   1479    bool allRangesNotHandled = true;
   1480    auto ret = EditActionResult::IgnoredResult();
   1481    for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) {
   1482      AutoBlockElementsJoiner joiner(*this);
   1483      if (!joiner.PrepareToDeleteAtCurrentBlockBoundary(
   1484              aHTMLEditor, aDirectionAndAmount,
   1485              *aScanFromCaretPointResult.ElementPtr(),
   1486              aWSRunScannerAtCaret.ScanStartRef(), aEditingHost)) {
   1487        continue;
   1488      }
   1489      allRangesNotHandled = false;
   1490      Result<EditActionResult, nsresult> result =
   1491          joiner.Run(aHTMLEditor, aDirectionAndAmount, aStripWrappers,
   1492                     aWSRunScannerAtCaret.ScanStartRef(), MOZ_KnownLive(range),
   1493                     aEditingHost);
   1494      if (MOZ_UNLIKELY(result.isErr())) {
   1495        NS_WARNING(
   1496            "AutoBlockElementsJoiner::Run() failed (current block boundary)");
   1497        return result;
   1498      }
   1499      ret |= result.inspect();
   1500    }
   1501    return allRangesNotHandled ? EditActionResult::CanceledResult()
   1502                               : std::move(ret);
   1503  }
   1504 
   1505  MOZ_ASSERT_UNREACHABLE("New type of reached content hasn't been handled yet");
   1506  return EditActionResult::IgnoredResult();
   1507 }
   1508 
   1509 nsresult HTMLEditor::AutoDeleteRangesHandler::
   1510    ComputeRangesToDeleteTextAroundCollapsedRanges(
   1511        nsIEditor::EDirection aDirectionAndAmount,
   1512        AutoClonedSelectionRangeArray& aRangesToDelete) const {
   1513  MOZ_ASSERT(aDirectionAndAmount == nsIEditor::eNext ||
   1514             aDirectionAndAmount == nsIEditor::ePrevious);
   1515 
   1516  const auto caretPosition =
   1517      aRangesToDelete.GetFirstRangeStartPoint<EditorDOMPoint>();
   1518  MOZ_ASSERT(caretPosition.IsSetAndValid());
   1519  if (MOZ_UNLIKELY(NS_WARN_IF(!caretPosition.IsInContentNode()))) {
   1520    return NS_ERROR_FAILURE;
   1521  }
   1522 
   1523  EditorDOMRangeInTexts rangeToDelete;
   1524  if (aDirectionAndAmount == nsIEditor::eNext) {
   1525    Result<EditorDOMRangeInTexts, nsresult> result =
   1526        WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom(
   1527            {WSRunScanner::Option::OnlyEditableNodes}, caretPosition);
   1528    if (result.isErr()) {
   1529      NS_WARNING(
   1530          "WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom() failed");
   1531      return result.unwrapErr();
   1532    }
   1533    rangeToDelete = result.unwrap();
   1534    if (!rangeToDelete.IsPositioned()) {
   1535      return NS_OK;  // no range to delete, but consume it.
   1536    }
   1537  } else {
   1538    Result<EditorDOMRangeInTexts, nsresult> result =
   1539        WSRunScanner::GetRangeInTextNodesToBackspaceFrom(
   1540            {WSRunScanner::Option::OnlyEditableNodes}, caretPosition);
   1541    if (result.isErr()) {
   1542      NS_WARNING("WSRunScanner::GetRangeInTextNodesToBackspaceFrom() failed");
   1543      return result.unwrapErr();
   1544    }
   1545    rangeToDelete = result.unwrap();
   1546    if (!rangeToDelete.IsPositioned()) {
   1547      return NS_OK;  // no range to delete, but consume it.
   1548    }
   1549  }
   1550 
   1551  // FIXME: If we'll delete unnecessary following <br>, we need to include it
   1552  // into aRangesToDelete.
   1553 
   1554  nsresult rv = aRangesToDelete.SetStartAndEnd(rangeToDelete.StartRef(),
   1555                                               rangeToDelete.EndRef());
   1556  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   1557                       "AutoArrayRanges::SetStartAndEnd() failed");
   1558  return rv;
   1559 }
   1560 
   1561 Result<CaretPoint, nsresult>
   1562 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteTextAroundCollapsedRanges(
   1563    HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1564    AutoClonedSelectionRangeArray& aRangesToDelete,
   1565    const Element& aEditingHost) {
   1566  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   1567  MOZ_ASSERT(aDirectionAndAmount == nsIEditor::eNext ||
   1568             aDirectionAndAmount == nsIEditor::ePrevious);
   1569 
   1570  nsresult rv = ComputeRangesToDeleteTextAroundCollapsedRanges(
   1571      aDirectionAndAmount, aRangesToDelete);
   1572  if (NS_FAILED(rv)) {
   1573    return Err(NS_ERROR_FAILURE);
   1574  }
   1575  if (MOZ_UNLIKELY(aRangesToDelete.IsCollapsed())) {
   1576    return CaretPoint(EditorDOMPoint());  // no range to delete
   1577  }
   1578 
   1579  // FYI: rangeToDelete does not contain newly empty inline ancestors which
   1580  //      are removed by DeleteTextAndNormalizeSurroundingWhiteSpaces().
   1581  //      So, if `getTargetRanges()` needs to include parent empty elements,
   1582  //      we need to extend the range with
   1583  //      HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement().
   1584  EditorRawDOMRange rangeToDelete(aRangesToDelete.FirstRangeRef());
   1585  if (MOZ_UNLIKELY(!rangeToDelete.IsInTextNodes())) {
   1586    NS_WARNING("The extended range to delete character was not in text nodes");
   1587    return Err(NS_ERROR_FAILURE);
   1588  }
   1589 
   1590  // If deleting some characters makes the last line before a block boundary
   1591  // empty, we need to put a line break.
   1592  const bool becomesEmptyLine = [&]() {
   1593    if (!rangeToDelete.StartRef().IsStartOfContainer() ||
   1594        !rangeToDelete.EndRef().IsEndOfContainer()) {
   1595      return false;
   1596    }
   1597    const WSScanResult previousThing =
   1598        WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   1599            {}, rangeToDelete.StartRef());
   1600    if (!previousThing.ReachedLineBoundary() ||
   1601        previousThing.ReachedBlockBoundary()) {
   1602      return false;
   1603    }
   1604    WSScanResult nextThing =
   1605        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   1606            {}, rangeToDelete.EndRef());
   1607    if (nextThing.ReachedBRElement() ||
   1608        nextThing.ReachedPreformattedLineBreak()) {
   1609      nextThing = WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   1610          {}, nextThing.PointAfterReachedContent<EditorRawDOMPoint>());
   1611    }
   1612    return nextThing.ReachedBlockBoundary();
   1613  }();
   1614 
   1615  Result<CaretPoint, nsresult> caretPointOrError =
   1616      aHTMLEditor.DeleteTextAndNormalizeSurroundingWhiteSpaces(
   1617          rangeToDelete.StartRef().AsInText(),
   1618          rangeToDelete.EndRef().AsInText(),
   1619          !aEditingHost.IsContentEditablePlainTextOnly()
   1620              ? TreatEmptyTextNodes::RemoveAllEmptyInlineAncestors
   1621              : TreatEmptyTextNodes::Remove,
   1622          aDirectionAndAmount == nsIEditor::eNext ? DeleteDirection::Forward
   1623                                                  : DeleteDirection::Backward,
   1624          aEditingHost);
   1625  if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   1626    NS_WARNING(
   1627        "HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces() failed");
   1628    return caretPointOrError;
   1629  }
   1630  if (!becomesEmptyLine) {
   1631    return caretPointOrError;
   1632  }
   1633  const EditorDOMPoint pointToPutLineBreak =
   1634      caretPointOrError.unwrap().UnwrapCaretPoint();
   1635  const Maybe<LineBreakType> lineBreakType =
   1636      aHTMLEditor.GetPreferredLineBreakType(
   1637          *pointToPutLineBreak.ContainerAs<nsIContent>(), aEditingHost);
   1638  if (NS_WARN_IF(lineBreakType.isNothing())) {
   1639    return Err(NS_ERROR_FAILURE);
   1640  }
   1641  Result<CreateLineBreakResult, nsresult> lineBreakOrError =
   1642      aHTMLEditor.InsertLineBreak(WithTransaction::Yes, *lineBreakType,
   1643                                  pointToPutLineBreak, nsIEditor::ePrevious);
   1644  if (MOZ_UNLIKELY(lineBreakOrError.isErr())) {
   1645    NS_WARNING(
   1646        "HTMLEditor::InsertLineBreak(WithTransaction::Yes, "
   1647        "nsIEditor::ePrevious) failed");
   1648    return lineBreakOrError.propagateErr();
   1649  }
   1650  return CaretPoint(lineBreakOrError.unwrap().UnwrapCaretPoint());
   1651 }
   1652 
   1653 // static
   1654 nsIContent* HTMLEditor::AutoDeleteRangesHandler::GetAtomicContentToDelete(
   1655    nsIEditor::EDirection aDirectionAndAmount,
   1656    const WSRunScanner& aWSRunScannerAtCaret,
   1657    const WSScanResult& aScanFromCaretPointResult) {
   1658  MOZ_ASSERT(aScanFromCaretPointResult.GetContent());
   1659 
   1660  if (!aScanFromCaretPointResult.ReachedSpecialContent()) {
   1661    return aScanFromCaretPointResult.GetContent();
   1662  }
   1663 
   1664  if (!aScanFromCaretPointResult.GetContent()->IsText() ||
   1665      HTMLEditUtils::IsRemovableNode(*aScanFromCaretPointResult.GetContent())) {
   1666    return aScanFromCaretPointResult.GetContent();
   1667  }
   1668 
   1669  // aScanFromCaretPointResult is non-removable text node.
   1670  // Since we try removing atomic content, we look for removable node from
   1671  // scanned point that is non-removable text.
   1672  nsIContent* removableRoot = aScanFromCaretPointResult.GetContent();
   1673  while (removableRoot && !HTMLEditUtils::IsRemovableNode(*removableRoot)) {
   1674    removableRoot = removableRoot->GetParent();
   1675  }
   1676 
   1677  if (removableRoot) {
   1678    return removableRoot;
   1679  }
   1680 
   1681  // Not found better content. This content may not be removable.
   1682  return aScanFromCaretPointResult.GetContent();
   1683 }
   1684 
   1685 nsresult
   1686 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent(
   1687    const nsIContent& aAtomicContent,
   1688    AutoClonedSelectionRangeArray& aRangesToDelete) const {
   1689  EditorDOMRange rangeToDelete =
   1690      WSRunScanner::GetRangesForDeletingAtomicContent(
   1691          {WSRunScanner::Option::OnlyEditableNodes}, aAtomicContent);
   1692  if (!rangeToDelete.IsPositioned()) {
   1693    NS_WARNING("WSRunScanner::GetRangeForDeleteAContentNode() failed");
   1694    return NS_ERROR_FAILURE;
   1695  }
   1696 
   1697  // FIXME: If we'll delete unnecessary following <br>, we need to include it
   1698  // into aRangesToDelete.
   1699 
   1700  nsresult rv = aRangesToDelete.SetStartAndEnd(rangeToDelete.StartRef(),
   1701                                               rangeToDelete.EndRef());
   1702  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   1703                       "AutoClonedRangeArray::SetStartAndEnd() failed");
   1704  return rv;
   1705 }
   1706 
   1707 Result<CaretPoint, nsresult>
   1708 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAtomicContent(
   1709    HTMLEditor& aHTMLEditor, nsIContent& aAtomicContent,
   1710    const EditorDOMPoint& aCaretPoint, const WSRunScanner& aWSRunScannerAtCaret,
   1711    const Element& aEditingHost) {
   1712  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   1713  MOZ_ASSERT(!HTMLEditUtils::IsInvisibleBRElement(aAtomicContent));
   1714  MOZ_ASSERT(!aAtomicContent.IsEditingHost());
   1715 
   1716  EditorDOMPoint pointToPutCaret = aCaretPoint;
   1717  {
   1718    AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
   1719                                           &pointToPutCaret);
   1720    Result<CaretPoint, nsresult> caretPointOrError =
   1721        WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
   1722            aHTMLEditor, aAtomicContent, aCaretPoint, aEditingHost);
   1723    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   1724      NS_WARNING(
   1725          "WhiteSpaceVisibilityKeeper::"
   1726          "DeleteContentNodeAndJoinTextNodesAroundIt() failed");
   1727      return caretPointOrError;
   1728    }
   1729    trackPointToPutCaret.FlushAndStopTracking();
   1730    caretPointOrError.unwrap().MoveCaretPointTo(
   1731        pointToPutCaret, aHTMLEditor,
   1732        {SuggestCaret::OnlyIfHasSuggestion,
   1733         SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
   1734    if (NS_WARN_IF(!pointToPutCaret.IsSet())) {
   1735      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1736    }
   1737  }
   1738 
   1739  if (MOZ_LIKELY(pointToPutCaret.IsInContentNode())) {
   1740    AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
   1741                                           &pointToPutCaret);
   1742    nsresult rv =
   1743        aHTMLEditor.EnsureNoFollowingUnnecessaryLineBreak(pointToPutCaret);
   1744    if (NS_FAILED(rv)) {
   1745      NS_WARNING("HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
   1746      return Err(rv);
   1747    }
   1748  }
   1749  if (NS_WARN_IF(!pointToPutCaret.IsSet())) {
   1750    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1751  }
   1752 
   1753  if ((aHTMLEditor.IsMailEditor() || aHTMLEditor.IsPlaintextMailComposer()) &&
   1754      MOZ_LIKELY(pointToPutCaret.IsInContentNode())) {
   1755    AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
   1756                                           &pointToPutCaret);
   1757    nsresult rv = aHTMLEditor.DeleteMostAncestorMailCiteElementIfEmpty(
   1758        MOZ_KnownLive(*pointToPutCaret.ContainerAs<nsIContent>()));
   1759    if (NS_FAILED(rv)) {
   1760      NS_WARNING(
   1761          "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed");
   1762      return Err(rv);
   1763    }
   1764    trackPointToPutCaret.FlushAndStopTracking();
   1765    if (NS_WARN_IF(!pointToPutCaret.IsSetAndValidInComposedDoc())) {
   1766      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1767    }
   1768  }
   1769 
   1770  if (aHTMLEditor.GetTopLevelEditSubAction() ==
   1771      EditSubAction::eDeleteSelectedContent) {
   1772    AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
   1773                                           &pointToPutCaret);
   1774    Result<CreateLineBreakResult, nsresult> insertPaddingBRElementOrError =
   1775        aHTMLEditor.InsertPaddingBRElementIfNeeded(
   1776            pointToPutCaret,
   1777            aEditingHost.IsContentEditablePlainTextOnly() ? nsIEditor::eNoStrip
   1778                                                          : nsIEditor::eStrip,
   1779            aEditingHost);
   1780    if (MOZ_UNLIKELY(insertPaddingBRElementOrError.isErr())) {
   1781      NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed");
   1782      return insertPaddingBRElementOrError.propagateErr();
   1783    }
   1784    trackPointToPutCaret.FlushAndStopTracking();
   1785    if (!pointToPutCaret.IsInTextNode()) {
   1786      insertPaddingBRElementOrError.unwrap().MoveCaretPointTo(
   1787          pointToPutCaret, aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion});
   1788      if (NS_WARN_IF(!pointToPutCaret.IsSet())) {
   1789        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1790      }
   1791    } else {
   1792      insertPaddingBRElementOrError.unwrap().IgnoreCaretPointSuggestion();
   1793      if (NS_WARN_IF(!pointToPutCaret.IsSet())) {
   1794        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1795      }
   1796    }
   1797  }
   1798  return CaretPoint(std::move(pointToPutCaret));
   1799 }
   1800 
   1801 // static
   1802 Result<bool, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   1803    ExtendRangeToContainAncestorInlineElementsAtStart(
   1804        nsRange& aRangeToDelete, const Element& aEditingHost) {
   1805  MOZ_ASSERT(aRangeToDelete.IsPositioned());
   1806  MOZ_ASSERT(aRangeToDelete.GetCommonAncestorContainer(IgnoreErrors()));
   1807  MOZ_ASSERT(aRangeToDelete.GetCommonAncestorContainer(IgnoreErrors())
   1808                 ->IsInclusiveDescendantOf(&aEditingHost));
   1809 
   1810  EditorRawDOMPoint startPoint(aRangeToDelete.StartRef());
   1811  if (startPoint.IsInTextNode()) {
   1812    if (!startPoint.IsStartOfContainer()) {
   1813      // FIXME: If before the point has only collapsible white-spaces and the
   1814      // text node follows a block boundary, we should treat the range start
   1815      // from start of the text node.
   1816      return true;
   1817    }
   1818    startPoint.Set(startPoint.ContainerAs<Text>());
   1819    if (NS_WARN_IF(!startPoint.IsSet())) {
   1820      return Err(NS_ERROR_FAILURE);
   1821    }
   1822    if (startPoint.GetContainer() == &aEditingHost) {
   1823      return false;
   1824    }
   1825  } else if (startPoint.IsInDataNode()) {
   1826    startPoint.Set(startPoint.ContainerAs<nsIContent>());
   1827    if (NS_WARN_IF(!startPoint.IsSet())) {
   1828      return Err(NS_ERROR_FAILURE);
   1829    }
   1830    if (startPoint.GetContainer() == &aEditingHost) {
   1831      return false;
   1832    }
   1833  } else if (startPoint.GetContainer() == &aEditingHost) {
   1834    return false;
   1835  }
   1836 
   1837  // FYI: This method is designed for deleting inline elements which become
   1838  // empty if aRangeToDelete which crosses a block boundary of right block
   1839  // child.  Therefore, you may need to improve this method if you want to use
   1840  // this in the other cases.
   1841 
   1842  nsINode* const commonAncestor =
   1843      nsContentUtils::GetClosestCommonInclusiveAncestor(
   1844          startPoint.GetContainer(), aRangeToDelete.GetEndContainer());
   1845  if (NS_WARN_IF(!commonAncestor)) {
   1846    return Err(NS_ERROR_FAILURE);
   1847  }
   1848  MOZ_ASSERT(commonAncestor->IsInclusiveDescendantOf(&aEditingHost));
   1849 
   1850  EditorRawDOMPoint newStartPoint(startPoint);
   1851  while (newStartPoint.GetContainer() != &aEditingHost &&
   1852         newStartPoint.GetContainer() != commonAncestor) {
   1853    if (NS_WARN_IF(!newStartPoint.IsInContentNode())) {
   1854      return Err(NS_ERROR_FAILURE);
   1855    }
   1856    if (!HTMLEditUtils::IsInlineContent(
   1857            *newStartPoint.ContainerAs<nsIContent>(),
   1858            BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
   1859      break;
   1860    }
   1861    // The container is inline, check whether the point is first visible point
   1862    // or not to consider whether climbing up the tree.
   1863    bool foundVisiblePrevSibling = false;
   1864    for (nsIContent* content = newStartPoint.GetPreviousSiblingOfChild();
   1865         content; content = content->GetPreviousSibling()) {
   1866      if (Text* text = Text::FromNode(content)) {
   1867        if (HTMLEditUtils::IsVisibleTextNode(*text)) {
   1868          foundVisiblePrevSibling = true;
   1869          break;
   1870        }
   1871        // The text node is invisible.
   1872      } else if (content->IsComment()) {
   1873        // Ignore the comment node.
   1874      } else if (!HTMLEditUtils::IsInlineContent(
   1875                     *content,
   1876                     BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
   1877                 !HTMLEditUtils::IsEmptyNode(
   1878                     *content,
   1879                     {EmptyCheckOption::TreatSingleBRElementAsVisible})) {
   1880        foundVisiblePrevSibling = true;
   1881        break;
   1882      }
   1883    }
   1884    if (foundVisiblePrevSibling) {
   1885      break;
   1886    }
   1887    // the point can be treated as start of the parent inline now.
   1888    newStartPoint.Set(newStartPoint.ContainerAs<nsIContent>());
   1889    if (NS_WARN_IF(!newStartPoint.IsSet())) {
   1890      return Err(NS_ERROR_FAILURE);
   1891    }
   1892  }
   1893  if (newStartPoint == startPoint) {
   1894    return false;  // Don't need to modify the range
   1895  }
   1896  IgnoredErrorResult error;
   1897  aRangeToDelete.SetStart(newStartPoint.ToRawRangeBoundary(), error);
   1898  if (MOZ_UNLIKELY(error.Failed())) {
   1899    return Err(NS_ERROR_FAILURE);
   1900  }
   1901  return true;
   1902 }
   1903 
   1904 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   1905    PrepareToDeleteAtOtherBlockBoundary(
   1906        const HTMLEditor& aHTMLEditor,
   1907        nsIEditor::EDirection aDirectionAndAmount, Element& aOtherBlockElement,
   1908        const EditorDOMPoint& aCaretPoint,
   1909        const WSRunScanner& aWSRunScannerAtCaret) {
   1910  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   1911  MOZ_ASSERT(aCaretPoint.IsSetAndValid());
   1912 
   1913  mMode = Mode::JoinOtherBlock;
   1914 
   1915  // Make sure it's not a table element.  If so, cancel the operation
   1916  // (translation: users cannot backspace or delete across table cells)
   1917  if (HTMLEditUtils::IsAnyTableElementExceptColumnElement(aOtherBlockElement)) {
   1918    return false;
   1919  }
   1920 
   1921  mOtherBlockElement = &aOtherBlockElement;
   1922  // First find the adjacent node in the block
   1923  mLeafContentInOtherBlock =
   1924      ComputeLeafContentInOtherBlockElement(aDirectionAndAmount);
   1925  if (aDirectionAndAmount == nsIEditor::ePrevious) {
   1926    mLeftContent = mLeafContentInOtherBlock;
   1927    mRightContent = aCaretPoint.GetContainerAs<nsIContent>();
   1928  } else {
   1929    mLeftContent = aCaretPoint.GetContainerAs<nsIContent>();
   1930    mRightContent = mLeafContentInOtherBlock;
   1931  }
   1932 
   1933  // Next to a block.  See if we are between the block and a `<br>`.
   1934  // If so, we really want to delete the `<br>`.  Else join content at
   1935  // selection to the block.
   1936  const WSScanResult scanFromCaretResult =
   1937      aDirectionAndAmount == nsIEditor::eNext
   1938          ? aWSRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
   1939                aCaretPoint)
   1940          : aWSRunScannerAtCaret
   1941                .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aCaretPoint);
   1942  // If we found a `<br>` element, we need to delete it instead of joining the
   1943  // contents.
   1944  if (scanFromCaretResult.ReachedBRElement()) {
   1945    mBRElement = scanFromCaretResult.BRElementPtr();
   1946    mMode = Mode::DeleteBRElement;
   1947    return true;
   1948  }
   1949 
   1950  return mLeftContent && mRightContent;
   1951 }
   1952 nsIContent* HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   1953    ComputeLeafContentInOtherBlockElement(
   1954        nsIEditor::EDirection aDirectionAndAmount) const {
   1955  MOZ_ASSERT(mOtherBlockElement);
   1956  return aDirectionAndAmount == nsIEditor::ePrevious
   1957             ? HTMLEditUtils::GetLastLeafContent(
   1958                   *mOtherBlockElement, {LeafNodeType::OnlyEditableLeafNode},
   1959                   BlockInlineCheck::Unused, mOtherBlockElement)
   1960             : HTMLEditUtils::GetFirstLeafContent(
   1961                   *mOtherBlockElement, {LeafNodeType::OnlyEditableLeafNode},
   1962                   BlockInlineCheck::Unused, mOtherBlockElement);
   1963 }
   1964 
   1965 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   1966    ComputeRangeToDeleteLineBreak(const HTMLEditor& aHTMLEditor,
   1967                                  nsRange& aRangeToDelete,
   1968                                  const Element& aEditingHost,
   1969                                  ComputeRangeFor aComputeRangeFor) const {
   1970  // FIXME: Scan invisible leading white-spaces after the <br>.
   1971  MOZ_ASSERT_IF(mMode == Mode::DeleteBRElement, mBRElement);
   1972  MOZ_ASSERT_IF(mMode == Mode::DeletePrecedingBRElementOfBlock, mBRElement);
   1973  MOZ_ASSERT_IF(mMode == Mode::DeletePrecedingPreformattedLineBreak,
   1974                mPreformattedLineBreak.IsSetAndValid());
   1975  MOZ_ASSERT_IF(mMode == Mode::DeletePrecedingPreformattedLineBreak,
   1976                mPreformattedLineBreak.IsCharPreformattedNewLine());
   1977  MOZ_ASSERT_IF(aComputeRangeFor == ComputeRangeFor::GetTargetRanges,
   1978                aRangeToDelete.IsPositioned());
   1979 
   1980  // If we're computing for beforeinput.getTargetRanges() and the inputType
   1981  // is not a simple deletion like replacing selected content with new
   1982  // content, the range should end at the original end boundary of the given
   1983  // range.
   1984  const bool preserveEndBoundary =
   1985      (mMode == Mode::DeletePrecedingBRElementOfBlock ||
   1986       mMode == Mode::DeletePrecedingPreformattedLineBreak) &&
   1987      aComputeRangeFor == ComputeRangeFor::GetTargetRanges &&
   1988      !MayEditActionDeleteAroundCollapsedSelection(aHTMLEditor.GetEditAction());
   1989 
   1990  if (mMode != Mode::DeletePrecedingPreformattedLineBreak) {
   1991    Element* const mostDistantInlineAncestor =
   1992        HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
   1993            *mBRElement, BlockInlineCheck::UseComputedDisplayOutsideStyle,
   1994            &aEditingHost);
   1995    if (preserveEndBoundary) {
   1996      // FIXME: If the range ends at end of an inline element, we may need to
   1997      // extend the range.
   1998      IgnoredErrorResult error;
   1999      aRangeToDelete.SetStart(EditorRawDOMPoint(mostDistantInlineAncestor
   2000                                                    ? mostDistantInlineAncestor
   2001                                                    : mBRElement)
   2002                                  .ToRawRangeBoundary(),
   2003                              error);
   2004      NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SetStart() failed");
   2005      MOZ_ASSERT_IF(!error.Failed(), !aRangeToDelete.Collapsed());
   2006      return error.StealNSResult();
   2007    }
   2008    IgnoredErrorResult error;
   2009    aRangeToDelete.SelectNode(
   2010        mostDistantInlineAncestor ? *mostDistantInlineAncestor : *mBRElement,
   2011        error);
   2012    NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SelectNode() failed");
   2013    return error.StealNSResult();
   2014  }
   2015 
   2016  Element* const mostDistantInlineAncestor =
   2017      mPreformattedLineBreak.ContainerAs<Text>()->TextDataLength() == 1
   2018          ? HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
   2019                *mPreformattedLineBreak.ContainerAs<Text>(),
   2020                BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)
   2021          : nullptr;
   2022 
   2023  if (!mostDistantInlineAncestor) {
   2024    if (preserveEndBoundary) {
   2025      // FIXME: If the range ends at end of an inline element, we may need to
   2026      // extend the range.
   2027      IgnoredErrorResult error;
   2028      aRangeToDelete.SetStart(mPreformattedLineBreak.ToRawRangeBoundary(),
   2029                              error);
   2030      MOZ_ASSERT_IF(!error.Failed(), !aRangeToDelete.Collapsed());
   2031      NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SetStart() failed");
   2032      return error.StealNSResult();
   2033    }
   2034    nsresult rv = aRangeToDelete.SetStartAndEnd(
   2035        mPreformattedLineBreak.ToRawRangeBoundary(),
   2036        mPreformattedLineBreak.NextPoint().ToRawRangeBoundary());
   2037    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
   2038    return rv;
   2039  }
   2040 
   2041  if (preserveEndBoundary) {
   2042    // FIXME: If the range ends at end of an inline element, we may need to
   2043    // extend the range.
   2044    IgnoredErrorResult error;
   2045    aRangeToDelete.SetStart(
   2046        EditorRawDOMPoint(mostDistantInlineAncestor).ToRawRangeBoundary(),
   2047        error);
   2048    MOZ_ASSERT_IF(!error.Failed(), !aRangeToDelete.Collapsed());
   2049    NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SetStart() failed");
   2050    return error.StealNSResult();
   2051  }
   2052 
   2053  IgnoredErrorResult error;
   2054  aRangeToDelete.SelectNode(*mostDistantInlineAncestor, error);
   2055  NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SelectNode() failed");
   2056  return error.StealNSResult();
   2057 }
   2058 
   2059 Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   2060    AutoBlockElementsJoiner::HandleDeleteLineBreak(
   2061        HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   2062        const EditorDOMPoint& aCaretPoint, const Element& aEditingHost) {
   2063  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   2064  MOZ_ASSERT(mBRElement || mPreformattedLineBreak.IsSet());
   2065 
   2066  // If we're deleting selection (not replacing with new content), we should
   2067  // put caret to end of preceding text node if there is.  Then, users can type
   2068  // text in it like the other browsers.
   2069  EditorDOMPoint pointToPutCaret = [&]() {
   2070    // but when we're deleting a preceding line break of current block, we
   2071    // should keep the caret position in the current block.
   2072    if (mMode == Mode::DeletePrecedingBRElementOfBlock ||
   2073        mMode == Mode::DeletePrecedingPreformattedLineBreak) {
   2074      return aCaretPoint;
   2075    }
   2076    if (!MayEditActionDeleteAroundCollapsedSelection(
   2077            aHTMLEditor.GetEditAction())) {
   2078      return EditorDOMPoint();
   2079    }
   2080    const WSRunScanner scanner({WSRunScanner::Option::OnlyEditableNodes},
   2081                               EditorRawDOMPoint(mBRElement));
   2082    const WSScanResult maybePreviousText =
   2083        scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
   2084            EditorRawDOMPoint(mBRElement));
   2085    if (maybePreviousText.IsContentEditable() &&
   2086        maybePreviousText.InVisibleOrCollapsibleCharacters() &&
   2087        !HTMLEditor::GetLinkElement(maybePreviousText.TextPtr())) {
   2088      return maybePreviousText.PointAfterReachedContent<EditorDOMPoint>();
   2089    }
   2090    const WSScanResult maybeNextText =
   2091        scanner.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
   2092            EditorRawDOMPoint::After(*mBRElement));
   2093    if (maybeNextText.IsContentEditable() &&
   2094        maybeNextText.InVisibleOrCollapsibleCharacters()) {
   2095      return maybeNextText.PointAtReachedContent<EditorDOMPoint>();
   2096    }
   2097    return EditorDOMPoint();
   2098  }();
   2099 
   2100  RefPtr<nsRange> rangeToDelete =
   2101      nsRange::Create(const_cast<Element*>(&aEditingHost));
   2102  MOZ_ASSERT(rangeToDelete);
   2103  nsresult rv =
   2104      ComputeRangeToDeleteLineBreak(aHTMLEditor, *rangeToDelete, aEditingHost,
   2105                                    ComputeRangeFor::ToDeleteTheRange);
   2106  if (NS_FAILED(rv)) {
   2107    NS_WARNING(
   2108        "AutoBlockElementsJoiner::ComputeRangeToDeleteLineBreak() failed");
   2109    return Err(rv);
   2110  }
   2111  Result<EditActionResult, nsresult> result = HandleDeleteNonCollapsedRange(
   2112      aHTMLEditor, aDirectionAndAmount, nsIEditor::eNoStrip, *rangeToDelete,
   2113      SelectionWasCollapsed::Yes, aEditingHost);
   2114  if (MOZ_UNLIKELY(result.isErr())) {
   2115    NS_WARNING(
   2116        "AutoBlockElementsJoiner::HandleDeleteNonCollapsedRange() failed");
   2117    return result;
   2118  }
   2119 
   2120  if (mLeftContent && mRightContent &&
   2121      HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent) !=
   2122          HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent)) {
   2123    return EditActionResult::HandledResult();
   2124  }
   2125 
   2126  // Put selection at edge of block and we are done.
   2127  if (NS_WARN_IF(mMode == Mode::DeleteBRElement && !mLeafContentInOtherBlock)) {
   2128    // XXX This must be odd case.  The other block can be empty.
   2129    return Err(NS_ERROR_FAILURE);
   2130  }
   2131 
   2132  if ((mMode == Mode::DeletePrecedingBRElementOfBlock ||
   2133       mMode == Mode::DeletePrecedingPreformattedLineBreak) &&
   2134      pointToPutCaret.IsSetAndValidInComposedDoc()) {
   2135    // If we're deleting only the preceding lines of a block, we should
   2136    // normalize the white-spaces at start of the block for compatibility
   2137    // with the other browsers.
   2138    AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
   2139                                           &pointToPutCaret);
   2140    Result<EditorDOMPoint, nsresult> atFirstVisibleThingOrError =
   2141        WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter(
   2142            aHTMLEditor, pointToPutCaret, {});
   2143    if (MOZ_UNLIKELY(atFirstVisibleThingOrError.isErr())) {
   2144      NS_WARNING(
   2145          "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() failed");
   2146      return atFirstVisibleThingOrError.propagateErr();
   2147    }
   2148    trackPointToPutCaret.FlushAndStopTracking();
   2149    if (NS_WARN_IF(!pointToPutCaret.IsSetAndValidInComposedDoc())) {
   2150      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2151    }
   2152  }
   2153 
   2154  if (pointToPutCaret.IsSet()) {
   2155    nsresult rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret);
   2156    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   2157      return Err(NS_ERROR_EDITOR_DESTROYED);
   2158    }
   2159    if (mMode == Mode::DeleteBRElement && NS_SUCCEEDED(rv)) {
   2160      // If we prefer to use style in the previous line, we should forget
   2161      // previous styles since the caret position has all styles which we want
   2162      // to use with new content.
   2163      if (nsIEditor::DirectionIsBackspace(aDirectionAndAmount)) {
   2164        aHTMLEditor.TopLevelEditSubActionDataRef()
   2165            .mCachedPendingStyles->Clear();
   2166      }
   2167      // And we don't want to keep extending a link at ex-end of the previous
   2168      // paragraph.
   2169      if (HTMLEditor::GetLinkElement(pointToPutCaret.GetContainer())) {
   2170        aHTMLEditor.mPendingStylesToApplyToNewContent
   2171            ->ClearLinkAndItsSpecifiedStyle();
   2172      }
   2173    } else {
   2174      NS_WARNING_ASSERTION(
   2175          NS_SUCCEEDED(rv),
   2176          "EditorBase::CollapseSelectionTo() failed, but ignored");
   2177    }
   2178    return EditActionResult::HandledResult();
   2179  }
   2180 
   2181  EditorRawDOMPoint newCaretPosition =
   2182      HTMLEditUtils::GetGoodCaretPointFor<EditorRawDOMPoint>(
   2183          *mLeafContentInOtherBlock, aDirectionAndAmount);
   2184  if (MOZ_UNLIKELY(!newCaretPosition.IsInContentNode())) {
   2185    NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed");
   2186    return Err(NS_ERROR_FAILURE);
   2187  }
   2188  // If we're deleting only a line break and move caret to left block, we
   2189  // want to normalize the white-spaces at end of the left block for the
   2190  // compatibility with the other browsers.
   2191  WSScanResult nextThingOfCaretPoint =
   2192      WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   2193          {}, newCaretPosition);
   2194  if (nextThingOfCaretPoint.ReachedBRElement() ||
   2195      nextThingOfCaretPoint.ReachedPreformattedLineBreak()) {
   2196    nextThingOfCaretPoint =
   2197        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   2198            {}, nextThingOfCaretPoint
   2199                    .PointAfterReachedContent<EditorRawDOMPoint>());
   2200  }
   2201  if (nextThingOfCaretPoint.ReachedBlockBoundary()) {
   2202    const EditorDOMPoint atBlockBoundary =
   2203        nextThingOfCaretPoint.ReachedCurrentBlockBoundary()
   2204            ? EditorDOMPoint::AtEndOf(*nextThingOfCaretPoint.ElementPtr())
   2205            : EditorDOMPoint(nextThingOfCaretPoint.ElementPtr());
   2206    Result<EditorDOMPoint, nsresult> afterLastVisibleThingOrError =
   2207        WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore(
   2208            aHTMLEditor, atBlockBoundary, {});
   2209    if (MOZ_UNLIKELY(afterLastVisibleThingOrError.isErr())) {
   2210      NS_WARNING(
   2211          "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore() "
   2212          "failed");
   2213      return afterLastVisibleThingOrError.propagateErr();
   2214    }
   2215  }
   2216  rv = aHTMLEditor.CollapseSelectionTo(newCaretPosition);
   2217  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   2218    return Err(NS_ERROR_EDITOR_DESTROYED);
   2219  }
   2220  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2221                       "EditorBase::CollapseSelectionTo() failed, but ignored");
   2222  return EditActionResult::HandledResult();
   2223 }
   2224 
   2225 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   2226    ComputeRangeToDeleteAtOtherBlockBoundary(
   2227        const HTMLEditor& aHTMLEditor,
   2228        nsIEditor::EDirection aDirectionAndAmount,
   2229        const EditorDOMPoint& aCaretPoint, nsRange& aRangeToDelete,
   2230        const Element& aEditingHost) const {
   2231  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   2232  MOZ_ASSERT(aCaretPoint.IsSetAndValid());
   2233  MOZ_ASSERT(mLeftContent);
   2234  MOZ_ASSERT(mRightContent);
   2235 
   2236  if (HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent) !=
   2237      HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent)) {
   2238    if (!mDeleteRangesHandlerConst.CanFallbackToDeleteRangeWithTransaction(
   2239            aRangeToDelete)) {
   2240      nsresult rv = aRangeToDelete.CollapseTo(aCaretPoint.ToRawRangeBoundary());
   2241      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::CollapseTo() failed");
   2242      return rv;
   2243    }
   2244    nsresult rv = mDeleteRangesHandlerConst
   2245                      .FallbackToComputeRangeToDeleteRangeWithTransaction(
   2246                          aHTMLEditor, aRangeToDelete, aEditingHost);
   2247    NS_WARNING_ASSERTION(
   2248        NS_SUCCEEDED(rv),
   2249        "AutoDeleteRangesHandler::"
   2250        "FallbackToComputeRangeToDeleteRangeWithTransaction() failed");
   2251    return rv;
   2252  }
   2253 
   2254  AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent,
   2255                                                  *mRightContent);
   2256  Result<bool, nsresult> canJoinThem =
   2257      joiner.Prepare(aHTMLEditor, aEditingHost);
   2258  if (canJoinThem.isErr()) {
   2259    NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
   2260    return canJoinThem.unwrapErr();
   2261  }
   2262  if (canJoinThem.inspect() && joiner.CanJoinBlocks() &&
   2263      !joiner.ShouldDeleteLeafContentInstead()) {
   2264    nsresult rv =
   2265        joiner.ComputeRangeToDelete(aHTMLEditor, aCaretPoint, aRangeToDelete);
   2266    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2267                         "AutoInclusiveAncestorBlockElementsJoiner::"
   2268                         "ComputeRangeToDelete() failed");
   2269    return rv;
   2270  }
   2271 
   2272  // If AutoInclusiveAncestorBlockElementsJoiner didn't handle it and it's not
   2273  // canceled, user may want to modify the start leaf node or the last leaf
   2274  // node of the block.
   2275  if (mLeafContentInOtherBlock == aCaretPoint.GetContainer()) {
   2276    return NS_OK;
   2277  }
   2278 
   2279  AutoHideSelectionChanges hideSelectionChanges(aHTMLEditor.SelectionRef());
   2280 
   2281  // If it's ignored, it didn't modify the DOM tree.  In this case, user must
   2282  // want to delete nearest leaf node in the other block element.
   2283  // TODO: We need to consider this before calling ComputeRangesToDelete() for
   2284  //       computing the deleting range.
   2285  EditorRawDOMPoint newCaretPoint =
   2286      aDirectionAndAmount == nsIEditor::ePrevious
   2287          ? EditorRawDOMPoint::AtEndOf(*mLeafContentInOtherBlock)
   2288          : EditorRawDOMPoint(mLeafContentInOtherBlock, 0);
   2289  // If new caret position is same as current caret position, we can do
   2290  // nothing anymore.
   2291  if (aRangeToDelete.Collapsed() &&
   2292      aRangeToDelete.EndRef() == newCaretPoint.ToRawRangeBoundary()) {
   2293    return NS_OK;
   2294  }
   2295  // TODO: Stop modifying the `Selection` for computing the target ranges.
   2296  nsresult rv = aHTMLEditor.CollapseSelectionTo(newCaretPoint);
   2297  if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
   2298    NS_WARNING(
   2299        "EditorBase::CollapseSelectionTo() caused destroying the editor");
   2300    return NS_ERROR_EDITOR_DESTROYED;
   2301  }
   2302  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2303                       "EditorBase::CollapseSelectionTo() failed");
   2304  if (NS_SUCCEEDED(rv)) {
   2305    AutoClonedSelectionRangeArray rangeArray(aHTMLEditor.SelectionRef());
   2306    if (!rangeArray.GetAncestorLimiter()) {
   2307      rangeArray.SetAncestorLimiter(
   2308          aHTMLEditor.FindSelectionRoot(aEditingHost));
   2309    }
   2310    AutoDeleteRangesHandler anotherHandler(mDeleteRangesHandlerConst);
   2311    rv = anotherHandler.ComputeRangesToDelete(aHTMLEditor, aDirectionAndAmount,
   2312                                              rangeArray, aEditingHost);
   2313    if (NS_SUCCEEDED(rv)) {
   2314      if (MOZ_LIKELY(!rangeArray.Ranges().IsEmpty())) {
   2315        MOZ_ASSERT(rangeArray.Ranges().Length() == 1);
   2316        aRangeToDelete.SetStartAndEnd(rangeArray.FirstRangeRef()->StartRef(),
   2317                                      rangeArray.FirstRangeRef()->EndRef());
   2318      } else {
   2319        NS_WARNING(
   2320            "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() "
   2321            "returned no range");
   2322        rv = NS_ERROR_FAILURE;
   2323      }
   2324    } else {
   2325      NS_WARNING(
   2326          "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() failed");
   2327    }
   2328  }
   2329  // Restore selection.
   2330  nsresult rvCollapsingSelectionTo =
   2331      aHTMLEditor.CollapseSelectionTo(aCaretPoint);
   2332  if (MOZ_UNLIKELY(rvCollapsingSelectionTo == NS_ERROR_EDITOR_DESTROYED)) {
   2333    NS_WARNING(
   2334        "EditorBase::CollapseSelectionTo() caused destroying the editor");
   2335    return NS_ERROR_EDITOR_DESTROYED;
   2336  }
   2337  NS_WARNING_ASSERTION(
   2338      NS_SUCCEEDED(rvCollapsingSelectionTo),
   2339      "EditorBase::CollapseSelectionTo() failed to restore caret position");
   2340  return NS_SUCCEEDED(rv) && NS_SUCCEEDED(rvCollapsingSelectionTo)
   2341             ? NS_OK
   2342             : NS_ERROR_FAILURE;
   2343 }
   2344 
   2345 Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   2346    AutoBlockElementsJoiner::HandleDeleteAtOtherBlockBoundary(
   2347        HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   2348        nsIEditor::EStripWrappers aStripWrappers,
   2349        const EditorDOMPoint& aCaretPoint, nsRange& aRangeToDelete,
   2350        const Element& aEditingHost) {
   2351  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   2352  MOZ_ASSERT(aCaretPoint.IsSetAndValid());
   2353  MOZ_ASSERT(mDeleteRangesHandler);
   2354  MOZ_ASSERT(mLeftContent);
   2355  MOZ_ASSERT(mRightContent);
   2356 
   2357  if (HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent) !=
   2358      HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent)) {
   2359    // If we have not deleted `<br>` element and are not called recursively,
   2360    // we should call `DeleteRangesWithTransaction()` here.
   2361    if (!mDeleteRangesHandler->CanFallbackToDeleteRangeWithTransaction(
   2362            aRangeToDelete)) {
   2363      return EditActionResult::IgnoredResult();
   2364    }
   2365    Result<CaretPoint, nsresult> caretPointOrError =
   2366        mDeleteRangesHandler->FallbackToDeleteRangeWithTransaction(
   2367            aHTMLEditor, aRangeToDelete);
   2368    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   2369      NS_WARNING(
   2370          "AutoDeleteRangesHandler::FallbackToDeleteRangesWithTransaction() "
   2371          "failed");
   2372      return caretPointOrError.propagateErr();
   2373    }
   2374    nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo(
   2375        aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
   2376                      SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   2377                      SuggestCaret::AndIgnoreTrivialError});
   2378    if (NS_FAILED(rv)) {
   2379      NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
   2380      return Err(rv);
   2381    }
   2382    NS_WARNING_ASSERTION(
   2383        rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   2384        "CaretPoint::SuggestCaretPointTo() failed, but ignored");
   2385    // Don't return "ignored" to avoid to fall it back to delete ranges
   2386    // recursively.
   2387    return EditActionResult::HandledResult();
   2388  }
   2389 
   2390  // Else we are joining content to block
   2391  AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent,
   2392                                                  *mRightContent);
   2393  Result<bool, nsresult> canJoinThem =
   2394      joiner.Prepare(aHTMLEditor, aEditingHost);
   2395  if (MOZ_UNLIKELY(canJoinThem.isErr())) {
   2396    NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
   2397    return canJoinThem.propagateErr();
   2398  }
   2399 
   2400  if (!canJoinThem.inspect() || !joiner.CanJoinBlocks()) {
   2401    nsresult rv = aHTMLEditor.CollapseSelectionTo(aCaretPoint);
   2402    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   2403      return Err(NS_ERROR_EDITOR_DESTROYED);
   2404    }
   2405    NS_WARNING_ASSERTION(
   2406        NS_SUCCEEDED(rv),
   2407        "EditorBase::CollapseSelectionTo() failed, but ignored");
   2408    return !canJoinThem.inspect() ? EditActionResult::CanceledResult()
   2409                                  : EditActionResult::IgnoredResult();
   2410  }
   2411 
   2412  EditorDOMPoint pointToPutCaret(aCaretPoint);
   2413  AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
   2414                                         &pointToPutCaret);
   2415  Result<DeleteRangeResult, nsresult> moveFirstLineResult =
   2416      joiner.Run(aHTMLEditor, aEditingHost);
   2417  if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) {
   2418    NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed");
   2419    return moveFirstLineResult.propagateErr();
   2420  }
   2421  DeleteRangeResult unwrappedMoveFirstLineResult = moveFirstLineResult.unwrap();
   2422 #ifdef DEBUG
   2423  if (joiner.ShouldDeleteLeafContentInstead()) {
   2424    NS_ASSERTION(unwrappedMoveFirstLineResult.Ignored(),
   2425                 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
   2426                 "returning ignored, but returned not ignored");
   2427  } else {
   2428    NS_ASSERTION(!unwrappedMoveFirstLineResult.Ignored(),
   2429                 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
   2430                 "returning handled, but returned ignored");
   2431  }
   2432 #endif  // #ifdef DEBUG
   2433  // Even if AutoInclusiveAncestorBlockElementsJoiner claims "ignored",
   2434  // invisible white-spaces may have been normalized.  So,
   2435  // mLeafContentInOtherBlock could've been removed from the DOM.  Therefore, we
   2436  // need to update mLeafContentInOtherBlock.
   2437  if (mLeafContentInOtherBlock &&
   2438      !mLeafContentInOtherBlock->IsInComposedDoc()) {
   2439    mLeafContentInOtherBlock =
   2440        ComputeLeafContentInOtherBlockElement(aDirectionAndAmount);
   2441  }
   2442  // If we're deleting selection (not replacing with new content) and
   2443  // AutoInclusiveAncestorBlockElementsJoiner computed new caret position,
   2444  // we should use it.  Otherwise, we should keep the our traditional behavior.
   2445  if (unwrappedMoveFirstLineResult.Handled() &&
   2446      unwrappedMoveFirstLineResult.HasCaretPointSuggestion() &&
   2447      MayEditActionDeleteAroundCollapsedSelection(
   2448          aHTMLEditor.GetEditAction())) {
   2449    EditorDOMPoint pointToPutCaret =
   2450        unwrappedMoveFirstLineResult.UnwrapCaretPoint();
   2451    nsresult rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret);
   2452    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   2453      return Err(NS_ERROR_EDITOR_DESTROYED);
   2454    }
   2455    if (NS_FAILED(rv)) {
   2456      NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored");
   2457      return EditActionResult::HandledResult();
   2458    }
   2459    // If we prefer to use style in the previous line, we should forget
   2460    // previous styles since the caret position has all styles which we want
   2461    // to use with new content.
   2462    if (nsIEditor::DirectionIsBackspace(aDirectionAndAmount)) {
   2463      aHTMLEditor.TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
   2464    }
   2465    // And we don't want to keep extending a link at ex-end of the previous
   2466    // paragraph.
   2467    if (HTMLEditor::GetLinkElement(pointToPutCaret.GetContainer())) {
   2468      aHTMLEditor.mPendingStylesToApplyToNewContent
   2469          ->ClearLinkAndItsSpecifiedStyle();
   2470    }
   2471    return EditActionResult::HandledResult();
   2472  }
   2473  trackPointToPutCaret.FlushAndStopTracking();
   2474  unwrappedMoveFirstLineResult.IgnoreCaretPointSuggestion();
   2475 
   2476  // If AutoInclusiveAncestorBlockElementsJoiner didn't handle it and it's not
   2477  // canceled, user may want to modify the start leaf node or the last leaf
   2478  // node of the block.
   2479  if (unwrappedMoveFirstLineResult.Ignored() && mLeafContentInOtherBlock &&
   2480      mLeafContentInOtherBlock != aCaretPoint.GetContainer()) {
   2481    // If it's ignored, it didn't modify the DOM tree.  In this case, user
   2482    // must want to delete nearest leaf node in the other block element.
   2483    // TODO: We need to consider this before calling Run() for computing the
   2484    //       deleting range.
   2485    EditorRawDOMPoint newCaretPoint =
   2486        aDirectionAndAmount == nsIEditor::ePrevious
   2487            ? EditorRawDOMPoint::AtEndOf(*mLeafContentInOtherBlock)
   2488            : EditorRawDOMPoint(mLeafContentInOtherBlock, 0);
   2489    // If new caret position is same as current caret position, we can do
   2490    // nothing anymore.
   2491    if (aRangeToDelete.Collapsed() &&
   2492        aRangeToDelete.EndRef() == newCaretPoint.ToRawRangeBoundary()) {
   2493      return EditActionResult::CanceledResult();
   2494    }
   2495    nsresult rv = aHTMLEditor.CollapseSelectionTo(newCaretPoint);
   2496    if (NS_FAILED(rv)) {
   2497      NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   2498      return Err(rv);
   2499    }
   2500    AutoClonedSelectionRangeArray rangesToDelete(aHTMLEditor.SelectionRef());
   2501    if (!rangesToDelete.GetAncestorLimiter()) {
   2502      rangesToDelete.SetAncestorLimiter(
   2503          aHTMLEditor.FindSelectionRoot(aEditingHost));
   2504    }
   2505    AutoDeleteRangesHandler anotherHandler(mDeleteRangesHandler);
   2506    Result<EditActionResult, nsresult> fallbackResult =
   2507        anotherHandler.Run(aHTMLEditor, aDirectionAndAmount, aStripWrappers,
   2508                           rangesToDelete, aEditingHost);
   2509    if (MOZ_UNLIKELY(fallbackResult.isErr())) {
   2510      NS_WARNING("Recursive AutoDeleteRangesHandler::Run() failed");
   2511      return fallbackResult;
   2512    }
   2513    return fallbackResult;
   2514  }
   2515  // Otherwise, we must have deleted the selection as user expected.
   2516  nsresult rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret);
   2517  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   2518    return Err(NS_ERROR_EDITOR_DESTROYED);
   2519  }
   2520  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2521                       "EditorBase::CollapseSelectionTo() failed, but ignored");
   2522  return unwrappedMoveFirstLineResult.Handled()
   2523             ? EditActionResult::HandledResult()
   2524             : EditActionResult::IgnoredResult();
   2525 }
   2526 
   2527 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   2528    PrepareToDeleteAtCurrentBlockBoundary(
   2529        const HTMLEditor& aHTMLEditor,
   2530        nsIEditor::EDirection aDirectionAndAmount,
   2531        Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint,
   2532        const Element& aEditingHost) {
   2533  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   2534 
   2535  // At edge of our block.  Look beside it and see if we can join to an
   2536  // adjacent block
   2537  mMode = Mode::JoinCurrentBlock;
   2538 
   2539  // Don't break the basic structure of the HTML document.
   2540  if (aCurrentBlockElement.IsAnyOfHTMLElements(nsGkAtoms::html, nsGkAtoms::head,
   2541                                               nsGkAtoms::body)) {
   2542    return false;
   2543  }
   2544 
   2545  // Make sure it's not a table element.  If so, cancel the operation
   2546  // (translation: users cannot backspace or delete across table cells)
   2547  if (HTMLEditUtils::IsAnyTableElementExceptColumnElement(
   2548          aCurrentBlockElement)) {
   2549    return false;
   2550  }
   2551 
   2552  auto ScanJoinTarget = [&]() -> nsIContent* {
   2553    nsIContent* targetContent =
   2554        aDirectionAndAmount == nsIEditor::ePrevious
   2555            ? HTMLEditUtils::GetPreviousContent(
   2556                  aCurrentBlockElement, {WalkTreeOption::IgnoreNonEditableNode},
   2557                  BlockInlineCheck::Unused, &aEditingHost)
   2558            : HTMLEditUtils::GetNextContent(
   2559                  aCurrentBlockElement, {WalkTreeOption::IgnoreNonEditableNode},
   2560                  BlockInlineCheck::Unused, &aEditingHost);
   2561    // If found content is an invisible text node, let's scan visible things.
   2562    auto IsIgnorableDataNode = [](nsIContent* aContent) {
   2563      return aContent && HTMLEditUtils::IsRemovableNode(*aContent) &&
   2564             ((aContent->IsText() &&
   2565               aContent->AsText()->TextIsOnlyWhitespace() &&
   2566               !HTMLEditUtils::IsVisibleTextNode(*aContent->AsText())) ||
   2567              (aContent->IsCharacterData() && !aContent->IsText()));
   2568    };
   2569    if (!IsIgnorableDataNode(targetContent)) {
   2570      return targetContent;
   2571    }
   2572    MOZ_ASSERT(mSkippedInvisibleContents.IsEmpty());
   2573    for (nsIContent* adjacentContent =
   2574             aDirectionAndAmount == nsIEditor::ePrevious
   2575                 ? HTMLEditUtils::GetPreviousContent(
   2576                       *targetContent, {WalkTreeOption::StopAtBlockBoundary},
   2577                       BlockInlineCheck::UseComputedDisplayOutsideStyle,
   2578                       &aEditingHost)
   2579                 : HTMLEditUtils::GetNextContent(
   2580                       *targetContent, {WalkTreeOption::StopAtBlockBoundary},
   2581                       BlockInlineCheck::UseComputedDisplayOutsideStyle,
   2582                       &aEditingHost);
   2583         adjacentContent;
   2584         adjacentContent =
   2585             aDirectionAndAmount == nsIEditor::ePrevious
   2586                 ? HTMLEditUtils::GetPreviousContent(
   2587                       *adjacentContent, {WalkTreeOption::StopAtBlockBoundary},
   2588                       BlockInlineCheck::UseComputedDisplayOutsideStyle,
   2589                       &aEditingHost)
   2590                 : HTMLEditUtils::GetNextContent(
   2591                       *adjacentContent, {WalkTreeOption::StopAtBlockBoundary},
   2592                       BlockInlineCheck::UseComputedDisplayOutsideStyle,
   2593                       &aEditingHost)) {
   2594      // If non-editable element is found, we should not skip it to avoid
   2595      // joining too far nodes.
   2596      if (!HTMLEditUtils::IsSimplyEditableNode(*adjacentContent)) {
   2597        break;
   2598      }
   2599      // If block element is found, we should join last leaf content in it.
   2600      if (HTMLEditUtils::IsBlockElement(
   2601              *adjacentContent,
   2602              BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
   2603        nsIContent* leafContent =
   2604            aDirectionAndAmount == nsIEditor::ePrevious
   2605                ? HTMLEditUtils::GetLastLeafContent(
   2606                      *adjacentContent, {LeafNodeType::OnlyEditableLeafNode})
   2607                : HTMLEditUtils::GetFirstLeafContent(
   2608                      *adjacentContent, {LeafNodeType::OnlyEditableLeafNode});
   2609        mSkippedInvisibleContents.AppendElement(*targetContent);
   2610        return leafContent ? leafContent : adjacentContent;
   2611      }
   2612      // Only when the found node is an invisible text node or a non-text data
   2613      // node, we should keep scanning.
   2614      if (IsIgnorableDataNode(adjacentContent)) {
   2615        mSkippedInvisibleContents.AppendElement(*targetContent);
   2616        targetContent = adjacentContent;
   2617        continue;
   2618      }
   2619      // Otherwise, we find a visible things. We should join with last found
   2620      // invisible text node.
   2621      break;
   2622    }
   2623    return targetContent;
   2624  };
   2625 
   2626  if (aDirectionAndAmount == nsIEditor::ePrevious) {
   2627    const WSScanResult prevVisibleThing = [&]() {
   2628      // When Backspace at start of a block, we need to delete only a preceding
   2629      // <br> element if there is.
   2630      const Result<Element*, nsresult>
   2631          inclusiveAncestorOfRightChildBlockOrError = AutoBlockElementsJoiner::
   2632              GetMostDistantBlockAncestorIfPointIsStartAtBlock(aCaretPoint,
   2633                                                               aEditingHost);
   2634      if (NS_WARN_IF(inclusiveAncestorOfRightChildBlockOrError.isErr()) ||
   2635          !inclusiveAncestorOfRightChildBlockOrError.inspect()) {
   2636        return WSScanResult::Error();
   2637      }
   2638      const WSScanResult prevVisibleThingBeforeCurrentBlock =
   2639          WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   2640              {WSRunScanner::Option::OnlyEditableNodes},
   2641              EditorRawDOMPoint(
   2642                  inclusiveAncestorOfRightChildBlockOrError.inspect()));
   2643      if (!prevVisibleThingBeforeCurrentBlock.ReachedBRElement() &&
   2644          !prevVisibleThingBeforeCurrentBlock.ReachedPreformattedLineBreak()) {
   2645        return WSScanResult::Error();
   2646      }
   2647      // There is a preceding line break, but it may be invisible.  Then, users
   2648      // want to delete its preceding content not only the line break.
   2649      // Therefore, let's check whether the line break follows another line
   2650      // break or a block boundary. In these cases, the line break causes an
   2651      // empty line which users may want to delete.
   2652      const auto atPrecedingLineBreak =
   2653          prevVisibleThingBeforeCurrentBlock
   2654              .PointAtReachedContent<EditorRawDOMPoint>();
   2655      MOZ_ASSERT(atPrecedingLineBreak.IsSet());
   2656      const WSScanResult prevVisibleThingBeforeLineBreak =
   2657          WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   2658              {WSRunScanner::Option::OnlyEditableNodes}, atPrecedingLineBreak);
   2659      if (prevVisibleThingBeforeLineBreak.ReachedBRElement() ||
   2660          prevVisibleThingBeforeLineBreak.ReachedPreformattedLineBreak() ||
   2661          prevVisibleThingBeforeLineBreak.ReachedCurrentBlockBoundary()) {
   2662        // Target the latter line break for things simpler.  It's easier to
   2663        // compute the target range.
   2664        MOZ_ASSERT_IF(
   2665            prevVisibleThingBeforeCurrentBlock.ReachedPreformattedLineBreak() &&
   2666                prevVisibleThingBeforeLineBreak.ReachedPreformattedLineBreak(),
   2667            prevVisibleThingBeforeCurrentBlock
   2668                    .PointAtReachedContent<EditorRawDOMPoint>() !=
   2669                prevVisibleThingBeforeLineBreak
   2670                    .PointAtReachedContent<EditorRawDOMPoint>());
   2671        return prevVisibleThingBeforeCurrentBlock;
   2672      }
   2673      return WSScanResult::Error();
   2674    }();
   2675 
   2676    // If previous visible thing is a <br>, we should just delete it without
   2677    // unwrapping the first line of the right child block.  Note that the <br>
   2678    // is always treated as invisible by HTMLEditUtils because it's immediately
   2679    // preceding <br> of the block boundary.  However, deleting it is fine
   2680    // because the above checks whether it causes empty line or not.
   2681    if (prevVisibleThing.ReachedBRElement()) {
   2682      mMode = Mode::DeletePrecedingBRElementOfBlock;
   2683      mBRElement = prevVisibleThing.BRElementPtr();
   2684      return true;
   2685    }
   2686 
   2687    // Same for a preformatted line break.
   2688    if (prevVisibleThing.ReachedPreformattedLineBreak()) {
   2689      mMode = Mode::DeletePrecedingPreformattedLineBreak;
   2690      mPreformattedLineBreak =
   2691          prevVisibleThing.PointAtReachedContent<EditorRawDOMPoint>()
   2692              .AsInText();
   2693      return true;
   2694    }
   2695 
   2696    mLeftContent = ScanJoinTarget();
   2697    mRightContent = aCaretPoint.GetContainerAs<nsIContent>();
   2698  } else {
   2699    mRightContent = ScanJoinTarget();
   2700    mLeftContent = aCaretPoint.GetContainerAs<nsIContent>();
   2701  }
   2702 
   2703  // Nothing to join
   2704  if (!mLeftContent || !mRightContent) {
   2705    return false;
   2706  }
   2707 
   2708  // Don't cross table boundaries.
   2709  return HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent) ==
   2710         HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent);
   2711 }
   2712 
   2713 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   2714    ComputeRangeToDeleteAtCurrentBlockBoundary(
   2715        const HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCaretPoint,
   2716        nsRange& aRangeToDelete, const Element& aEditingHost) const {
   2717  MOZ_ASSERT(mLeftContent);
   2718  MOZ_ASSERT(mRightContent);
   2719 
   2720  AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent,
   2721                                                  *mRightContent);
   2722  Result<bool, nsresult> canJoinThem =
   2723      joiner.Prepare(aHTMLEditor, aEditingHost);
   2724  if (canJoinThem.isErr()) {
   2725    NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
   2726    return canJoinThem.unwrapErr();
   2727  }
   2728  if (canJoinThem.inspect()) {
   2729    nsresult rv =
   2730        joiner.ComputeRangeToDelete(aHTMLEditor, aCaretPoint, aRangeToDelete);
   2731    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2732                         "AutoInclusiveAncestorBlockElementsJoiner::"
   2733                         "ComputeRangesToDelete() failed");
   2734    return rv;
   2735  }
   2736 
   2737  // In this case, nothing will be deleted so that the affected range should
   2738  // be collapsed.
   2739  nsresult rv = aRangeToDelete.CollapseTo(aCaretPoint.ToRawRangeBoundary());
   2740  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::CollapseTo() failed");
   2741  return rv;
   2742 }
   2743 
   2744 Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   2745    AutoBlockElementsJoiner::HandleDeleteAtCurrentBlockBoundary(
   2746        HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   2747        const EditorDOMPoint& aCaretPoint, const Element& aEditingHost) {
   2748  MOZ_ASSERT(mLeftContent);
   2749  MOZ_ASSERT(mRightContent);
   2750 
   2751  AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent,
   2752                                                  *mRightContent);
   2753  Result<bool, nsresult> canJoinThem =
   2754      joiner.Prepare(aHTMLEditor, aEditingHost);
   2755  if (MOZ_UNLIKELY(canJoinThem.isErr())) {
   2756    NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
   2757    return Err(canJoinThem.unwrapErr());
   2758  }
   2759 
   2760  if (!canJoinThem.inspect() || !joiner.CanJoinBlocks()) {
   2761    nsresult rv = aHTMLEditor.CollapseSelectionTo(aCaretPoint);
   2762    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   2763      return Err(NS_ERROR_EDITOR_DESTROYED);
   2764    }
   2765    NS_WARNING_ASSERTION(
   2766        NS_SUCCEEDED(rv),
   2767        "EditorBase::CollapseSelectionTo() failed, but ignored");
   2768    return !canJoinThem.inspect() ? EditActionResult::CanceledResult()
   2769                                  : EditActionResult::HandledResult();
   2770  }
   2771 
   2772  EditorDOMPoint pointToPutCaret(aCaretPoint);
   2773  AutoTrackDOMPoint tracker(aHTMLEditor.RangeUpdaterRef(), &pointToPutCaret);
   2774  Result<DeleteRangeResult, nsresult> moveFirstLineResult =
   2775      joiner.Run(aHTMLEditor, aEditingHost);
   2776  if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) {
   2777    NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed");
   2778    return moveFirstLineResult.propagateErr();
   2779  }
   2780  DeleteRangeResult unwrappedMoveFirstLineResult = moveFirstLineResult.unwrap();
   2781  MOZ_ASSERT_IF(
   2782      unwrappedMoveFirstLineResult.HasCaretPointSuggestion(),
   2783      HTMLEditUtils::IsSimplyEditableNode(
   2784          *unwrappedMoveFirstLineResult.CaretPointRef().GetContainer()));
   2785 #ifdef DEBUG
   2786  if (joiner.ShouldDeleteLeafContentInstead()) {
   2787    NS_ASSERTION(unwrappedMoveFirstLineResult.Ignored(),
   2788                 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
   2789                 "returning ignored, but returned not ignored");
   2790  } else {
   2791    NS_ASSERTION(!unwrappedMoveFirstLineResult.Ignored(),
   2792                 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
   2793                 "returning handled, but returned ignored");
   2794  }
   2795 #endif  // #ifdef DEBUG
   2796 
   2797  // Cleaning up invisible nodes which are skipped at scanning mLeftContent or
   2798  // mRightContent.
   2799  {
   2800    AutoTrackDOMDeleteRangeResult trackMoveFirstLineResult(
   2801        aHTMLEditor.RangeUpdaterRef(), &unwrappedMoveFirstLineResult);
   2802    for (const OwningNonNull<nsIContent>& content : mSkippedInvisibleContents) {
   2803      nsresult rv =
   2804          aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(content));
   2805      if (NS_FAILED(rv)) {
   2806        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   2807        return Err(rv);
   2808      }
   2809    }
   2810    mSkippedInvisibleContents.Clear();
   2811    trackMoveFirstLineResult.FlushAndStopTracking();
   2812    if (unwrappedMoveFirstLineResult.HasCaretPointSuggestion() &&
   2813        NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(
   2814            *unwrappedMoveFirstLineResult.CaretPointRef().GetContainer()))) {
   2815      unwrappedMoveFirstLineResult.ForgetCaretPointSuggestion();
   2816    }
   2817  }
   2818 
   2819  // If we're deleting selection (not replacing with new content) and
   2820  // AutoInclusiveAncestorBlockElementsJoiner computed new caret position, we
   2821  // should use it.  Otherwise, we should keep the our traditional behavior.
   2822  if (unwrappedMoveFirstLineResult.Handled() &&
   2823      unwrappedMoveFirstLineResult.HasCaretPointSuggestion() &&
   2824      MayEditActionDeleteAroundCollapsedSelection(
   2825          aHTMLEditor.GetEditAction())) {
   2826    EditorDOMPoint pointToPutCaret =
   2827        unwrappedMoveFirstLineResult.UnwrapCaretPoint();
   2828    // Don't remove empty inline elements in the plaintext-only mode because
   2829    // nobody can restore the style again.
   2830    if (pointToPutCaret.IsInContentNodeAndValidInComposedDoc() &&
   2831        !aEditingHost.IsContentEditablePlainTextOnly() &&
   2832        MOZ_LIKELY(HTMLEditUtils::IsRemovableFromParentNode(
   2833            *pointToPutCaret.ContainerAs<nsIContent>()))) {
   2834      AutoTrackDOMPoint trackCaretPoint(aHTMLEditor.RangeUpdaterRef(),
   2835                                        &pointToPutCaret);
   2836      Result<CaretPoint, nsresult> caretPointOrError =
   2837          aHTMLEditor.DeleteEmptyInclusiveAncestorInlineElements(
   2838              MOZ_KnownLive(*pointToPutCaret.ContainerAs<nsIContent>()),
   2839              aEditingHost);
   2840      if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   2841        NS_WARNING(
   2842            "HTMLEditor::DeleteEmptyInclusiveAncestorInlineElements() failed");
   2843        return caretPointOrError.propagateErr();
   2844      }
   2845      trackCaretPoint.FlushAndStopTracking();
   2846      caretPointOrError.unwrap().MoveCaretPointTo(
   2847          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   2848    }
   2849    if ((aHTMLEditor.IsMailEditor() || aHTMLEditor.IsPlaintextMailComposer()) &&
   2850        MOZ_LIKELY(pointToPutCaret.IsInContentNode())) {
   2851      AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
   2852                                             &pointToPutCaret);
   2853      nsresult rv = aHTMLEditor.DeleteMostAncestorMailCiteElementIfEmpty(
   2854          MOZ_KnownLive(*pointToPutCaret.ContainerAs<nsIContent>()));
   2855      if (NS_FAILED(rv)) {
   2856        NS_WARNING(
   2857            "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed");
   2858        return Err(rv);
   2859      }
   2860      trackPointToPutCaret.FlushAndStopTracking();
   2861      if (NS_WARN_IF(!pointToPutCaret.IsSetAndValidInComposedDoc())) {
   2862        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2863      }
   2864    }
   2865    if (aHTMLEditor.GetTopLevelEditSubAction() ==
   2866            EditSubAction::eDeleteSelectedContent &&
   2867        pointToPutCaret.IsSetAndValidInComposedDoc()) {
   2868      AutoTrackDOMPoint trackCaretPoint(aHTMLEditor.RangeUpdaterRef(),
   2869                                        &pointToPutCaret);
   2870      Result<CreateLineBreakResult, nsresult> insertPaddingBRElementOrError =
   2871          aHTMLEditor.InsertPaddingBRElementIfNeeded(
   2872              pointToPutCaret,
   2873              aEditingHost.IsContentEditablePlainTextOnly()
   2874                  ? nsIEditor::eNoStrip
   2875                  : nsIEditor::eStrip,
   2876              aEditingHost);
   2877      if (MOZ_UNLIKELY(insertPaddingBRElementOrError.isErr())) {
   2878        NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed");
   2879        return insertPaddingBRElementOrError.propagateErr();
   2880      }
   2881      insertPaddingBRElementOrError.unwrap().MoveCaretPointTo(
   2882          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   2883    }
   2884    nsresult rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret);
   2885    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   2886      return Err(NS_ERROR_EDITOR_DESTROYED);
   2887    }
   2888    if (NS_FAILED(rv)) {
   2889      NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored");
   2890      return EditActionResult::HandledResult();
   2891    }
   2892    // If we prefer to use style in the previous line, we should forget
   2893    // previous styles since the caret position has all styles which we want
   2894    // to use with new content.
   2895    if (nsIEditor::DirectionIsBackspace(aDirectionAndAmount)) {
   2896      aHTMLEditor.TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
   2897    }
   2898    // And we don't want to keep extending a link at ex-end of the previous
   2899    // paragraph.
   2900    if (HTMLEditor::GetLinkElement(pointToPutCaret.GetContainer())) {
   2901      aHTMLEditor.mPendingStylesToApplyToNewContent
   2902          ->ClearLinkAndItsSpecifiedStyle();
   2903    }
   2904    return EditActionResult::HandledResult();
   2905  }
   2906  unwrappedMoveFirstLineResult.IgnoreCaretPointSuggestion();
   2907  tracker.FlushAndStopTracking();
   2908  nsresult rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret);
   2909  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   2910    return Err(NS_ERROR_EDITOR_DESTROYED);
   2911  }
   2912  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2913                       "EditorBase::CollapseSelectionTo() failed, but ignored");
   2914  // This should claim that trying to join the block means that
   2915  // this handles the action because the caller shouldn't do anything
   2916  // anymore in this case.
   2917  return EditActionResult::HandledResult();
   2918 }
   2919 
   2920 nsresult
   2921 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteNonCollapsedRanges(
   2922    const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   2923    AutoClonedSelectionRangeArray& aRangesToDelete,
   2924    AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed,
   2925    const Element& aEditingHost) const {
   2926  MOZ_ASSERT(!aRangesToDelete.IsCollapsed());
   2927 
   2928  if (NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->StartRef().IsSet()) ||
   2929      NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->EndRef().IsSet())) {
   2930    return NS_ERROR_FAILURE;
   2931  }
   2932 
   2933  if (aRangesToDelete.Ranges().Length() == 1) {
   2934    Result<EditorRawDOMRange, nsresult> result = ExtendOrShrinkRangeToDelete(
   2935        aHTMLEditor, aRangesToDelete.LimitersAndCaretDataRef(),
   2936        EditorRawDOMRange(aRangesToDelete.FirstRangeRef()));
   2937    if (MOZ_UNLIKELY(result.isErr())) {
   2938      NS_WARNING(
   2939          "AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete() failed");
   2940      return NS_ERROR_FAILURE;
   2941    }
   2942    EditorRawDOMRange newRange(result.unwrap());
   2943    if (MOZ_UNLIKELY(NS_FAILED(aRangesToDelete.FirstRangeRef()->SetStartAndEnd(
   2944            newRange.StartRef().ToRawRangeBoundary(),
   2945            newRange.EndRef().ToRawRangeBoundary())))) {
   2946      NS_WARNING("nsRange::SetStartAndEnd() failed");
   2947      return NS_ERROR_FAILURE;
   2948    }
   2949    if (MOZ_UNLIKELY(
   2950            NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->IsPositioned()))) {
   2951      return NS_ERROR_FAILURE;
   2952    }
   2953    if (NS_WARN_IF(aRangesToDelete.FirstRangeRef()->Collapsed())) {
   2954      return NS_OK;  // Hmm, there is nothing to delete...?
   2955    }
   2956  }
   2957 
   2958  if (!aHTMLEditor.IsPlaintextMailComposer()) {
   2959    EditorDOMRange firstRange(aRangesToDelete.FirstRangeRef());
   2960    EditorDOMRange extendedRange =
   2961        WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
   2962            {WSRunScanner::Option::OnlyEditableNodes},
   2963            EditorDOMRange(aRangesToDelete.FirstRangeRef()));
   2964    if (firstRange != extendedRange) {
   2965      nsresult rv = aRangesToDelete.FirstRangeRef()->SetStartAndEnd(
   2966          extendedRange.StartRef().ToRawRangeBoundary(),
   2967          extendedRange.EndRef().ToRawRangeBoundary());
   2968      if (NS_FAILED(rv)) {
   2969        NS_WARNING("nsRange::SetStartAndEnd() failed");
   2970        return NS_ERROR_FAILURE;
   2971      }
   2972    }
   2973  }
   2974 
   2975  if (aRangesToDelete.FirstRangeRef()->GetStartContainer() ==
   2976      aRangesToDelete.FirstRangeRef()->GetEndContainer()) {
   2977    if (!aRangesToDelete.FirstRangeRef()->Collapsed()) {
   2978      nsresult rv = ComputeRangesToDeleteRangesWithTransaction(
   2979          aHTMLEditor, aDirectionAndAmount, aRangesToDelete, aEditingHost);
   2980      NS_WARNING_ASSERTION(
   2981          NS_SUCCEEDED(rv),
   2982          "AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction("
   2983          ") failed");
   2984      return rv;
   2985    }
   2986    // `DeleteUnnecessaryNodes()` may delete parent elements, but it does not
   2987    // affect computing target ranges.  Therefore, we don't need to touch
   2988    // aRangesToDelete in this case.
   2989    return NS_OK;
   2990  }
   2991 
   2992  Element* startCiteNode = aHTMLEditor.GetMostDistantAncestorMailCiteElement(
   2993      *aRangesToDelete.FirstRangeRef()->GetStartContainer());
   2994  Element* endCiteNode = aHTMLEditor.GetMostDistantAncestorMailCiteElement(
   2995      *aRangesToDelete.FirstRangeRef()->GetEndContainer());
   2996 
   2997  if (startCiteNode && !endCiteNode) {
   2998    aDirectionAndAmount = nsIEditor::eNext;
   2999  } else if (!startCiteNode && endCiteNode) {
   3000    aDirectionAndAmount = nsIEditor::ePrevious;
   3001  }
   3002 
   3003  for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) {
   3004    if (MOZ_UNLIKELY(range->Collapsed())) {
   3005      continue;
   3006    }
   3007    AutoBlockElementsJoiner joiner(*this);
   3008    if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range,
   3009                                                 aEditingHost)) {
   3010      return NS_ERROR_FAILURE;
   3011    }
   3012    nsresult rv = joiner.ComputeRangeToDelete(
   3013        aHTMLEditor, aRangesToDelete, aDirectionAndAmount, range,
   3014        aSelectionWasCollapsed, aEditingHost);
   3015    if (NS_FAILED(rv)) {
   3016      NS_WARNING("AutoBlockElementsJoiner::ComputeRangeToDelete() failed");
   3017      return rv;
   3018    }
   3019  }
   3020  return NS_OK;
   3021 }
   3022 
   3023 Result<EditActionResult, nsresult>
   3024 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges(
   3025    HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   3026    nsIEditor::EStripWrappers aStripWrappers,
   3027    AutoClonedSelectionRangeArray& aRangesToDelete,
   3028    SelectionWasCollapsed aSelectionWasCollapsed, const Element& aEditingHost) {
   3029  MOZ_ASSERT(aHTMLEditor.IsTopLevelEditSubActionDataAvailable());
   3030  MOZ_ASSERT(!aRangesToDelete.IsCollapsed());
   3031 
   3032  if (NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->StartRef().IsSet()) ||
   3033      NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->EndRef().IsSet())) {
   3034    return Err(NS_ERROR_FAILURE);
   3035  }
   3036 
   3037  MOZ_ASSERT_IF(aRangesToDelete.Ranges().Length() == 1,
   3038                aRangesToDelete.IsFirstRangeEditable(aEditingHost));
   3039 
   3040  // Else we have a non-collapsed selection.  First adjust the selection.
   3041  // XXX Why do we extend selection only when there is only one range?
   3042  if (aRangesToDelete.Ranges().Length() == 1) {
   3043    Result<EditorRawDOMRange, nsresult> result = ExtendOrShrinkRangeToDelete(
   3044        aHTMLEditor, aRangesToDelete.LimitersAndCaretDataRef(),
   3045        EditorRawDOMRange(aRangesToDelete.FirstRangeRef()));
   3046    if (MOZ_UNLIKELY(result.isErr())) {
   3047      NS_WARNING(
   3048          "AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete() failed");
   3049      return Err(NS_ERROR_FAILURE);
   3050    }
   3051    EditorRawDOMRange newRange(result.unwrap());
   3052    if (NS_FAILED(aRangesToDelete.FirstRangeRef()->SetStartAndEnd(
   3053            newRange.StartRef().ToRawRangeBoundary(),
   3054            newRange.EndRef().ToRawRangeBoundary()))) {
   3055      NS_WARNING("nsRange::SetStartAndEnd() failed");
   3056      return Err(NS_ERROR_FAILURE);
   3057    }
   3058    if (NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->IsPositioned())) {
   3059      return Err(NS_ERROR_FAILURE);
   3060    }
   3061    if (NS_WARN_IF(aRangesToDelete.FirstRangeRef()->Collapsed())) {
   3062      // Hmm, there is nothing to delete...?
   3063      // In this case, the callers want collapsed selection.  Therefore, we need
   3064      // to change the `Selection` here.
   3065      nsresult rv = aHTMLEditor.CollapseSelectionTo(
   3066          aRangesToDelete.GetFirstRangeStartPoint<EditorRawDOMPoint>());
   3067      if (NS_FAILED(rv)) {
   3068        NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   3069        return Err(rv);
   3070      }
   3071      return EditActionResult::HandledResult();
   3072    }
   3073    MOZ_ASSERT(aRangesToDelete.IsFirstRangeEditable(aEditingHost));
   3074  }
   3075 
   3076  // Remember that we did a ranged delete for the benefit of AfterEditInner().
   3077  aHTMLEditor.TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange = true;
   3078 
   3079  // Figure out if the endpoints are in nodes that can be merged.  Adjust
   3080  // surrounding white-space in preparation to delete selection.
   3081  if (!aHTMLEditor.IsPlaintextMailComposer()) {
   3082    {
   3083      AutoTrackDOMRange firstRangeTracker(aHTMLEditor.RangeUpdaterRef(),
   3084                                          &aRangesToDelete.FirstRangeRef());
   3085      for (OwningNonNull<nsRange>& range : Reversed(aRangesToDelete.Ranges())) {
   3086        if (MOZ_UNLIKELY(!range->IsPositioned() || range->Collapsed())) {
   3087          continue;
   3088        }
   3089        Maybe<AutoTrackDOMRange> trackRange;
   3090        if (range != aRangesToDelete.FirstRangeRef()) {
   3091          trackRange.emplace(aHTMLEditor.RangeUpdaterRef(), &range);
   3092        }
   3093        Result<EditorDOMRange, nsresult> rangeToDeleteOrError =
   3094            WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToJoin(
   3095                aHTMLEditor, EditorDOMRange(range));
   3096        if (MOZ_UNLIKELY(rangeToDeleteOrError.isErr())) {
   3097          NS_WARNING(
   3098              "WhiteSpaceVisibilityKeeper::"
   3099              "NormalizeSurroundingWhiteSpacesToJoin() failed");
   3100          return rangeToDeleteOrError.propagateErr();
   3101        }
   3102        trackRange.reset();
   3103        EditorDOMRange rangeToDelete = rangeToDeleteOrError.unwrap();
   3104        if (MOZ_LIKELY(rangeToDelete.IsPositionedAndValidInComposedDoc())) {
   3105          nsresult rv = range->SetStartAndEnd(
   3106              rangeToDelete.StartRef().ToRawRangeBoundary(),
   3107              rangeToDelete.EndRef().ToRawRangeBoundary());
   3108          if (NS_FAILED(rv)) {
   3109            NS_WARNING("nsRange::SetStartAndEnd() failed");
   3110            return Err(rv);
   3111          }
   3112        }
   3113      }
   3114    }
   3115    aRangesToDelete.RemoveCollapsedRanges();
   3116    if (MOZ_UNLIKELY(aRangesToDelete.IsCollapsed())) {
   3117      return EditActionResult::HandledResult();
   3118    }
   3119    if (NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->IsPositioned()) ||
   3120        (aHTMLEditor.MaybeNodeRemovalsObservedByDevTools() &&
   3121         NS_WARN_IF(!aRangesToDelete.IsFirstRangeEditable(aEditingHost)))) {
   3122      NS_WARNING(
   3123          "WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() made the first "
   3124          "range invalid");
   3125      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   3126    }
   3127  }
   3128 
   3129  // XXX This is odd.  We do we simply use `DeleteRangesWithTransaction()`
   3130  //     only when **first** range is in same container?
   3131  if (aRangesToDelete.FirstRangeRef()->GetStartContainer() ==
   3132      aRangesToDelete.FirstRangeRef()->GetEndContainer()) {
   3133    // Because of previous DOM tree changes, the range may be collapsed.
   3134    // If we've already removed all contents in the range, we shouldn't
   3135    // delete anything around the caret.
   3136    if (!aRangesToDelete.FirstRangeRef()->Collapsed()) {
   3137      const auto stripWrappers = [&]() -> nsIEditor::EStripWrappers {
   3138        if (mOriginalStripWrappers == nsIEditor::eStrip &&
   3139            aEditingHost.IsContentEditablePlainTextOnly()) {
   3140          return nsIEditor::eNoStrip;
   3141        }
   3142        return mOriginalStripWrappers;
   3143      }();
   3144      AutoTrackDOMRange firstRangeTracker(aHTMLEditor.RangeUpdaterRef(),
   3145                                          &aRangesToDelete.FirstRangeRef());
   3146      Result<CaretPoint, nsresult> caretPointOrError =
   3147          aHTMLEditor.DeleteRangesWithTransaction(
   3148              aDirectionAndAmount, stripWrappers, aRangesToDelete);
   3149      if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   3150        NS_WARNING("HTMLEditor::DeleteRangesWithTransaction() failed");
   3151        return caretPointOrError.propagateErr();
   3152      }
   3153      firstRangeTracker.FlushAndStopTracking();
   3154      nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo(
   3155          aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
   3156                        SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   3157                        SuggestCaret::AndIgnoreTrivialError});
   3158      if (NS_FAILED(rv)) {
   3159        NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
   3160        return Err(rv);
   3161      }
   3162      NS_WARNING_ASSERTION(
   3163          rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   3164          "CaretPoint::SuggestCaretPointTo() failed, but ignored");
   3165      if (NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->IsPositioned()) ||
   3166          (aHTMLEditor.MaybeNodeRemovalsObservedByDevTools() &&
   3167           NS_WARN_IF(!aRangesToDelete.IsFirstRangeEditable(aEditingHost)))) {
   3168        NS_WARNING(
   3169            "HTMLEditor::DeleteRangesWithTransaction() made the first range "
   3170            "invalid");
   3171        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   3172      }
   3173    }
   3174    // However, even if the range is removed, we may need to clean up the
   3175    // containers which become empty.
   3176    EditorDOMRange rangeToCleanUp(aRangesToDelete.FirstRangeRef());
   3177    AutoTrackDOMRange trackRangeToCleanUp(aHTMLEditor.RangeUpdaterRef(),
   3178                                          &rangeToCleanUp);
   3179    nsresult rv =
   3180        DeleteUnnecessaryNodes(aHTMLEditor, rangeToCleanUp, aEditingHost);
   3181    if (NS_FAILED(rv)) {
   3182      NS_WARNING("AutoDeleteRangesHandler::DeleteUnnecessaryNodes() failed");
   3183      return Err(rv);
   3184    }
   3185    trackRangeToCleanUp.FlushAndStopTracking();
   3186    if (NS_WARN_IF(!rangeToCleanUp.IsPositionedAndValidInComposedDoc())) {
   3187      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   3188    }
   3189    const auto& pointToPutCaret =
   3190        !nsIEditor::DirectionIsBackspace(aDirectionAndAmount) ||
   3191                (aHTMLEditor.TopLevelEditSubActionDataRef()
   3192                     .mDidDeleteEmptyParentBlocks &&
   3193                 (aHTMLEditor.GetEditAction() == EditAction::eDrop ||
   3194                  aHTMLEditor.GetEditAction() == EditAction::eDeleteByDrag))
   3195            ? rangeToCleanUp.StartRef()
   3196            : rangeToCleanUp.EndRef();
   3197    rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret);
   3198    if (NS_FAILED(rv)) {
   3199      NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   3200      return Err(rv);
   3201    }
   3202    return EditActionResult::HandledResult();
   3203  }
   3204 
   3205  if (NS_WARN_IF(
   3206          !aRangesToDelete.FirstRangeRef()->GetStartContainer()->IsContent()) ||
   3207      NS_WARN_IF(
   3208          !aRangesToDelete.FirstRangeRef()->GetEndContainer()->IsContent())) {
   3209    return Err(NS_ERROR_FAILURE);
   3210  }
   3211 
   3212  // Figure out mailcite ancestors
   3213  RefPtr<Element> startCiteNode =
   3214      aHTMLEditor.GetMostDistantAncestorMailCiteElement(
   3215          *aRangesToDelete.FirstRangeRef()->GetStartContainer());
   3216  RefPtr<Element> endCiteNode =
   3217      aHTMLEditor.GetMostDistantAncestorMailCiteElement(
   3218          *aRangesToDelete.FirstRangeRef()->GetEndContainer());
   3219 
   3220  // If we only have a mailcite at one of the two endpoints, set the
   3221  // directionality of the deletion so that the selection will end up
   3222  // outside the mailcite.
   3223  if (startCiteNode && !endCiteNode) {
   3224    aDirectionAndAmount = nsIEditor::eNext;
   3225  } else if (!startCiteNode && endCiteNode) {
   3226    aDirectionAndAmount = nsIEditor::ePrevious;
   3227  }
   3228 
   3229  MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
   3230  auto ret = EditActionResult::IgnoredResult();
   3231  for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) {
   3232    if (MOZ_UNLIKELY(range->Collapsed())) {
   3233      continue;
   3234    }
   3235    AutoBlockElementsJoiner joiner(*this);
   3236    if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range,
   3237                                                 aEditingHost)) {
   3238      return Err(NS_ERROR_FAILURE);
   3239    }
   3240    Result<EditActionResult, nsresult> result =
   3241        joiner.Run(aHTMLEditor, aRangesToDelete.LimitersAndCaretDataRef(),
   3242                   aDirectionAndAmount, aStripWrappers, MOZ_KnownLive(range),
   3243                   aSelectionWasCollapsed, aEditingHost);
   3244    if (MOZ_UNLIKELY(result.isErr())) {
   3245      NS_WARNING("AutoBlockElementsJoiner::Run() failed");
   3246      return result;
   3247    }
   3248    ret |= result.inspect();
   3249  }
   3250  return ret;
   3251 }
   3252 
   3253 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   3254    PrepareToDeleteNonCollapsedRange(const HTMLEditor& aHTMLEditor,
   3255                                     const nsRange& aRangeToDelete,
   3256                                     const Element& aEditingHost) {
   3257  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   3258  MOZ_ASSERT(!aRangeToDelete.Collapsed());
   3259 
   3260  mLeftContent = HTMLEditUtils::GetInclusiveAncestorElement(
   3261      *aRangeToDelete.GetStartContainer()->AsContent(),
   3262      HTMLEditUtils::ClosestEditableBlockElement,
   3263      BlockInlineCheck::UseComputedDisplayOutsideStyle);
   3264  mRightContent = HTMLEditUtils::GetInclusiveAncestorElement(
   3265      *aRangeToDelete.GetEndContainer()->AsContent(),
   3266      HTMLEditUtils::ClosestEditableBlockElement,
   3267      BlockInlineCheck::UseComputedDisplayOutsideStyle);
   3268  // Note that mLeftContent and/or mRightContent can be nullptr if editing host
   3269  // is an inline element.  If both editable ancestor block is exactly same
   3270  // one or one reaches an inline editing host, we can just delete the content
   3271  // in ranges.
   3272  if (mLeftContent == mRightContent || !mLeftContent || !mRightContent) {
   3273    MOZ_ASSERT_IF(
   3274        !mLeftContent || !mRightContent,
   3275        aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost() ==
   3276            aRangeToDelete.GetEndContainer()->AsContent()->GetEditingHost());
   3277    mMode = Mode::DeleteContentInRange;
   3278    return true;
   3279  }
   3280 
   3281  // If left block and right block are adjacent siblings and they are same
   3282  // type of elements, we can merge them after deleting the selected contents.
   3283  // MOOSE: this could conceivably screw up a table.. fix me.
   3284  if (mLeftContent->GetParentNode() == mRightContent->GetParentNode() &&
   3285      HTMLEditUtils::CanContentsBeJoined(*mLeftContent, *mRightContent) &&
   3286      // XXX What's special about these three types of block?
   3287      (mLeftContent->IsHTMLElement(nsGkAtoms::p) ||
   3288       HTMLEditUtils::IsListItemElement(*mLeftContent) ||
   3289       HTMLEditUtils::IsHeadingElement(*mLeftContent))) {
   3290    mMode = Mode::JoinBlocksInSameParent;
   3291    return true;
   3292  }
   3293 
   3294  // If the range starts immediately after a line end and ends in a
   3295  // child right block, we should not unwrap the right block unless the
   3296  // right block will have no nodes.
   3297  if (mRightContent->IsInclusiveDescendantOf(mLeftContent)) {
   3298    // FYI: Chrome does not remove the right child block even if there will be
   3299    // only single <br> or a comment node in it.  Therefore, we should use this
   3300    // rough check.
   3301    const WSScanResult nextVisibleThingOfEndBoundary =
   3302        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   3303            {WSRunScanner::Option::OnlyEditableNodes},
   3304            EditorRawDOMPoint(aRangeToDelete.EndRef()));
   3305    if (!nextVisibleThingOfEndBoundary.ReachedCurrentBlockBoundary()) {
   3306      MOZ_ASSERT(mLeftContent->IsElement());
   3307      Result<Element*, nsresult> mostDistantBlockOrError =
   3308          AutoBlockElementsJoiner::
   3309              GetMostDistantBlockAncestorIfPointIsStartAtBlock(
   3310                  EditorRawDOMPoint(mRightContent, 0), aEditingHost,
   3311                  mLeftContent->AsElement());
   3312      MOZ_ASSERT(mostDistantBlockOrError.isOk());
   3313      if (MOZ_LIKELY(mostDistantBlockOrError.inspect())) {
   3314        const WSScanResult prevVisibleThingOfStartBoundary =
   3315            WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   3316                {WSRunScanner::Option::OnlyEditableNodes},
   3317                EditorRawDOMPoint(aRangeToDelete.StartRef()));
   3318        if (prevVisibleThingOfStartBoundary.ReachedBRElement()) {
   3319          // If the range start after a <br> followed by the block boundary,
   3320          // we want to delete the <br> or following <br> element unless it's
   3321          // not a part of empty line like `<div>abc<br>{<div>]def`.
   3322          const WSScanResult nextVisibleThingOfBR =
   3323              WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   3324                  {WSRunScanner::Option::OnlyEditableNodes},
   3325                  EditorRawDOMPoint::After(
   3326                      *prevVisibleThingOfStartBoundary.GetContent()));
   3327          MOZ_ASSERT(!nextVisibleThingOfBR.ReachedCurrentBlockBoundary());
   3328          if (!nextVisibleThingOfBR.ReachedOtherBlockElement() ||
   3329              nextVisibleThingOfBR.GetContent() !=
   3330                  mostDistantBlockOrError.inspect()) {
   3331            // The range selects a non-empty line or a child block at least.
   3332            mMode = Mode::DeletePrecedingLinesAndContentInRange;
   3333            return true;
   3334          }
   3335          const WSScanResult prevVisibleThingOfBR =
   3336              WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   3337                  {WSRunScanner::Option::OnlyEditableNodes},
   3338                  EditorRawDOMPoint(
   3339                      prevVisibleThingOfStartBoundary.GetContent()));
   3340          if (prevVisibleThingOfBR.ReachedBRElement() ||
   3341              prevVisibleThingOfBR.ReachedPreformattedLineBreak() ||
   3342              prevVisibleThingOfBR.ReachedBlockBoundary()) {
   3343            // The preceding <br> causes an empty line.
   3344            mMode = Mode::DeletePrecedingLinesAndContentInRange;
   3345            return true;
   3346          }
   3347        } else if (prevVisibleThingOfStartBoundary
   3348                       .ReachedPreformattedLineBreak()) {
   3349          const WSScanResult nextVisibleThingOfLineBreak =
   3350              WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   3351                  {WSRunScanner::Option::OnlyEditableNodes},
   3352                  prevVisibleThingOfStartBoundary
   3353                      .PointAfterReachedContent<EditorRawDOMPoint>());
   3354          MOZ_ASSERT(
   3355              !nextVisibleThingOfLineBreak.ReachedCurrentBlockBoundary());
   3356          if (!nextVisibleThingOfLineBreak.ReachedOtherBlockElement() ||
   3357              nextVisibleThingOfLineBreak.GetContent() !=
   3358                  mostDistantBlockOrError.inspect()) {
   3359            // The range selects a non-empty line or a child block at least.
   3360            mMode = Mode::DeletePrecedingLinesAndContentInRange;
   3361            return true;
   3362          }
   3363          const WSScanResult prevVisibleThingOfLineBreak =
   3364              WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   3365                  {WSRunScanner::Option::OnlyEditableNodes},
   3366                  prevVisibleThingOfStartBoundary
   3367                      .PointAtReachedContent<EditorRawDOMPoint>());
   3368          if (prevVisibleThingOfLineBreak.ReachedBRElement() ||
   3369              prevVisibleThingOfLineBreak.ReachedPreformattedLineBreak() ||
   3370              prevVisibleThingOfLineBreak.ReachedBlockBoundary()) {
   3371            // The preceding line break causes an empty line.
   3372            mMode = Mode::DeletePrecedingLinesAndContentInRange;
   3373            return true;
   3374          }
   3375        } else if (prevVisibleThingOfStartBoundary
   3376                       .ReachedCurrentBlockBoundary()) {
   3377          MOZ_ASSERT(prevVisibleThingOfStartBoundary.ElementPtr() ==
   3378                     mLeftContent);
   3379          const WSScanResult firstVisibleThingInBlock =
   3380              WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   3381                  {WSRunScanner::Option::OnlyEditableNodes},
   3382                  EditorRawDOMPoint(
   3383                      prevVisibleThingOfStartBoundary.ElementPtr(), 0));
   3384          if (!firstVisibleThingInBlock.ReachedOtherBlockElement() ||
   3385              firstVisibleThingInBlock.ElementPtr() !=
   3386                  mostDistantBlockOrError.inspect()) {
   3387            mMode = Mode::DeletePrecedingLinesAndContentInRange;
   3388            return true;
   3389          }
   3390        } else if (prevVisibleThingOfStartBoundary.ReachedOtherBlockElement()) {
   3391          const WSScanResult firstVisibleThingAfterBlock =
   3392              WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   3393                  {WSRunScanner::Option::OnlyEditableNodes},
   3394                  EditorRawDOMPoint::After(
   3395                      *prevVisibleThingOfStartBoundary.ElementPtr()));
   3396          if (!firstVisibleThingAfterBlock.ReachedOtherBlockElement() ||
   3397              firstVisibleThingAfterBlock.ElementPtr() !=
   3398                  mostDistantBlockOrError.inspect()) {
   3399            mMode = Mode::DeletePrecedingLinesAndContentInRange;
   3400            return true;
   3401          }
   3402        }
   3403      }
   3404    }
   3405  }
   3406 
   3407  mMode = Mode::DeleteNonCollapsedRange;
   3408  return true;
   3409 }
   3410 
   3411 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   3412    ComputeRangeToDeleteContentInRange(
   3413        const HTMLEditor& aHTMLEditor,
   3414        nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete,
   3415        const Element& aEditingHost) const {
   3416  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   3417  MOZ_ASSERT(!aRangeToDelete.Collapsed());
   3418  MOZ_ASSERT(mMode == Mode::DeleteContentInRange);
   3419  MOZ_ASSERT(aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost());
   3420  MOZ_ASSERT(
   3421      aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost() ==
   3422      aRangeToDelete.GetEndContainer()->AsContent()->GetEditingHost());
   3423  MOZ_ASSERT(!mLeftContent == !mRightContent);
   3424  MOZ_ASSERT_IF(mLeftContent, mLeftContent->IsElement());
   3425  MOZ_ASSERT_IF(mLeftContent,
   3426                aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf(
   3427                    mLeftContent));
   3428  MOZ_ASSERT_IF(mRightContent, mRightContent->IsElement());
   3429  MOZ_ASSERT_IF(
   3430      mRightContent,
   3431      aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent));
   3432  MOZ_ASSERT_IF(
   3433      !mLeftContent,
   3434      HTMLEditUtils::IsInlineContent(
   3435          *aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost(),
   3436          BlockInlineCheck::UseComputedDisplayOutsideStyle));
   3437 
   3438  nsresult rv =
   3439      mDeleteRangesHandlerConst.ComputeRangeToDeleteRangeWithTransaction(
   3440          aHTMLEditor, aDirectionAndAmount, aRangeToDelete, aEditingHost);
   3441  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3442                       "AutoDeleteRangesHandler::"
   3443                       "ComputeRangeToDeleteRangeWithTransaction() failed");
   3444  return rv;
   3445 }
   3446 
   3447 Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   3448    AutoBlockElementsJoiner::DeleteContentInRange(
   3449        HTMLEditor& aHTMLEditor,
   3450        const LimitersAndCaretData& aLimitersAndCaretData,
   3451        nsIEditor::EDirection aDirectionAndAmount,
   3452        nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete,
   3453        const Element& aEditingHost) {
   3454  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   3455  MOZ_ASSERT(!aRangeToDelete.Collapsed());
   3456  MOZ_ASSERT(mMode == Mode::DeleteContentInRange);
   3457  MOZ_ASSERT(mDeleteRangesHandler);
   3458  MOZ_ASSERT(aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost());
   3459  MOZ_ASSERT(
   3460      aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost() ==
   3461      aRangeToDelete.GetEndContainer()->AsContent()->GetEditingHost());
   3462  MOZ_ASSERT_IF(mLeftContent, mLeftContent->IsElement());
   3463  MOZ_ASSERT_IF(mLeftContent,
   3464                aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf(
   3465                    mLeftContent));
   3466  MOZ_ASSERT_IF(mRightContent, mRightContent->IsElement());
   3467  MOZ_ASSERT_IF(
   3468      mRightContent,
   3469      aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent));
   3470  MOZ_ASSERT_IF(
   3471      !mLeftContent,
   3472      HTMLEditUtils::IsInlineContent(
   3473          *aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost(),
   3474          BlockInlineCheck::UseComputedDisplayOutsideStyle));
   3475 
   3476  const OwningNonNull<nsRange> rangeToDelete(aRangeToDelete);
   3477  Result<EditorDOMRange, nsresult> rangeToDeleteOrError =
   3478      WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToJoin(
   3479          aHTMLEditor, EditorDOMRange(rangeToDelete));
   3480  if (MOZ_UNLIKELY(rangeToDeleteOrError.isErr())) {
   3481    NS_WARNING(
   3482        "WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToJoin() "
   3483        "failed");
   3484    return rangeToDeleteOrError.propagateErr();
   3485  }
   3486  nsresult rv = rangeToDeleteOrError.unwrap().SetToRange(rangeToDelete);
   3487  if (NS_FAILED(rv)) {
   3488    NS_WARNING("EditorDOMRange::SetToRange() failed");
   3489    return Err(rv);
   3490  }
   3491  if (!rangeToDelete->Collapsed()) {
   3492    AutoClonedSelectionRangeArray rangesToDelete(*rangeToDelete,
   3493                                                 aLimitersAndCaretData);
   3494    AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(),
   3495                                         &rangeToDelete);
   3496    Result<CaretPoint, nsresult> caretPointOrError =
   3497        aHTMLEditor.DeleteRangesWithTransaction(aDirectionAndAmount,
   3498                                                aStripWrappers, rangesToDelete);
   3499    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   3500      if (NS_WARN_IF(caretPointOrError.inspectErr() ==
   3501                     NS_ERROR_EDITOR_DESTROYED)) {
   3502        return Err(NS_ERROR_EDITOR_DESTROYED);
   3503      }
   3504      NS_WARNING(
   3505          "HTMLEditor::DeleteRangesWithTransaction() failed, but ignored");
   3506    } else {
   3507      nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo(
   3508          aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
   3509                        SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   3510                        SuggestCaret::AndIgnoreTrivialError});
   3511      if (NS_FAILED(rv)) {
   3512        NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
   3513        return Err(rv);
   3514      }
   3515      NS_WARNING_ASSERTION(
   3516          rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   3517          "CaretPoint::SuggestCaretPointTo() failed, but ignored");
   3518    }
   3519  }
   3520 
   3521  if (NS_WARN_IF(!rangeToDelete->IsPositioned())) {
   3522    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   3523  }
   3524 
   3525  EditorDOMRange rangeToCleanUp(*rangeToDelete);
   3526  {
   3527    AutoTrackDOMRange trackRangeToCleanUp(aHTMLEditor.RangeUpdaterRef(),
   3528                                          &rangeToCleanUp);
   3529    nsresult rv = mDeleteRangesHandler->DeleteUnnecessaryNodes(
   3530        aHTMLEditor, rangeToCleanUp, aEditingHost);
   3531    if (NS_FAILED(rv)) {
   3532      NS_WARNING("AutoDeleteRangesHandler::DeleteUnnecessaryNodes() failed");
   3533      return Err(rv);
   3534    }
   3535  }
   3536  const auto& pointToPutCaret =
   3537      !nsIEditor::DirectionIsBackspace(aDirectionAndAmount) ||
   3538              (aHTMLEditor.TopLevelEditSubActionDataRef()
   3539                   .mDidDeleteEmptyParentBlocks &&
   3540               (aHTMLEditor.GetEditAction() == EditAction::eDrop ||
   3541                aHTMLEditor.GetEditAction() == EditAction::eDeleteByDrag))
   3542          ? rangeToCleanUp.StartRef()
   3543          : rangeToCleanUp.EndRef();
   3544  rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret);
   3545  if (NS_FAILED(rv)) {
   3546    NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   3547    return Err(rv);
   3548  }
   3549  return EditActionResult::HandledResult();
   3550 }
   3551 
   3552 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   3553    ComputeRangeToJoinBlockElementsInSameParent(
   3554        const HTMLEditor& aHTMLEditor,
   3555        nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete,
   3556        const Element& aEditingHost) const {
   3557  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   3558  MOZ_ASSERT(!aRangeToDelete.Collapsed());
   3559  MOZ_ASSERT(mMode == Mode::JoinBlocksInSameParent);
   3560  MOZ_ASSERT(mLeftContent);
   3561  MOZ_ASSERT(mLeftContent->IsElement());
   3562  MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf(
   3563      mLeftContent));
   3564  MOZ_ASSERT(mRightContent);
   3565  MOZ_ASSERT(mRightContent->IsElement());
   3566  MOZ_ASSERT(
   3567      aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent));
   3568  MOZ_ASSERT(mLeftContent->GetParentNode() == mRightContent->GetParentNode());
   3569 
   3570  nsresult rv =
   3571      mDeleteRangesHandlerConst.ComputeRangeToDeleteRangeWithTransaction(
   3572          aHTMLEditor, aDirectionAndAmount, aRangeToDelete, aEditingHost);
   3573  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   3574                       "AutoDeleteRangesHandler::"
   3575                       "ComputeRangeToDeleteRangeWithTransaction() failed");
   3576  return rv;
   3577 }
   3578 
   3579 Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   3580    AutoBlockElementsJoiner::JoinBlockElementsInSameParent(
   3581        HTMLEditor& aHTMLEditor,
   3582        const LimitersAndCaretData& aLimitersAndCaretData,
   3583        nsIEditor::EDirection aDirectionAndAmount,
   3584        nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete,
   3585        SelectionWasCollapsed aSelectionWasCollapsed,
   3586        const Element& aEditingHost) {
   3587  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   3588  MOZ_ASSERT(!aRangeToDelete.Collapsed());
   3589  MOZ_ASSERT(mMode == Mode::JoinBlocksInSameParent);
   3590  MOZ_ASSERT(mLeftContent);
   3591  MOZ_ASSERT(mLeftContent->IsElement());
   3592  MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf(
   3593      mLeftContent));
   3594  MOZ_ASSERT(mRightContent);
   3595  MOZ_ASSERT(mRightContent->IsElement());
   3596  MOZ_ASSERT(
   3597      aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent));
   3598  MOZ_ASSERT(mLeftContent->GetParentNode() == mRightContent->GetParentNode());
   3599 
   3600  const bool backspaceInRightBlock =
   3601      aSelectionWasCollapsed == SelectionWasCollapsed::Yes &&
   3602      nsIEditor::DirectionIsBackspace(aDirectionAndAmount);
   3603 
   3604  const OwningNonNull<nsRange> rangeToDelete(aRangeToDelete);
   3605 
   3606  // If mLeftContent ends with an invisible line break, we should delete it
   3607  // before joining the blocks because that will appear as a visible line break
   3608  // if JoinNodesDeepWithTransaction() moves the first line content of
   3609  // mRightContent to end of the line break in mLeftContent.
   3610  if (HTMLEditUtils::IsBlockElement(
   3611          *mLeftContent, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
   3612    const WSScanResult lastThingInLeftBlock =
   3613        WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   3614            {}, EditorRawDOMPoint::AtEndOf(*mLeftContent));
   3615    if (lastThingInLeftBlock.ReachedLineBreak()) {
   3616      const EditorLineBreak lineBreak =
   3617          lastThingInLeftBlock.CreateEditorLineBreak<EditorLineBreak>();
   3618      // FIXME: If the line break is wrapped in an non-editable inline element,
   3619      // we should delete its parent too or should fail.
   3620      Result<EditorDOMPoint, nsresult> exLineBreakPointOrError =
   3621          aHTMLEditor.DeleteLineBreakWithTransaction(
   3622              lineBreak, nsIEditor::eNoStrip, aEditingHost);
   3623      if (MOZ_UNLIKELY(exLineBreakPointOrError.isErr())) {
   3624        NS_WARNING("HTMLEditor::DeleteLineBreakWithTransaction() failed");
   3625        return exLineBreakPointOrError.propagateErr();
   3626      }
   3627    }
   3628  }
   3629 
   3630  // Then, normalizer white-spaces at end of mLeftContent and at start of
   3631  // mRightContent to keep their visibility.
   3632  Result<EditorDOMRange, nsresult> rangeToDeleteOrError =
   3633      WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToJoin(
   3634          aHTMLEditor, EditorDOMRange(*rangeToDelete));
   3635  if (MOZ_UNLIKELY(rangeToDeleteOrError.isErr())) {
   3636    NS_WARNING(
   3637        "WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToJoin() "
   3638        "failed");
   3639    return rangeToDeleteOrError.propagateErr();
   3640  }
   3641  nsresult rv = rangeToDeleteOrError.unwrap().SetToRange(rangeToDelete);
   3642  if (NS_FAILED(rv)) {
   3643    NS_WARNING("EditorDOMRange::SetToRange() failed");
   3644    return Err(rv);
   3645  }
   3646 
   3647  // Finally, delete the selected content and move first line of mRightContent
   3648  // to end of mLeftContent.
   3649  if (!rangeToDelete->Collapsed()) {
   3650    AutoClonedSelectionRangeArray rangesToDelete(rangeToDelete,
   3651                                                 aLimitersAndCaretData);
   3652    Result<CaretPoint, nsresult> caretPointOrError =
   3653        aHTMLEditor.DeleteRangesWithTransaction(aDirectionAndAmount,
   3654                                                aStripWrappers, rangesToDelete);
   3655    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   3656      NS_WARNING("HTMLEditor::DeleteRangesWithTransaction() failed");
   3657      return caretPointOrError.propagateErr();
   3658    }
   3659 
   3660    nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo(
   3661        aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
   3662                      SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   3663                      SuggestCaret::AndIgnoreTrivialError});
   3664    if (NS_FAILED(rv)) {
   3665      NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
   3666      return Err(rv);
   3667    }
   3668    NS_WARNING_ASSERTION(
   3669        rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   3670        "CaretPoint::SuggestCaretPointTo() failed, but ignored");
   3671  }
   3672 
   3673  if (NS_WARN_IF(!mLeftContent->GetParentNode()) ||
   3674      NS_WARN_IF(!mRightContent->GetParentNode()) ||
   3675      NS_WARN_IF(mLeftContent->GetParentNode() !=
   3676                 mRightContent->GetParentNode())) {
   3677    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   3678  }
   3679 
   3680  auto startOfRightContent =
   3681      HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
   3682          *mRightContent, {EditablePointOption::RecognizeInvisibleWhiteSpaces,
   3683                           EditablePointOption::StopAtComment});
   3684  AutoTrackDOMPoint trackStartOfRightContent(aHTMLEditor.RangeUpdaterRef(),
   3685                                             &startOfRightContent);
   3686  Result<EditorDOMPoint, nsresult> atFirstChildOfTheLastRightNodeOrError =
   3687      JoinNodesDeepWithTransaction(aHTMLEditor, MOZ_KnownLive(*mLeftContent),
   3688                                   MOZ_KnownLive(*mRightContent));
   3689  if (MOZ_UNLIKELY(atFirstChildOfTheLastRightNodeOrError.isErr())) {
   3690    NS_WARNING("HTMLEditor::JoinNodesDeepWithTransaction() failed");
   3691    return atFirstChildOfTheLastRightNodeOrError.propagateErr();
   3692  }
   3693  MOZ_ASSERT(atFirstChildOfTheLastRightNodeOrError.inspect().IsSet());
   3694  trackStartOfRightContent.FlushAndStopTracking();
   3695  if (NS_WARN_IF(!startOfRightContent.IsSet()) ||
   3696      NS_WARN_IF(!startOfRightContent.GetContainer()->IsInComposedDoc())) {
   3697    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   3698  }
   3699 
   3700  // If we're deleting selection (not replacing with new content) and the joined
   3701  // point follows a text node, we should put caret to end of the preceding text
   3702  // node because the other browsers insert following inputs into there.
   3703  if (MayEditActionDeleteAroundCollapsedSelection(
   3704          aHTMLEditor.GetEditAction())) {
   3705    const WSScanResult maybePreviousText =
   3706        WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   3707            {}, startOfRightContent, &aEditingHost);
   3708    if (maybePreviousText.IsContentEditable() &&
   3709        maybePreviousText.InVisibleOrCollapsibleCharacters()) {
   3710      nsresult rv = aHTMLEditor.CollapseSelectionTo(
   3711          maybePreviousText.PointAfterReachedContent<EditorRawDOMPoint>());
   3712      if (NS_FAILED(rv)) {
   3713        NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   3714        return Err(rv);
   3715      }
   3716      // If we prefer to use style in the previous line, we should forget
   3717      // previous styles since the caret position has all styles which we want
   3718      // to use with new content.
   3719      if (backspaceInRightBlock) {
   3720        aHTMLEditor.TopLevelEditSubActionDataRef()
   3721            .mCachedPendingStyles->Clear();
   3722      }
   3723      // And we don't want to keep extending a link at ex-end of the previous
   3724      // paragraph.
   3725      if (HTMLEditor::GetLinkElement(maybePreviousText.TextPtr())) {
   3726        aHTMLEditor.mPendingStylesToApplyToNewContent
   3727            ->ClearLinkAndItsSpecifiedStyle();
   3728      }
   3729      return EditActionResult::HandledResult();
   3730    }
   3731  }
   3732 
   3733  // Otherwise, we should put caret at start of the right content.
   3734  rv = aHTMLEditor.CollapseSelectionTo(
   3735      atFirstChildOfTheLastRightNodeOrError.inspect());
   3736  if (NS_FAILED(rv)) {
   3737    NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   3738    return Err(rv);
   3739  }
   3740  return EditActionResult::HandledResult();
   3741 }
   3742 
   3743 Result<bool, nsresult>
   3744 HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   3745    ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure(
   3746        const HTMLEditor& aHTMLEditor, nsRange& aRange,
   3747        AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed)
   3748        const {
   3749  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   3750 
   3751  AutoTArray<OwningNonNull<nsIContent>, 10> arrayOfTopChildren;
   3752  DOMSubtreeIterator iter;
   3753  nsresult rv = iter.Init(aRange);
   3754  if (NS_FAILED(rv)) {
   3755    NS_WARNING("DOMSubtreeIterator::Init() failed");
   3756    return Err(rv);
   3757  }
   3758  iter.AppendAllNodesToArray(arrayOfTopChildren);
   3759  return NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
   3760      aHTMLEditor, arrayOfTopChildren, aSelectionWasCollapsed);
   3761 }
   3762 
   3763 Result<DeleteRangeResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   3764    AutoBlockElementsJoiner::DeleteNodesEntirelyInRangeButKeepTableStructure(
   3765        HTMLEditor& aHTMLEditor,
   3766        const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContent,
   3767        PutCaretTo aPutCaretTo) {
   3768  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   3769 
   3770  DeleteRangeResult deleteContentResult = DeleteRangeResult::IgnoredResult();
   3771  for (const auto& content : aArrayOfContent) {
   3772    // XXX After here, the child contents in the array may have been moved
   3773    //     to somewhere or removed.  We should handle it.
   3774    //
   3775    // MOZ_KnownLive because 'aArrayOfContent' is guaranteed to
   3776    // keep it alive.
   3777    AutoTrackDOMDeleteRangeResult trackDeleteContentResult(
   3778        aHTMLEditor.RangeUpdaterRef(), &deleteContentResult);
   3779    Result<DeleteRangeResult, nsresult> deleteResult =
   3780        DeleteContentButKeepTableStructure(aHTMLEditor, MOZ_KnownLive(content));
   3781    if (MOZ_UNLIKELY(deleteResult.isErr())) {
   3782      if (NS_WARN_IF(deleteResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
   3783        return Err(NS_ERROR_EDITOR_DESTROYED);
   3784      }
   3785      NS_WARNING(
   3786          "AutoBlockElementsJoiner::DeleteContentButKeepTableStructure() "
   3787          "failed, but ignored");
   3788      continue;
   3789    }
   3790    trackDeleteContentResult.FlushAndStopTracking();
   3791    deleteContentResult |= deleteResult.unwrap();
   3792  }
   3793  if (deleteContentResult.Handled()) {
   3794    EditorDOMPoint pointToPutCaret =
   3795        aPutCaretTo == PutCaretTo::StartOfRange
   3796            ? deleteContentResult.DeleteRangeRef().StartRef()
   3797            : deleteContentResult.DeleteRangeRef().EndRef();
   3798    deleteContentResult |= CaretPoint(std::move(pointToPutCaret));
   3799  }
   3800  return std::move(deleteContentResult);
   3801 }
   3802 
   3803 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   3804    NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
   3805        const HTMLEditor& aHTMLEditor,
   3806        const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
   3807        AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed)
   3808        const {
   3809  switch (mMode) {
   3810    case Mode::DeletePrecedingLinesAndContentInRange:
   3811    case Mode::DeleteBRElement:
   3812    case Mode::DeletePrecedingBRElementOfBlock:
   3813    case Mode::DeletePrecedingPreformattedLineBreak:
   3814      return false;
   3815    default:
   3816      break;
   3817  }
   3818 
   3819  // If original selection was collapsed, we need always to join the nodes.
   3820  // XXX Why?
   3821  if (aSelectionWasCollapsed ==
   3822      AutoDeleteRangesHandler::SelectionWasCollapsed::No) {
   3823    return true;
   3824  }
   3825  // If something visible is deleted, no need to join.  Visible means
   3826  // all nodes except non-visible textnodes and breaks.
   3827  if (aArrayOfContents.IsEmpty()) {
   3828    return true;
   3829  }
   3830  for (const OwningNonNull<nsIContent>& content : aArrayOfContents) {
   3831    if (content->IsText()) {
   3832      if (HTMLEditUtils::IsInVisibleTextFrames(aHTMLEditor.GetPresContext(),
   3833                                               *content->AsText())) {
   3834        return false;
   3835      }
   3836      continue;
   3837    }
   3838    // XXX If it's an element node, we should check whether it has visible
   3839    //     frames or not.
   3840    if (!content->IsElement() ||
   3841        HTMLEditUtils::IsEmptyNode(
   3842            *content->AsElement(),
   3843            {EmptyCheckOption::TreatSingleBRElementAsVisible,
   3844             EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
   3845      continue;
   3846    }
   3847    if (!HTMLEditUtils::IsInvisibleBRElement(*content)) {
   3848      return false;
   3849    }
   3850  }
   3851  return true;
   3852 }
   3853 
   3854 Result<DeleteRangeResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   3855    AutoBlockElementsJoiner::DeleteTextAtStartAndEndOfRange(
   3856        HTMLEditor& aHTMLEditor, nsRange& aRange, PutCaretTo aPutCaretTo) {
   3857  if (MOZ_UNLIKELY(aRange.Collapsed())) {
   3858    return DeleteRangeResult::IgnoredResult();
   3859  }
   3860 
   3861  const auto DeleteTextNode =
   3862      [&aHTMLEditor](const OwningNonNull<Text>& aTextNode)
   3863          MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
   3864      -> Result<DeleteRangeResult, nsresult> {
   3865    const nsCOMPtr<nsINode> parentNode = aTextNode->GetParentNode();
   3866    if (NS_WARN_IF(!parentNode)) {
   3867      return Err(NS_ERROR_FAILURE);
   3868    }
   3869    const nsCOMPtr<nsIContent> nextSibling = aTextNode->GetNextSibling();
   3870    nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aTextNode);
   3871    if (NS_FAILED(rv)) {
   3872      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   3873      return Err(rv);
   3874    }
   3875    if (NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parentNode) ||
   3876        NS_WARN_IF(!parentNode->IsInComposedDoc())) {
   3877      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   3878    }
   3879    const auto atRemovedTextNode = nextSibling
   3880                                       ? EditorDOMPoint(nextSibling)
   3881                                       : EditorDOMPoint::AtEndOf(*parentNode);
   3882    return DeleteRangeResult(EditorDOMRange(atRemovedTextNode),
   3883                             atRemovedTextNode);
   3884  };
   3885 
   3886  EditorDOMRange range(aRange);
   3887  // If the range is in a text node, delete middle of the text or the text node
   3888  // itself.
   3889  if (range.StartRef().IsInTextNode() && range.InSameContainer()) {
   3890    const OwningNonNull<Text> textNode = *range.StartRef().ContainerAs<Text>();
   3891    if (range.StartRef().IsStartOfContainer() &&
   3892        range.EndRef().IsEndOfContainer()) {
   3893      Result<DeleteRangeResult, nsresult> deleteTextNodeResult =
   3894          DeleteTextNode(textNode);
   3895      NS_WARNING_ASSERTION(
   3896          deleteTextNodeResult.isOk(),
   3897          "DeleteTextNode() failed to delete the selected Text node");
   3898      return deleteTextNodeResult;
   3899    }
   3900    MOZ_ASSERT(range.EndRef().Offset() - range.StartRef().Offset() > 0);
   3901    Result<CaretPoint, nsresult> caretPointOrError =
   3902        aHTMLEditor.DeleteTextWithTransaction(
   3903            textNode, range.StartRef().Offset(),
   3904            range.EndRef().Offset() - range.StartRef().Offset());
   3905    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   3906      NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
   3907      return caretPointOrError.propagateErr();
   3908    }
   3909    const EditorDOMPoint atRemovedText =
   3910        caretPointOrError.unwrap().UnwrapCaretPoint();
   3911    if (NS_WARN_IF(!atRemovedText.IsSetAndValidInComposedDoc())) {
   3912      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   3913    }
   3914    return DeleteRangeResult(EditorDOMRange(atRemovedText), atRemovedText);
   3915  }
   3916 
   3917  // If the range starts in a text node and ends in a different node, delete
   3918  // the text after the start boundary.
   3919  auto deleteStartTextResultOrError =
   3920      [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
   3921      -> Result<DeleteRangeResult, nsresult> {
   3922    if (!range.StartRef().IsInTextNode() ||
   3923        range.StartRef().IsEndOfContainer()) {
   3924      return DeleteRangeResult::IgnoredResult();
   3925    }
   3926    AutoTrackDOMRange trackRange(aHTMLEditor.RangeUpdaterRef(), &range);
   3927    const OwningNonNull<Text> textNode = *range.StartRef().ContainerAs<Text>();
   3928    if (range.StartRef().IsStartOfContainer()) {
   3929      Result<DeleteRangeResult, nsresult> deleteTextNodeResult =
   3930          DeleteTextNode(textNode);
   3931      NS_WARNING_ASSERTION(
   3932          deleteTextNodeResult.isOk(),
   3933          "DeleteTextNode() failed to delete the start Text node");
   3934      return deleteTextNodeResult;
   3935    }
   3936    Result<CaretPoint, nsresult> caretPointOrError =
   3937        aHTMLEditor.DeleteTextWithTransaction(
   3938            textNode, range.StartRef().Offset(),
   3939            textNode->TextDataLength() - range.StartRef().Offset());
   3940    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   3941      NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
   3942      return caretPointOrError.propagateErr();
   3943    }
   3944    trackRange.FlushAndStopTracking();
   3945    const EditorDOMPoint atRemovedText =
   3946        caretPointOrError.unwrap().UnwrapCaretPoint();
   3947    if (NS_WARN_IF(!atRemovedText.IsSetAndValidInComposedDoc())) {
   3948      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   3949    }
   3950    return DeleteRangeResult(EditorDOMRange(atRemovedText), atRemovedText);
   3951  }();
   3952  if (MOZ_UNLIKELY(deleteStartTextResultOrError.isErr())) {
   3953    return deleteStartTextResultOrError.propagateErr();
   3954  }
   3955  DeleteRangeResult deleteStartTextResult =
   3956      deleteStartTextResultOrError.unwrap();
   3957 
   3958  // If the range ends in a text node and starts from a different node, delete
   3959  // the text before the end boundary.
   3960  auto deleteEndTextResultOrError =
   3961      [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
   3962      -> Result<DeleteRangeResult, nsresult> {
   3963    if (!range.EndRef().IsInTextNode() || range.EndRef().IsStartOfContainer()) {
   3964      return DeleteRangeResult::IgnoredResult();
   3965    }
   3966    AutoTrackDOMRange trackRange(aHTMLEditor.RangeUpdaterRef(), &range);
   3967    AutoTrackDOMDeleteRangeResult trackDeleteStartTextResult(
   3968        aHTMLEditor.RangeUpdaterRef(), &deleteStartTextResult);
   3969    const OwningNonNull<Text> textNode = *range.EndRef().ContainerAs<Text>();
   3970    if (range.EndRef().IsEndOfContainer()) {
   3971      Result<DeleteRangeResult, nsresult> deleteTextNodeResult =
   3972          DeleteTextNode(textNode);
   3973      NS_WARNING_ASSERTION(
   3974          deleteTextNodeResult.isOk(),
   3975          "DeleteTextNode() failed to delete the end Text node");
   3976      return deleteTextNodeResult;
   3977    }
   3978    Result<CaretPoint, nsresult> caretPointOrError =
   3979        aHTMLEditor.DeleteTextWithTransaction(textNode, 0,
   3980                                              range.EndRef().Offset());
   3981    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   3982      NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
   3983      return caretPointOrError.propagateErr();
   3984    }
   3985    trackRange.FlushAndStopTracking();
   3986    const EditorDOMPoint atRemovedText =
   3987        caretPointOrError.unwrap().UnwrapCaretPoint();
   3988    if (NS_WARN_IF(!atRemovedText.IsSetAndValidInComposedDoc())) {
   3989      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   3990    }
   3991    return DeleteRangeResult(EditorDOMRange(atRemovedText), atRemovedText);
   3992  }();
   3993  if (MOZ_UNLIKELY(deleteEndTextResultOrError.isErr())) {
   3994    return deleteEndTextResultOrError.propagateErr();
   3995  }
   3996  DeleteRangeResult deleteEndTextResult = deleteEndTextResultOrError.unwrap();
   3997 
   3998  if (!deleteStartTextResult.Handled() && !deleteEndTextResult.Handled()) {
   3999    deleteStartTextResult.IgnoreCaretPointSuggestion();
   4000    deleteEndTextResult.IgnoreCaretPointSuggestion();
   4001    return DeleteRangeResult::IgnoredResult();
   4002  }
   4003 
   4004  EditorDOMPoint pointToPutCaret =
   4005      aPutCaretTo == PutCaretTo::EndOfRange
   4006          ? (deleteEndTextResult.Handled()
   4007                 ? deleteEndTextResult.UnwrapCaretPoint()
   4008                 : EditorDOMPoint())
   4009          : (deleteStartTextResult.Handled()
   4010                 ? deleteStartTextResult.UnwrapCaretPoint()
   4011                 : EditorDOMPoint());
   4012  deleteStartTextResult |= deleteEndTextResult;
   4013  deleteStartTextResult.ForgetCaretPointSuggestion();
   4014  if (pointToPutCaret.IsSet()) {
   4015    deleteStartTextResult |= CaretPoint(std::move(pointToPutCaret));
   4016  }
   4017  return std::move(deleteStartTextResult);
   4018 }
   4019 
   4020 // static
   4021 template <typename EditorDOMPointType>
   4022 Result<Element*, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   4023    AutoBlockElementsJoiner::GetMostDistantBlockAncestorIfPointIsStartAtBlock(
   4024        const EditorDOMPointType& aPoint, const Element& aEditingHost,
   4025        const Element* aAncestorLimiter /* = nullptr */) {
   4026  MOZ_ASSERT(aPoint.IsSetAndValid());
   4027  MOZ_ASSERT(aPoint.IsInComposedDoc());
   4028 
   4029  if (!aAncestorLimiter) {
   4030    aAncestorLimiter = &aEditingHost;
   4031  }
   4032 
   4033  const auto ReachedCurrentBlockBoundaryWhichWeCanCross =
   4034      [&aEditingHost, aAncestorLimiter](const WSScanResult& aScanResult) {
   4035        // When the scan result is "reached current block boundary", it may not
   4036        // be so literally.
   4037        return aScanResult.ReachedCurrentBlockBoundary() &&
   4038               HTMLEditUtils::IsRemovableFromParentNode(
   4039                   *aScanResult.ElementPtr()) &&
   4040               aScanResult.ElementPtr() != &aEditingHost &&
   4041               aScanResult.ElementPtr() != aAncestorLimiter &&
   4042               // Don't cross <body>, <head> and <html>
   4043               !aScanResult.ElementPtr()->IsAnyOfHTMLElements(
   4044                   nsGkAtoms::body, nsGkAtoms::head, nsGkAtoms::html) &&
   4045               // Don't cross table elements
   4046               !HTMLEditUtils::IsAnyTableElementExceptColumnElement(
   4047                   *aScanResult.ElementPtr());
   4048      };
   4049 
   4050  const WSScanResult prevVisibleThing =
   4051      WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   4052          {WSRunScanner::Option::OnlyEditableNodes}, aPoint, aAncestorLimiter);
   4053  if (!ReachedCurrentBlockBoundaryWhichWeCanCross(prevVisibleThing)) {
   4054    return nullptr;
   4055  }
   4056  MOZ_ASSERT(
   4057      HTMLEditUtils::IsBlockElement(*prevVisibleThing.ElementPtr(),
   4058                                    BlockInlineCheck::UseComputedDisplayStyle));
   4059  for (Element* ancestorBlock = prevVisibleThing.ElementPtr(); ancestorBlock;) {
   4060    const WSScanResult prevVisibleThing =
   4061        WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   4062            {WSRunScanner::Option::OnlyEditableNodes},
   4063            EditorRawDOMPoint(ancestorBlock), aAncestorLimiter);
   4064    if (!ReachedCurrentBlockBoundaryWhichWeCanCross(prevVisibleThing)) {
   4065      return ancestorBlock;
   4066    }
   4067    MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
   4068        *prevVisibleThing.ElementPtr(),
   4069        BlockInlineCheck::UseComputedDisplayStyle));
   4070    ancestorBlock = prevVisibleThing.ElementPtr();
   4071  }
   4072  return Err(NS_ERROR_FAILURE);
   4073 }
   4074 
   4075 void HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   4076    ExtendRangeToDeleteNonCollapsedRange(
   4077        const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete,
   4078        const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const {
   4079  MOZ_ASSERT_IF(aComputeRangeFor == ComputeRangeFor::GetTargetRanges,
   4080                aRangeToDelete.IsPositioned());
   4081  MOZ_ASSERT(!aRangeToDelete.Collapsed());
   4082  MOZ_ASSERT(mLeftContent);
   4083  MOZ_ASSERT(mLeftContent->IsElement());
   4084  MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf(
   4085      mLeftContent));
   4086  MOZ_ASSERT(mRightContent);
   4087  MOZ_ASSERT(mRightContent->IsElement());
   4088  MOZ_ASSERT(
   4089      aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent));
   4090 
   4091  const DebugOnly<Result<bool, nsresult>> extendRangeResult =
   4092      AutoDeleteRangesHandler::
   4093          ExtendRangeToContainAncestorInlineElementsAtStart(aRangeToDelete,
   4094                                                            aEditingHost);
   4095  NS_WARNING_ASSERTION(extendRangeResult.value.isOk(),
   4096                       "AutoDeleteRangesHandler::"
   4097                       "ExtendRangeToContainAncestorInlineElementsAtStart() "
   4098                       "failed, but ignored");
   4099  if (mMode != Mode::DeletePrecedingLinesAndContentInRange) {
   4100    return;
   4101  }
   4102 
   4103  // If we're computing for beforeinput.getTargetRanges() and the inputType
   4104  // is not a simple deletion like replacing selected content with new
   4105  // content, the range should end at the original end boundary of the given
   4106  // range even if we're deleting only preceding lines of the right child
   4107  // block.
   4108  const bool preserveEndBoundary =
   4109      aComputeRangeFor == ComputeRangeFor::GetTargetRanges &&
   4110      !MayEditActionDeleteAroundCollapsedSelection(aHTMLEditor.GetEditAction());
   4111  // We need to delete only the preceding lines of the right block. Therefore,
   4112  // we need to shrink the range to ends before the right block if the range
   4113  // does not contain any meaningful content in the right block.
   4114  const Result<Element*, nsresult> inclusiveAncestorCurrentBlockOrError =
   4115      AutoBlockElementsJoiner::GetMostDistantBlockAncestorIfPointIsStartAtBlock(
   4116          EditorRawDOMPoint(aRangeToDelete.EndRef()), aEditingHost,
   4117          mLeftContent->AsElement());
   4118  MOZ_ASSERT(inclusiveAncestorCurrentBlockOrError.isOk());
   4119  MOZ_ASSERT_IF(inclusiveAncestorCurrentBlockOrError.inspect(),
   4120                mRightContent->IsInclusiveDescendantOf(
   4121                    inclusiveAncestorCurrentBlockOrError.inspect()));
   4122  if (MOZ_UNLIKELY(!inclusiveAncestorCurrentBlockOrError.isOk() ||
   4123                   !inclusiveAncestorCurrentBlockOrError.inspect())) {
   4124    return;
   4125  }
   4126 
   4127  const WSScanResult prevVisibleThingOfStartBoundary =
   4128      WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   4129          {WSRunScanner::Option::OnlyEditableNodes},
   4130          EditorRawDOMPoint(aRangeToDelete.StartRef()));
   4131  // If the range starts after an invisible <br> of empty line immediately
   4132  // before the most distant inclusive ancestor of the right block like
   4133  // `<br><br>{<div>]abc`, we should delete the last empty line because
   4134  // users won't see any reaction of the builtin editor in this case.
   4135  if (prevVisibleThingOfStartBoundary.ReachedBRElement() ||
   4136      prevVisibleThingOfStartBoundary.ReachedPreformattedLineBreak()) {
   4137    const WSScanResult prevVisibleThingOfPreviousLineBreak =
   4138        WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   4139            {WSRunScanner::Option::OnlyEditableNodes},
   4140            prevVisibleThingOfStartBoundary
   4141                .PointAtReachedContent<EditorRawDOMPoint>());
   4142    const WSScanResult nextVisibleThingOfPreviousBR =
   4143        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   4144            {WSRunScanner::Option::OnlyEditableNodes},
   4145            prevVisibleThingOfStartBoundary
   4146                .PointAfterReachedContent<EditorRawDOMPoint>());
   4147    if ((prevVisibleThingOfPreviousLineBreak.ReachedBRElement() ||
   4148         prevVisibleThingOfPreviousLineBreak.ReachedPreformattedLineBreak()) &&
   4149        nextVisibleThingOfPreviousBR.ReachedOtherBlockElement() &&
   4150        nextVisibleThingOfPreviousBR.ElementPtr() ==
   4151            inclusiveAncestorCurrentBlockOrError.inspect()) {
   4152      aRangeToDelete.SetStart(prevVisibleThingOfStartBoundary
   4153                                  .PointAtReachedContent<EditorRawDOMPoint>()
   4154                                  .ToRawRangeBoundary(),
   4155                              IgnoreErrors());
   4156    }
   4157  }
   4158 
   4159  if (preserveEndBoundary) {
   4160    return;
   4161  }
   4162 
   4163  if (aComputeRangeFor == ComputeRangeFor::GetTargetRanges) {
   4164    // When we set the end boundary to around the right block, the new end
   4165    // boundary should not after inline ancestors of the line break which won't
   4166    // be deleted.
   4167    const WSScanResult lastVisibleThingBeforeRightChildBlock =
   4168        [&]() -> WSScanResult {
   4169      EditorRawDOMPoint scanStartPoint(aRangeToDelete.StartRef());
   4170      WSScanResult lastScanResult = WSScanResult::Error();
   4171      while (true) {
   4172        WSScanResult scanResult =
   4173            WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   4174                {WSRunScanner::Option::OnlyEditableNodes}, scanStartPoint,
   4175                mLeftContent->AsElement());
   4176        if (scanResult.ReachedBlockBoundary() ||
   4177            scanResult.ReachedInlineEditingHostBoundary()) {
   4178          return lastScanResult;
   4179        }
   4180        scanStartPoint =
   4181            scanResult.PointAfterReachedContent<EditorRawDOMPoint>();
   4182        lastScanResult = scanResult;
   4183      }
   4184    }();
   4185    if (lastVisibleThingBeforeRightChildBlock.GetContent()) {
   4186      const nsIContent* commonAncestor = nsIContent::FromNode(
   4187          nsContentUtils::GetClosestCommonInclusiveAncestor(
   4188              aRangeToDelete.StartRef().GetContainer(),
   4189              lastVisibleThingBeforeRightChildBlock.GetContent()));
   4190      MOZ_ASSERT(commonAncestor);
   4191      if (commonAncestor &&
   4192          !mRightContent->IsInclusiveDescendantOf(commonAncestor)) {
   4193        IgnoredErrorResult error;
   4194        aRangeToDelete.SetEnd(
   4195            EditorRawDOMPoint::AtEndOf(*commonAncestor).ToRawRangeBoundary(),
   4196            error);
   4197        NS_WARNING_ASSERTION(!error.Failed(),
   4198                             "nsRange::SetEnd() failed, but ignored");
   4199        return;
   4200      }
   4201    }
   4202  }
   4203 
   4204  IgnoredErrorResult error;
   4205  aRangeToDelete.SetEnd(
   4206      EditorRawDOMPoint(inclusiveAncestorCurrentBlockOrError.inspect())
   4207          .ToRawRangeBoundary(),
   4208      error);
   4209  NS_WARNING_ASSERTION(!error.Failed(),
   4210                       "nsRange::SetEnd() failed, but ignored");
   4211 }
   4212 
   4213 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   4214    ComputeRangeToDeleteNonCollapsedRange(
   4215        const HTMLEditor& aHTMLEditor,
   4216        nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete,
   4217        AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed,
   4218        const Element& aEditingHost) const {
   4219  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   4220  MOZ_ASSERT(!aRangeToDelete.Collapsed());
   4221  MOZ_ASSERT(mLeftContent);
   4222  MOZ_ASSERT(mLeftContent->IsElement());
   4223  MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf(
   4224      mLeftContent));
   4225  MOZ_ASSERT(mRightContent);
   4226  MOZ_ASSERT(mRightContent->IsElement());
   4227  MOZ_ASSERT(
   4228      aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent));
   4229 
   4230  ExtendRangeToDeleteNonCollapsedRange(aHTMLEditor, aRangeToDelete,
   4231                                       aEditingHost,
   4232                                       ComputeRangeFor::GetTargetRanges);
   4233 
   4234  Result<bool, nsresult> result =
   4235      ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure(
   4236          aHTMLEditor, aRangeToDelete, aSelectionWasCollapsed);
   4237  if (result.isErr()) {
   4238    NS_WARNING(
   4239        "AutoBlockElementsJoiner::"
   4240        "ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure() "
   4241        "failed");
   4242    return result.unwrapErr();
   4243  }
   4244  if (!result.unwrap()) {
   4245    return NS_OK;
   4246  }
   4247 
   4248  AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent,
   4249                                                  *mRightContent);
   4250  Result<bool, nsresult> canJoinThem =
   4251      joiner.Prepare(aHTMLEditor, aEditingHost);
   4252  if (canJoinThem.isErr()) {
   4253    NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
   4254    return canJoinThem.unwrapErr();
   4255  }
   4256 
   4257  if (!canJoinThem.unwrap()) {
   4258    return NS_SUCCESS_DOM_NO_OPERATION;
   4259  }
   4260 
   4261  if (!joiner.CanJoinBlocks()) {
   4262    return NS_OK;
   4263  }
   4264 
   4265  nsresult rv = joiner.ComputeRangeToDelete(aHTMLEditor, EditorDOMPoint(),
   4266                                            aRangeToDelete);
   4267  NS_WARNING_ASSERTION(
   4268      NS_SUCCEEDED(rv),
   4269      "AutoInclusiveAncestorBlockElementsJoiner::ComputeRangeToDelete() "
   4270      "failed");
   4271 
   4272  // FIXME: If we'll delete unnecessary following <br>, we need to include it
   4273  // into aRangesToDelete.
   4274 
   4275  return rv;
   4276 }
   4277 
   4278 Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   4279    AutoBlockElementsJoiner::HandleDeleteNonCollapsedRange(
   4280        HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   4281        nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete,
   4282        AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed,
   4283        const Element& aEditingHost) {
   4284  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   4285  MOZ_ASSERT(!aRangeToDelete.Collapsed());
   4286  MOZ_ASSERT(mDeleteRangesHandler);
   4287 
   4288  const bool isDeletingLineBreak =
   4289      mMode == Mode::DeleteBRElement ||
   4290      mMode == Mode::DeletePrecedingBRElementOfBlock ||
   4291      mMode == Mode::DeletePrecedingPreformattedLineBreak;
   4292  if (!isDeletingLineBreak) {
   4293    MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf(
   4294        mLeftContent));
   4295    MOZ_ASSERT(aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(
   4296        mRightContent));
   4297    ExtendRangeToDeleteNonCollapsedRange(aHTMLEditor, aRangeToDelete,
   4298                                         aEditingHost,
   4299                                         ComputeRangeFor::ToDeleteTheRange);
   4300  }
   4301 
   4302  const bool backspaceInRightBlock =
   4303      aSelectionWasCollapsed == SelectionWasCollapsed::Yes &&
   4304      nsIEditor::DirectionIsBackspace(aDirectionAndAmount);
   4305 
   4306  AutoTArray<OwningNonNull<nsIContent>, 10> arrayOfTopChildren;
   4307  {
   4308    DOMSubtreeIterator iter;
   4309    nsresult rv = iter.Init(aRangeToDelete);
   4310    if (NS_FAILED(rv)) {
   4311      NS_WARNING("DOMSubtreeIterator::Init() failed");
   4312      return Err(rv);
   4313    }
   4314    iter.AppendAllNodesToArray(arrayOfTopChildren);
   4315  }
   4316 
   4317  const bool needsToJoinLater =
   4318      NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
   4319          aHTMLEditor, arrayOfTopChildren, aSelectionWasCollapsed);
   4320  const bool joinInclusiveAncestorBlockElements =
   4321      !isDeletingLineBreak && needsToJoinLater;
   4322  const bool maybeDeleteOnlyFollowingContentOfFollowingBlockBoundary =
   4323      !isDeletingLineBreak &&
   4324      mMode != Mode::DeletePrecedingLinesAndContentInRange &&
   4325      HTMLEditUtils::PointIsImmediatelyBeforeCurrentBlockBoundary(
   4326          EditorRawDOMPoint(aRangeToDelete.StartRef()),
   4327          HTMLEditUtils::IgnoreInvisibleLineBreak::Yes);
   4328  const PutCaretTo putCaretTo = [&]() {
   4329    // When we delete only preceding lines of the right child block, we should
   4330    // put caret into start of the right block.
   4331    if (mMode == Mode::DeletePrecedingLinesAndContentInRange) {
   4332      return PutCaretTo::EndOfRange;
   4333    }
   4334    // If we're joining blocks: if deleting forward the selection should be
   4335    // collapsed to the end of the selection, if deleting backward the selection
   4336    // should be collapsed to the beginning of the selection.
   4337    if (joinInclusiveAncestorBlockElements) {
   4338      return nsIEditor::DirectionIsDelete(aDirectionAndAmount)
   4339                 ? PutCaretTo::EndOfRange
   4340                 : PutCaretTo::StartOfRange;
   4341    }
   4342    // But if we're not joining then the selection should collapse to the
   4343    // beginning of the selection if we're deleting forward, because the end of
   4344    // the selection will still be in the next block. And same thing for
   4345    // deleting backwards (selection should collapse to the end, because the
   4346    // beginning will still be in the first block). See Bug 507936.
   4347    return nsIEditor::DirectionIsDelete(aDirectionAndAmount)
   4348               ? PutCaretTo::StartOfRange
   4349               : PutCaretTo::EndOfRange;
   4350  }();
   4351 
   4352  auto deleteContentResultOrError =
   4353      [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
   4354      -> Result<DeleteRangeResult, nsresult> {
   4355    OwningNonNull<nsRange> rangeToDelete(aRangeToDelete);
   4356    AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(),
   4357                                         &rangeToDelete);
   4358 
   4359    // First, delete nodes which are entirely selected except table structure
   4360    // elements like <td>, <th>, <caption>.
   4361    Result<DeleteRangeResult, nsresult> deleteResultOrError =
   4362        DeleteNodesEntirelyInRangeButKeepTableStructure(
   4363            aHTMLEditor, arrayOfTopChildren, putCaretTo);
   4364    if (MOZ_UNLIKELY(deleteResultOrError.isErr())) {
   4365      NS_WARNING(
   4366          "AutoBlockElementsJoiner::"
   4367          "DeleteNodesEntirelyInRangeButKeepTableStructure() failed");
   4368      return deleteResultOrError.propagateErr();
   4369    }
   4370    DeleteRangeResult deleteResult = deleteResultOrError.unwrap();
   4371    // We'll compute caret position below, so, we don't need the caret point
   4372    // suggestion of DeleteNodesEntirelyInRangeButKeepTableStructure().
   4373    deleteResult.ForgetCaretPointSuggestion();
   4374 
   4375    // Check endpoints for possible text deletion.  We can assume that if
   4376    // text node is found, we can delete to end or to beginning as
   4377    // appropriate, since the case where both sel endpoints in same text
   4378    // node was already handled (we wouldn't be here)
   4379    AutoTrackDOMDeleteRangeResult trackDeleteResult(
   4380        aHTMLEditor.RangeUpdaterRef(), &deleteResult);
   4381    Result<DeleteRangeResult, nsresult> deleteSurroundingTextResultOrError =
   4382        DeleteTextAtStartAndEndOfRange(aHTMLEditor, rangeToDelete, putCaretTo);
   4383    if (MOZ_UNLIKELY(deleteSurroundingTextResultOrError.isErr())) {
   4384      NS_WARNING(
   4385          "AutoBlockElementsJoiner::DeleteTextAtStartAndEndOfRange() failed");
   4386      return deleteSurroundingTextResultOrError.propagateErr();
   4387    }
   4388    trackDeleteResult.FlushAndStopTracking();
   4389    trackRangeToDelete.FlushAndStopTracking();
   4390 
   4391    DeleteRangeResult deleteSurroundingTextResult =
   4392        deleteSurroundingTextResultOrError.unwrap();
   4393    // We'll compute caret position below, so, we don't need the caret point
   4394    // suggestion of DeleteTextAtStartAndEndOfRange().
   4395    deleteSurroundingTextResult.ForgetCaretPointSuggestion();
   4396 
   4397    // Merge the deleted range.
   4398    deleteResult |= deleteSurroundingTextResult;
   4399 
   4400    if (mRightContent && mMode == Mode::DeletePrecedingLinesAndContentInRange) {
   4401      if (NS_WARN_IF(!mRightContent->IsInComposedDoc())) {
   4402        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   4403      }
   4404      auto pointToPutCaret =
   4405          HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
   4406              *mRightContent, {});
   4407      MOZ_ASSERT(pointToPutCaret.IsSet());
   4408      deleteResult |= CaretPoint(std::move(pointToPutCaret));
   4409    }
   4410    return std::move(deleteResult);
   4411  }();
   4412  if (MOZ_UNLIKELY(deleteContentResultOrError.isErr())) {
   4413    return deleteContentResultOrError.propagateErr();
   4414  }
   4415  DeleteRangeResult deleteContentResult = deleteContentResultOrError.unwrap();
   4416  // HandleDeleteLineBreak() should handle the new caret position by itself.
   4417  if (isDeletingLineBreak) {
   4418    MOZ_ASSERT(!joinInclusiveAncestorBlockElements);
   4419    deleteContentResult.IgnoreCaretPointSuggestion();
   4420    return EditActionResult::HandledResult();
   4421  }
   4422 
   4423  auto moveFirstLineResultOrError =
   4424      [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
   4425      -> Result<DeleteRangeResult, nsresult> {
   4426    if (!joinInclusiveAncestorBlockElements) {
   4427      return DeleteRangeResult::IgnoredResult();
   4428    }
   4429 
   4430    MOZ_ASSERT(mLeftContent);
   4431    MOZ_ASSERT(mLeftContent->IsElement());
   4432    MOZ_ASSERT(mRightContent);
   4433    MOZ_ASSERT(mRightContent->IsElement());
   4434 
   4435    if (!joinInclusiveAncestorBlockElements) {
   4436      return DeleteRangeResult::IgnoredResult();
   4437    }
   4438 
   4439    // Finally, join elements containing either mLeftContent or mRightContent.
   4440    // XXX This may join only inline elements despite its name.
   4441    AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent,
   4442                                                    *mRightContent);
   4443    Result<bool, nsresult> canJoinThem =
   4444        joiner.Prepare(aHTMLEditor, aEditingHost);
   4445    if (canJoinThem.isErr()) {
   4446      NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed");
   4447      return canJoinThem.propagateErr();
   4448    }
   4449 
   4450    if (!canJoinThem.inspect() || !joiner.CanJoinBlocks()) {
   4451      return DeleteRangeResult::IgnoredResult();
   4452    }
   4453 
   4454    OwningNonNull<nsRange> rangeToDelete(aRangeToDelete);
   4455    AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(),
   4456                                         &rangeToDelete);
   4457    AutoTrackDOMDeleteRangeResult trackDeleteContentResult(
   4458        aHTMLEditor.RangeUpdaterRef(), &deleteContentResult);
   4459    Result<DeleteRangeResult, nsresult> moveFirstLineResultOrError =
   4460        joiner.Run(aHTMLEditor, aEditingHost);
   4461    if (MOZ_UNLIKELY(moveFirstLineResultOrError.isErr())) {
   4462      NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed");
   4463      return moveFirstLineResultOrError.propagateErr();
   4464    }
   4465    trackDeleteContentResult.FlushAndStopTracking();
   4466    trackRangeToDelete.FlushAndStopTracking();
   4467    DeleteRangeResult moveFirstLineResult = moveFirstLineResultOrError.unwrap();
   4468 #ifdef DEBUG
   4469    if (joiner.ShouldDeleteLeafContentInstead()) {
   4470      NS_ASSERTION(moveFirstLineResult.Ignored(),
   4471                   "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
   4472                   "returning ignored, but returned not ignored");
   4473    } else {
   4474      NS_ASSERTION(!moveFirstLineResult.Ignored(),
   4475                   "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` "
   4476                   "returning handled, but returned ignored");
   4477    }
   4478 #endif  // #ifdef DEBUG
   4479    return std::move(moveFirstLineResult);
   4480  }();
   4481  if (MOZ_UNLIKELY(moveFirstLineResultOrError.isErr())) {
   4482    deleteContentResult.IgnoreCaretPointSuggestion();
   4483    return moveFirstLineResultOrError.propagateErr();
   4484  }
   4485  DeleteRangeResult moveFirstLineResult = moveFirstLineResultOrError.unwrap();
   4486 
   4487  auto pointToPutCaret = [&]() MOZ_NEVER_INLINE_DEBUG -> EditorDOMPoint {
   4488    if (moveFirstLineResult.HasCaretPointSuggestion()) {
   4489      MOZ_ASSERT(moveFirstLineResult.Handled());
   4490      if (MayEditActionDeleteAroundCollapsedSelection(
   4491              aHTMLEditor.GetEditAction())) {
   4492        deleteContentResult.IgnoreCaretPointSuggestion();
   4493        // If we're deleting selection (not replacing with new content) and
   4494        // AutoInclusiveAncestorBlockElementsJoiner computed new caret position,
   4495        // we should use it.
   4496        return moveFirstLineResult.UnwrapCaretPoint();
   4497      }
   4498      moveFirstLineResult.IgnoreCaretPointSuggestion();
   4499    }
   4500    if (deleteContentResult.HasCaretPointSuggestion()) {
   4501      return deleteContentResult.UnwrapCaretPoint();
   4502    }
   4503    return EditorDOMPoint(putCaretTo == PutCaretTo::StartOfRange
   4504                              ? aRangeToDelete.StartRef()
   4505                              : aRangeToDelete.EndRef());
   4506  }();
   4507  MOZ_ASSERT(pointToPutCaret.IsSetAndValidInComposedDoc());
   4508 
   4509  {
   4510    AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
   4511                                           &pointToPutCaret);
   4512    nsresult rv = mDeleteRangesHandler->DeleteUnnecessaryNodes(
   4513        aHTMLEditor, EditorDOMRange(aRangeToDelete), aEditingHost);
   4514    if (NS_FAILED(rv)) {
   4515      NS_WARNING("AutoDeleteRangesHandler::DeleteUnnecessaryNodes() failed");
   4516      return Err(rv);
   4517    }
   4518    trackPointToPutCaret.FlushAndStopTracking();
   4519    if (NS_WARN_IF(!pointToPutCaret.IsSetAndValidInComposedDoc())) {
   4520      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   4521    }
   4522  }
   4523 
   4524  if (aHTMLEditor.IsMailEditor() &&
   4525      MOZ_LIKELY(pointToPutCaret.IsInContentNode())) {
   4526    AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
   4527                                           &pointToPutCaret);
   4528    nsresult rv = aHTMLEditor.DeleteMostAncestorMailCiteElementIfEmpty(
   4529        MOZ_KnownLive(*pointToPutCaret.ContainerAs<nsIContent>()));
   4530    if (NS_FAILED(rv)) {
   4531      NS_WARNING(
   4532          "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed");
   4533      return Err(rv);
   4534    }
   4535    trackPointToPutCaret.FlushAndStopTracking();
   4536    if (NS_WARN_IF(!pointToPutCaret.IsSetAndValidInComposedDoc())) {
   4537      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   4538    }
   4539  }
   4540 
   4541  const auto EnsureNoFollowingUnnecessaryLineBreak =
   4542      [&](const EditorDOMPoint& aPoint)
   4543          MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT {
   4544            if (!aPoint.IsInContentNode()) {
   4545              return NS_OK;
   4546            }
   4547            AutoTrackDOMDeleteRangeResult trackDeleteContentResult(
   4548                aHTMLEditor.RangeUpdaterRef(), &deleteContentResult);
   4549            AutoTrackDOMDeleteRangeResult trackMoveFirstLineResult(
   4550                aHTMLEditor.RangeUpdaterRef(), &moveFirstLineResult);
   4551            AutoTrackDOMPoint trackPointToPutCaret(
   4552                aHTMLEditor.RangeUpdaterRef(), &pointToPutCaret);
   4553            nsresult rv =
   4554                aHTMLEditor.EnsureNoFollowingUnnecessaryLineBreak(aPoint);
   4555            NS_WARNING_ASSERTION(
   4556                NS_SUCCEEDED(rv),
   4557                "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
   4558            return rv;
   4559          };
   4560 
   4561  const auto InsertPaddingBRElementIfNeeded =
   4562      [&](const EditorDOMPoint& aPoint)
   4563          MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
   4564      -> Result<CaretPoint, nsresult> {
   4565    if (!aPoint.IsInContentNode()) {
   4566      return CaretPoint(EditorDOMPoint());
   4567    }
   4568    const bool insertingAtCaretPoint = aPoint == pointToPutCaret;
   4569    if (insertingAtCaretPoint && aHTMLEditor.GetTopLevelEditSubAction() !=
   4570                                     EditSubAction::eDeleteSelectedContent) {
   4571      return CaretPoint(EditorDOMPoint());
   4572    }
   4573    if (!insertingAtCaretPoint &&
   4574        mMode == Mode::DeletePrecedingLinesAndContentInRange) {
   4575      return CaretPoint(EditorDOMPoint());
   4576    }
   4577    AutoTrackDOMDeleteRangeResult trackDeleteContentResult(
   4578        aHTMLEditor.RangeUpdaterRef(), &deleteContentResult);
   4579    AutoTrackDOMDeleteRangeResult trackMoveFirstLineResult(
   4580        aHTMLEditor.RangeUpdaterRef(), &moveFirstLineResult);
   4581    AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
   4582                                           &pointToPutCaret);
   4583    Result<CreateLineBreakResult, nsresult> insertPaddingBRElementOrError =
   4584        aHTMLEditor.InsertPaddingBRElementIfNeeded(
   4585            aPoint,
   4586            aEditingHost.IsContentEditablePlainTextOnly() ? nsIEditor::eNoStrip
   4587                                                          : nsIEditor::eStrip,
   4588            aEditingHost);
   4589    if (MOZ_UNLIKELY(insertPaddingBRElementOrError.isErr())) {
   4590      NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed");
   4591      return insertPaddingBRElementOrError.propagateErr();
   4592    }
   4593    CreateLineBreakResult insertPaddingBRElement =
   4594        insertPaddingBRElementOrError.unwrap();
   4595    if (!insertPaddingBRElement.Handled() || !insertingAtCaretPoint) {
   4596      insertPaddingBRElement.IgnoreCaretPointSuggestion();
   4597      return CaretPoint(EditorDOMPoint());
   4598    }
   4599    return CaretPoint(insertPaddingBRElement.UnwrapCaretPoint());
   4600  };
   4601 
   4602  // If we moved content from the right element to the left element, we need to
   4603  // maintain padding line break at end of moved content.
   4604  if (moveFirstLineResult.Handled() &&
   4605      moveFirstLineResult.DeleteRangeRef().IsPositioned()) {
   4606    nsresult rv = EnsureNoFollowingUnnecessaryLineBreak(
   4607        moveFirstLineResult.DeleteRangeRef().EndRef());
   4608    if (NS_FAILED(rv)) {
   4609      NS_WARNING("EnsureNoFollowingUnnecessaryLineBreak() failed");
   4610      return Err(rv);
   4611    }
   4612    // If we moved a child block of the first line (although this is
   4613    // logically wrong...), we should not put a <br> after that.
   4614    const bool movedLineEndsWithBlockBoundary = [&]() {
   4615      Element* const commonAncestor =
   4616          Element::FromNodeOrNull(moveFirstLineResult.DeleteRangeRef()
   4617                                      .GetClosestCommonInclusiveAncestor());
   4618      nsIContent* const previousVisibleLeafOrChildBlock =
   4619          HTMLEditUtils::GetPreviousNonEmptyLeafContentOrPreviousBlockElement(
   4620              moveFirstLineResult.DeleteRangeRef().EndRef(),
   4621              {LeafNodeType::LeafNodeOrChildBlock},
   4622              BlockInlineCheck::UseComputedDisplayOutsideStyle, commonAncestor);
   4623      if (!previousVisibleLeafOrChildBlock) {
   4624        return false;
   4625      }
   4626      return HTMLEditUtils::IsBlockElement(
   4627                 *previousVisibleLeafOrChildBlock,
   4628                 BlockInlineCheck::UseComputedDisplayOutsideStyle) &&
   4629             moveFirstLineResult.DeleteRangeRef().StartRef().EqualsOrIsBefore(
   4630                 EditorRawDOMPoint::After(*previousVisibleLeafOrChildBlock));
   4631    }();
   4632    if (MOZ_LIKELY(!movedLineEndsWithBlockBoundary)) {
   4633      Result<CaretPoint, nsresult> caretPointOrError =
   4634          InsertPaddingBRElementIfNeeded(
   4635              moveFirstLineResult.DeleteRangeRef().EndRef());
   4636      if (NS_WARN_IF(caretPointOrError.isErr())) {
   4637        return caretPointOrError.propagateErr();
   4638      }
   4639      caretPointOrError.unwrap().MoveCaretPointTo(
   4640          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   4641    }
   4642  }
   4643  // If we only deleted content in the range, we need to maintain padding line
   4644  // breaks at both deleted range boundaries.
   4645  else if (deleteContentResult.DeleteRangeRef().IsPositioned()) {
   4646    if (!deleteContentResult.DeleteRangeRef().Collapsed()) {
   4647      nsresult rv;
   4648      if (NS_WARN_IF(
   4649              NS_FAILED(rv = EnsureNoFollowingUnnecessaryLineBreak(
   4650                            deleteContentResult.DeleteRangeRef().EndRef())))) {
   4651        return Err(rv);
   4652      }
   4653      // If we deleted blocks following current block, we should not insert
   4654      // padding line break after current block when we're handling Backspace.
   4655      const bool isFollowingBlockDeletedByBackspace =
   4656          [&]() MOZ_NEVER_INLINE_DEBUG {
   4657            if (putCaretTo == PutCaretTo::EndOfRange) {
   4658              return false;
   4659            }
   4660            if (!HTMLEditUtils::RangeIsAcrossStartBlockBoundary(
   4661                    deleteContentResult.DeleteRangeRef(),
   4662                    // XXX UseComputedDisplayStyle?
   4663                    BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
   4664              return false;
   4665            }
   4666            WSScanResult nextThing =
   4667                WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   4668                    {WSRunScanner::Option::OnlyEditableNodes},
   4669                    deleteContentResult.DeleteRangeRef().EndRef());
   4670            return nextThing.ReachedBRElement() ||
   4671                   nextThing.ReachedPreformattedLineBreak() ||
   4672                   nextThing.ReachedHRElement() ||
   4673                   nextThing.ReachedBlockBoundary();
   4674          }();
   4675      if (!isFollowingBlockDeletedByBackspace) {
   4676        Result<CaretPoint, nsresult> caretPointOrError =
   4677            InsertPaddingBRElementIfNeeded(
   4678                deleteContentResult.DeleteRangeRef().EndRef());
   4679        if (NS_WARN_IF(caretPointOrError.isErr())) {
   4680          return caretPointOrError.propagateErr();
   4681        }
   4682        caretPointOrError.unwrap().MoveCaretPointTo(
   4683            pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   4684      }
   4685    }
   4686    // If we deleted content only after current block, we don't need to
   4687    // maintain line breaks at start of the deleted range because nothing has
   4688    // been changed from the caret point of view.
   4689    if (!maybeDeleteOnlyFollowingContentOfFollowingBlockBoundary) {
   4690      nsresult rv;
   4691      if (NS_WARN_IF(NS_FAILED(
   4692              rv = EnsureNoFollowingUnnecessaryLineBreak(
   4693                  deleteContentResult.DeleteRangeRef().StartRef())))) {
   4694        return Err(rv);
   4695      }
   4696      Result<CaretPoint, nsresult> caretPointOrError =
   4697          InsertPaddingBRElementIfNeeded(
   4698              deleteContentResult.DeleteRangeRef().StartRef());
   4699      if (NS_WARN_IF(caretPointOrError.isErr())) {
   4700        return caretPointOrError.propagateErr();
   4701      }
   4702      caretPointOrError.unwrap().MoveCaretPointTo(
   4703          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   4704    }
   4705  }
   4706 
   4707  nsresult rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret);
   4708  if (NS_FAILED(rv)) {
   4709    NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   4710    return Err(rv);
   4711  }
   4712  if (mMode == Mode::DeletePrecedingLinesAndContentInRange ||
   4713      moveFirstLineResult.Handled()) {
   4714    // If we prefer to use style in the previous line, we should forget previous
   4715    // styles since the caret position has all styles which we want to use with
   4716    // new content.
   4717    if (backspaceInRightBlock) {
   4718      aHTMLEditor.TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
   4719    }
   4720    // And we don't want to keep extending a link at ex-end of the previous
   4721    // paragraph.
   4722    if (HTMLEditor::GetLinkElement(pointToPutCaret.GetContainer())) {
   4723      aHTMLEditor.mPendingStylesToApplyToNewContent
   4724          ->ClearLinkAndItsSpecifiedStyle();
   4725    }
   4726  }
   4727  return EditActionResult::HandledResult();
   4728 }
   4729 
   4730 nsresult HTMLEditor::AutoDeleteRangesHandler::DeleteUnnecessaryNodes(
   4731    HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange,
   4732    const Element& aEditingHost) {
   4733  MOZ_ASSERT(aHTMLEditor.IsTopLevelEditSubActionDataAvailable());
   4734  MOZ_ASSERT(EditorUtils::IsEditableContent(
   4735      *aRange.StartRef().ContainerAs<nsIContent>(), EditorType::HTML));
   4736  MOZ_ASSERT(EditorUtils::IsEditableContent(
   4737      *aRange.EndRef().ContainerAs<nsIContent>(), EditorType::HTML));
   4738 
   4739  EditorDOMRange range(aRange);
   4740 
   4741  // If we're handling DnD, this is called to delete dragging item from the
   4742  // tree.  In this case, we should remove parent blocks if it becomes empty.
   4743  if (aHTMLEditor.GetEditAction() == EditAction::eDrop ||
   4744      aHTMLEditor.GetEditAction() == EditAction::eDeleteByDrag) {
   4745    MOZ_ASSERT(range.Collapsed() ||
   4746               (range.StartRef().GetContainer()->GetNextSibling() ==
   4747                    range.EndRef().GetContainer() &&
   4748                range.StartRef().IsEndOfContainer() &&
   4749                range.EndRef().IsStartOfContainer()));
   4750    AutoTrackDOMRange trackRange(aHTMLEditor.RangeUpdaterRef(), &range);
   4751 
   4752    nsresult rv = DeleteParentBlocksWithTransactionIfEmpty(
   4753        aHTMLEditor, range.StartRef(), aEditingHost);
   4754    if (NS_FAILED(rv)) {
   4755      NS_WARNING(
   4756          "HTMLEditor::DeleteParentBlocksWithTransactionIfEmpty() failed");
   4757      return rv;
   4758    }
   4759    aHTMLEditor.TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks =
   4760        rv == NS_OK;
   4761    // If we removed parent blocks, Selection should be collapsed at where
   4762    // the most ancestor empty block has been.
   4763    if (aHTMLEditor.TopLevelEditSubActionDataRef()
   4764            .mDidDeleteEmptyParentBlocks) {
   4765      return NS_OK;
   4766    }
   4767  }
   4768 
   4769  if (NS_WARN_IF(!range.IsInContentNodes()) ||
   4770      NS_WARN_IF(!EditorUtils::IsEditableContent(
   4771          *range.StartRef().ContainerAs<nsIContent>(), EditorType::HTML)) ||
   4772      NS_WARN_IF(!EditorUtils::IsEditableContent(
   4773          *range.EndRef().ContainerAs<nsIContent>(), EditorType::HTML))) {
   4774    return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
   4775  }
   4776 
   4777  // We might have left only collapsed white-space in the start/end nodes
   4778  {
   4779    AutoTrackDOMRange trackRange(aHTMLEditor.RangeUpdaterRef(), &range);
   4780 
   4781    OwningNonNull<nsIContent> startContainer =
   4782        *range.StartRef().ContainerAs<nsIContent>();
   4783    OwningNonNull<nsIContent> endContainer =
   4784        *range.EndRef().ContainerAs<nsIContent>();
   4785    nsresult rv =
   4786        DeleteNodeIfInvisibleAndEditableTextNode(aHTMLEditor, startContainer);
   4787    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   4788      return NS_ERROR_EDITOR_DESTROYED;
   4789    }
   4790    NS_WARNING_ASSERTION(
   4791        NS_SUCCEEDED(rv),
   4792        "AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode() "
   4793        "failed to remove start node, but ignored");
   4794    // If we've not handled the selection end container, and it's still
   4795    // editable, let's handle it.
   4796    if (!range.InSameContainer() &&
   4797        EditorUtils::IsEditableContent(
   4798            *range.EndRef().ContainerAs<nsIContent>(), EditorType::HTML)) {
   4799      rv = DeleteNodeIfInvisibleAndEditableTextNode(aHTMLEditor, endContainer);
   4800      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   4801        return NS_ERROR_EDITOR_DESTROYED;
   4802      }
   4803      NS_WARNING_ASSERTION(
   4804          NS_SUCCEEDED(rv),
   4805          "AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode() "
   4806          "failed to remove end node, but ignored");
   4807    }
   4808  }
   4809 
   4810  if (NS_WARN_IF(!range.IsPositioned())) {
   4811    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   4812  }
   4813 
   4814  if (MOZ_LIKELY(range.EndRef().IsInContentNode())) {
   4815    AutoTrackDOMRange trackRange(aHTMLEditor.RangeUpdaterRef(), &range);
   4816    nsresult rv =
   4817        aHTMLEditor.EnsureNoFollowingUnnecessaryLineBreak(range.EndRef());
   4818    if (NS_FAILED(rv)) {
   4819      NS_WARNING("HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
   4820      return Err(rv);
   4821    }
   4822  }
   4823  if (NS_WARN_IF(!range.IsPositioned())) {
   4824    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   4825  }
   4826 
   4827  return NS_OK;
   4828 }
   4829 
   4830 nsresult
   4831 HTMLEditor::AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode(
   4832    HTMLEditor& aHTMLEditor, nsIContent& aContent) {
   4833  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   4834 
   4835  Text* text = aContent.GetAsText();
   4836  if (!text) {
   4837    return NS_OK;
   4838  }
   4839 
   4840  if (!HTMLEditUtils::IsRemovableFromParentNode(*text) ||
   4841      HTMLEditUtils::IsVisibleTextNode(*text)) {
   4842    return NS_OK;
   4843  }
   4844 
   4845  nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aContent);
   4846  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   4847                       "EditorBase::DeleteNodeWithTransaction() failed");
   4848  return rv;
   4849 }
   4850 
   4851 nsresult
   4852 HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
   4853    HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint,
   4854    const Element& aEditingHost) {
   4855  MOZ_ASSERT(aPoint.IsSet());
   4856  MOZ_ASSERT(aHTMLEditor.mPlaceholderBatch);
   4857 
   4858  const WSRunScanner scanner({}, aPoint, &aEditingHost);
   4859 
   4860  // First, check there is visible contents before the point in current block.
   4861  const WSScanResult prevVisibleThing =
   4862      scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPoint);
   4863  if (!prevVisibleThing.ReachedCurrentBlockBoundary() &&
   4864      !prevVisibleThing.ReachedInlineEditingHostBoundary()) {
   4865    // If there is visible node before the point, we shouldn't remove the
   4866    // parent block.
   4867    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
   4868  }
   4869  MOZ_ASSERT(prevVisibleThing.ElementPtr());
   4870  if (&aEditingHost == prevVisibleThing.ElementPtr() ||
   4871      HTMLEditUtils::IsRemovableFromParentNode(
   4872          *prevVisibleThing.ElementPtr())) {
   4873    // If we reach editing host, there is no parent blocks which can be removed.
   4874    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
   4875  }
   4876  if (HTMLEditUtils::IsTableCellOrCaptionElement(
   4877          *prevVisibleThing.ElementPtr())) {
   4878    // If we reach a <td>, <th> or <caption>, we shouldn't remove it even
   4879    // becomes empty because removing such element changes the structure of
   4880    // the <table>.
   4881    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
   4882  }
   4883 
   4884  // Next, check there is visible contents after the point in current block.
   4885  const WSScanResult nextVisibleThing =
   4886      scanner.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aPoint);
   4887  if (nextVisibleThing.Failed()) {
   4888    NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
   4889    return NS_ERROR_FAILURE;
   4890  }
   4891  if (nextVisibleThing.ReachedBRElement()) {
   4892    // XXX In my understanding, this is odd.  The end reason may not be
   4893    //     same as the reached <br> element because the equality is
   4894    //     guaranteed only when ReachedCurrentBlockBoundary() returns true.
   4895    //     However, looks like that this code assumes that
   4896    //     GetEndReasonContent() returns the (or a) <br> element.
   4897    // If the <br> element is visible, we shouldn't remove the parent block.
   4898    if (HTMLEditUtils::IsVisibleBRElement(*nextVisibleThing.BRElementPtr())) {
   4899      return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
   4900    }
   4901    if (nextVisibleThing.BRElementPtr()->GetNextSibling()) {
   4902      const WSScanResult nextVisibleThingAfterBR =
   4903          WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   4904              {}, EditorRawDOMPoint::After(*nextVisibleThing.BRElementPtr()));
   4905      if (MOZ_UNLIKELY(nextVisibleThingAfterBR.Failed())) {
   4906        NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
   4907        return NS_ERROR_FAILURE;
   4908      }
   4909      if (!nextVisibleThingAfterBR.ReachedCurrentBlockBoundary() &&
   4910          !nextVisibleThingAfterBR.ReachedInlineEditingHostBoundary()) {
   4911        // If we couldn't reach the block's end after the invisible <br>,
   4912        // that means that there is visible content.
   4913        return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
   4914      }
   4915    }
   4916  } else if (!nextVisibleThing.ReachedCurrentBlockBoundary() &&
   4917             !nextVisibleThing.ReachedInlineEditingHostBoundary()) {
   4918    // If we couldn't reach the block's end, the block has visible content.
   4919    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
   4920  }
   4921 
   4922  // Delete the parent block.
   4923  const nsCOMPtr<nsIContent> nextSibling =
   4924      prevVisibleThing.ElementPtr()->GetNextSibling();
   4925  const nsCOMPtr<nsINode> parentNode =
   4926      prevVisibleThing.ElementPtr()->GetParentNode();
   4927  nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(
   4928      // MOZ_KnownLive because of grabbed by prevVisibleThing.
   4929      MOZ_KnownLive(*prevVisibleThing.ElementPtr()));
   4930  if (NS_FAILED(rv)) {
   4931    NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   4932    return rv;
   4933  }
   4934  // If we reach editing host, return NS_OK.
   4935  if (parentNode == &aEditingHost) {
   4936    return NS_OK;
   4937  }
   4938 
   4939  // Otherwise, we need to check whether we're still in empty block or not.
   4940 
   4941  // If the mutations in the document is observed by DevTools, the next point
   4942  // may be now outside of editing host or editing hos has been changed.
   4943  if (aHTMLEditor.MaybeNodeRemovalsObservedByDevTools()) {
   4944    if (NS_WARN_IF(nextSibling &&
   4945                   !nextSibling->IsInclusiveDescendantOf(&aEditingHost)) ||
   4946        NS_WARN_IF(!parentNode->IsInclusiveDescendantOf(&aEditingHost))) {
   4947      return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
   4948    }
   4949    Element* newEditingHost = aHTMLEditor.ComputeEditingHost();
   4950    if (NS_WARN_IF(!newEditingHost) ||
   4951        NS_WARN_IF(newEditingHost != &aEditingHost)) {
   4952      return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
   4953    }
   4954    if (NS_WARN_IF(
   4955            !EditorUtils::IsDescendantOf(*parentNode, *newEditingHost))) {
   4956      return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
   4957    }
   4958  }
   4959 
   4960  const EditorDOMPoint nextPoint = nextSibling
   4961                                       ? EditorDOMPoint(nextSibling)
   4962                                       : EditorDOMPoint::AtEndOf(parentNode);
   4963  rv = DeleteParentBlocksWithTransactionIfEmpty(aHTMLEditor, nextPoint,
   4964                                                aEditingHost);
   4965  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   4966                       "AutoDeleteRangesHandler::"
   4967                       "DeleteParentBlocksWithTransactionIfEmpty() failed");
   4968  return rv;
   4969 }
   4970 
   4971 nsresult
   4972 HTMLEditor::AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction(
   4973    const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   4974    nsRange& aRangeToDelete, const Element& aEditingHost) const {
   4975  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   4976 
   4977  const EditorBase::HowToHandleCollapsedRange howToHandleCollapsedRange =
   4978      EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount);
   4979  if (MOZ_UNLIKELY(aRangeToDelete.Collapsed() &&
   4980                   howToHandleCollapsedRange ==
   4981                       EditorBase::HowToHandleCollapsedRange::Ignore)) {
   4982    return NS_SUCCESS_DOM_NO_OPERATION;
   4983  }
   4984 
   4985  // If it's not collapsed, `DeleteRangeTransaction::Create()` will be called
   4986  // with it and `DeleteRangeTransaction` won't modify the range.
   4987  if (!aRangeToDelete.Collapsed()) {
   4988    return NS_OK;
   4989  }
   4990 
   4991  const auto ExtendRangeToSelectCharacterForward =
   4992      [](nsRange& aRange, const EditorRawDOMPointInText& aCaretPoint) -> void {
   4993    const CharacterDataBuffer& characterDataBuffer =
   4994        aCaretPoint.ContainerAs<Text>()->DataBuffer();
   4995    if (!characterDataBuffer.GetLength()) {
   4996      return;
   4997    }
   4998    if (characterDataBuffer.IsHighSurrogateFollowedByLowSurrogateAt(
   4999            aCaretPoint.Offset())) {
   5000      DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd(
   5001          aCaretPoint.ContainerAs<Text>(), aCaretPoint.Offset(),
   5002          aCaretPoint.ContainerAs<Text>(), aCaretPoint.Offset() + 2);
   5003      NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   5004                           "nsRange::SetStartAndEnd() failed");
   5005      return;
   5006    }
   5007    DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd(
   5008        aCaretPoint.ContainerAs<Text>(), aCaretPoint.Offset(),
   5009        aCaretPoint.ContainerAs<Text>(), aCaretPoint.Offset() + 1);
   5010    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   5011                         "nsRange::SetStartAndEnd() failed");
   5012  };
   5013  const auto ExtendRangeToSelectCharacterBackward =
   5014      [](nsRange& aRange, const EditorRawDOMPointInText& aCaretPoint) -> void {
   5015    if (aCaretPoint.IsStartOfContainer()) {
   5016      return;
   5017    }
   5018    const CharacterDataBuffer& characterDataBuffer =
   5019        aCaretPoint.ContainerAs<Text>()->DataBuffer();
   5020    if (!characterDataBuffer.GetLength()) {
   5021      return;
   5022    }
   5023    if (characterDataBuffer.IsLowSurrogateFollowingHighSurrogateAt(
   5024            aCaretPoint.Offset() - 1)) {
   5025      DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd(
   5026          aCaretPoint.ContainerAs<Text>(), aCaretPoint.Offset() - 2,
   5027          aCaretPoint.ContainerAs<Text>(), aCaretPoint.Offset());
   5028      NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   5029                           "nsRange::SetStartAndEnd() failed");
   5030      return;
   5031    }
   5032    DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd(
   5033        aCaretPoint.ContainerAs<Text>(), aCaretPoint.Offset() - 1,
   5034        aCaretPoint.ContainerAs<Text>(), aCaretPoint.Offset());
   5035    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   5036                         "nsRange::SetStartAndEnd() failed");
   5037  };
   5038 
   5039  // In the other cases, `EditorBase::CreateTransactionForCollapsedRange()`
   5040  // will handle the collapsed range.
   5041  EditorRawDOMPoint caretPoint(aRangeToDelete.StartRef());
   5042  if (howToHandleCollapsedRange ==
   5043          EditorBase::HowToHandleCollapsedRange::ExtendBackward &&
   5044      caretPoint.IsStartOfContainer()) {
   5045    nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent(
   5046        *caretPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode},
   5047        BlockInlineCheck::Unused, &aEditingHost);
   5048    if (!previousEditableContent) {
   5049      return NS_OK;
   5050    }
   5051    if (!previousEditableContent->IsText()) {
   5052      IgnoredErrorResult ignoredError;
   5053      aRangeToDelete.SelectNode(*previousEditableContent, ignoredError);
   5054      NS_WARNING_ASSERTION(!ignoredError.Failed(),
   5055                           "nsRange::SelectNode() failed");
   5056      return NS_OK;
   5057    }
   5058 
   5059    ExtendRangeToSelectCharacterBackward(
   5060        aRangeToDelete,
   5061        EditorRawDOMPointInText::AtEndOf(*previousEditableContent->AsText()));
   5062    return NS_OK;
   5063  }
   5064 
   5065  if (howToHandleCollapsedRange ==
   5066          EditorBase::HowToHandleCollapsedRange::ExtendForward &&
   5067      caretPoint.IsEndOfContainer()) {
   5068    nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent(
   5069        *caretPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode},
   5070        BlockInlineCheck::Unused, &aEditingHost);
   5071    if (!nextEditableContent) {
   5072      return NS_OK;
   5073    }
   5074 
   5075    if (!nextEditableContent->IsText()) {
   5076      IgnoredErrorResult ignoredError;
   5077      aRangeToDelete.SelectNode(*nextEditableContent, ignoredError);
   5078      NS_WARNING_ASSERTION(!ignoredError.Failed(),
   5079                           "nsRange::SelectNode() failed");
   5080      return NS_OK;
   5081    }
   5082 
   5083    ExtendRangeToSelectCharacterForward(
   5084        aRangeToDelete,
   5085        EditorRawDOMPointInText(nextEditableContent->AsText(), 0));
   5086    return NS_OK;
   5087  }
   5088 
   5089  if (caretPoint.IsInTextNode()) {
   5090    if (howToHandleCollapsedRange ==
   5091        EditorBase::HowToHandleCollapsedRange::ExtendBackward) {
   5092      ExtendRangeToSelectCharacterBackward(
   5093          aRangeToDelete,
   5094          EditorRawDOMPointInText(caretPoint.ContainerAs<Text>(),
   5095                                  caretPoint.Offset()));
   5096      return NS_OK;
   5097    }
   5098    ExtendRangeToSelectCharacterForward(
   5099        aRangeToDelete, EditorRawDOMPointInText(caretPoint.ContainerAs<Text>(),
   5100                                                caretPoint.Offset()));
   5101    return NS_OK;
   5102  }
   5103 
   5104  nsIContent* editableContent =
   5105      howToHandleCollapsedRange ==
   5106              EditorBase::HowToHandleCollapsedRange::ExtendBackward
   5107          ? HTMLEditUtils::GetPreviousContent(
   5108                caretPoint, {WalkTreeOption::IgnoreNonEditableNode},
   5109                BlockInlineCheck::Unused, &aEditingHost)
   5110          : HTMLEditUtils::GetNextContent(
   5111                caretPoint, {WalkTreeOption::IgnoreNonEditableNode},
   5112                BlockInlineCheck::Unused, &aEditingHost);
   5113  if (!editableContent) {
   5114    return NS_OK;
   5115  }
   5116  while (editableContent && editableContent->IsCharacterData() &&
   5117         !editableContent->Length()) {
   5118    editableContent =
   5119        howToHandleCollapsedRange ==
   5120                EditorBase::HowToHandleCollapsedRange::ExtendBackward
   5121            ? HTMLEditUtils::GetPreviousContent(
   5122                  *editableContent, {WalkTreeOption::IgnoreNonEditableNode},
   5123                  BlockInlineCheck::Unused, &aEditingHost)
   5124            : HTMLEditUtils::GetNextContent(
   5125                  *editableContent, {WalkTreeOption::IgnoreNonEditableNode},
   5126                  BlockInlineCheck::Unused, &aEditingHost);
   5127  }
   5128  if (!editableContent) {
   5129    return NS_OK;
   5130  }
   5131 
   5132  if (!editableContent->IsText()) {
   5133    IgnoredErrorResult ignoredError;
   5134    aRangeToDelete.SelectNode(*editableContent, ignoredError);
   5135    NS_WARNING_ASSERTION(!ignoredError.Failed(),
   5136                         "nsRange::SelectNode() failed, but ignored");
   5137    return NS_OK;
   5138  }
   5139 
   5140  if (howToHandleCollapsedRange ==
   5141      EditorBase::HowToHandleCollapsedRange::ExtendBackward) {
   5142    ExtendRangeToSelectCharacterBackward(
   5143        aRangeToDelete,
   5144        EditorRawDOMPointInText::AtEndOf(*editableContent->AsText()));
   5145    return NS_OK;
   5146  }
   5147  ExtendRangeToSelectCharacterForward(
   5148      aRangeToDelete, EditorRawDOMPointInText(editableContent->AsText(), 0));
   5149 
   5150  return NS_OK;
   5151 }
   5152 
   5153 template <typename EditorDOMPointType>
   5154 Result<CaretPoint, nsresult> HTMLEditor::DeleteTextAndTextNodesWithTransaction(
   5155    const EditorDOMPointType& aStartPoint, const EditorDOMPointType& aEndPoint,
   5156    TreatEmptyTextNodes aTreatEmptyTextNodes) {
   5157  if (NS_WARN_IF(!aStartPoint.IsSet()) || NS_WARN_IF(!aEndPoint.IsSet())) {
   5158    return Err(NS_ERROR_INVALID_ARG);
   5159  }
   5160 
   5161  // MOOSE: this routine needs to be modified to preserve the integrity of the
   5162  // wsFragment info.
   5163 
   5164  if (aStartPoint == aEndPoint) {
   5165    // Nothing to delete
   5166    return CaretPoint(EditorDOMPoint());
   5167  }
   5168 
   5169  RefPtr<Element> editingHost = ComputeEditingHost();
   5170  auto DeleteEmptyContentNodeWithTransaction =
   5171      [this, &aTreatEmptyTextNodes, &editingHost](nsIContent& aContent)
   5172          MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> nsresult {
   5173    OwningNonNull<nsIContent> nodeToRemove = aContent;
   5174    if (aTreatEmptyTextNodes ==
   5175        TreatEmptyTextNodes::RemoveAllEmptyInlineAncestors) {
   5176      Element* emptyParentElementToRemove =
   5177          HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
   5178              nodeToRemove, BlockInlineCheck::UseComputedDisplayOutsideStyle,
   5179              editingHost);
   5180      if (emptyParentElementToRemove) {
   5181        nodeToRemove = *emptyParentElementToRemove;
   5182      }
   5183    }
   5184    nsresult rv = DeleteNodeWithTransaction(nodeToRemove);
   5185    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   5186                         "EditorBase::DeleteNodeWithTransaction() failed");
   5187    return rv;
   5188  };
   5189 
   5190  if (aStartPoint.GetContainer() == aEndPoint.GetContainer() &&
   5191      aStartPoint.IsInTextNode()) {
   5192    if (aTreatEmptyTextNodes !=
   5193            TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries &&
   5194        aStartPoint.IsStartOfContainer() && aEndPoint.IsEndOfContainer()) {
   5195      nsresult rv = DeleteEmptyContentNodeWithTransaction(
   5196          MOZ_KnownLive(*aStartPoint.template ContainerAs<Text>()));
   5197      if (NS_FAILED(rv)) {
   5198        NS_WARNING("deleteEmptyContentNodeWithTransaction() failed");
   5199        return Err(rv);
   5200      }
   5201      return CaretPoint(EditorDOMPoint());
   5202    }
   5203    RefPtr<Text> textNode = aStartPoint.template ContainerAs<Text>();
   5204    Result<CaretPoint, nsresult> caretPointOrError =
   5205        DeleteTextWithTransaction(*textNode, aStartPoint.Offset(),
   5206                                  aEndPoint.Offset() - aStartPoint.Offset());
   5207    NS_WARNING_ASSERTION(caretPointOrError.isOk(),
   5208                         "HTMLEditor::DeleteTextWithTransaction() failed");
   5209    return caretPointOrError;
   5210  }
   5211 
   5212  RefPtr<nsRange> range =
   5213      nsRange::Create(aStartPoint.ToRawRangeBoundary(),
   5214                      aEndPoint.ToRawRangeBoundary(), IgnoreErrors());
   5215  if (!range) {
   5216    NS_WARNING("nsRange::Create() failed");
   5217    return Err(NS_ERROR_FAILURE);
   5218  }
   5219 
   5220  // Collect editable text nodes in the given range.
   5221  AutoTArray<OwningNonNull<Text>, 16> arrayOfTextNodes;
   5222  DOMIterator iter;
   5223  if (NS_FAILED(iter.Init(*range))) {
   5224    return CaretPoint(EditorDOMPoint());  // Nothing to delete in the range.
   5225  }
   5226  iter.AppendNodesToArray(
   5227      +[](nsINode& aNode, void*) {
   5228        MOZ_ASSERT(aNode.IsText());
   5229        return HTMLEditUtils::IsSimplyEditableNode(aNode);
   5230      },
   5231      arrayOfTextNodes);
   5232  EditorDOMPoint pointToPutCaret;
   5233  for (OwningNonNull<Text>& textNode : arrayOfTextNodes) {
   5234    if (textNode == aStartPoint.GetContainer()) {
   5235      if (aStartPoint.IsEndOfContainer()) {
   5236        continue;
   5237      }
   5238      if (aStartPoint.IsStartOfContainer() &&
   5239          aTreatEmptyTextNodes !=
   5240              TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries) {
   5241        AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(),
   5242                                               &pointToPutCaret);
   5243        nsresult rv = DeleteEmptyContentNodeWithTransaction(
   5244            MOZ_KnownLive(*aStartPoint.template ContainerAs<Text>()));
   5245        if (NS_FAILED(rv)) {
   5246          NS_WARNING("DeleteEmptyContentNodeWithTransaction() failed");
   5247          return Err(rv);
   5248        }
   5249        continue;
   5250      }
   5251      AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(),
   5252                                             &pointToPutCaret);
   5253      Result<CaretPoint, nsresult> caretPointOrError =
   5254          DeleteTextWithTransaction(MOZ_KnownLive(textNode),
   5255                                    aStartPoint.Offset(),
   5256                                    textNode->Length() - aStartPoint.Offset());
   5257      if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   5258        NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
   5259        return caretPointOrError;
   5260      }
   5261      trackPointToPutCaret.FlushAndStopTracking();
   5262      caretPointOrError.unwrap().MoveCaretPointTo(
   5263          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   5264      continue;
   5265    }
   5266 
   5267    if (textNode == aEndPoint.GetContainer()) {
   5268      if (aEndPoint.IsStartOfContainer()) {
   5269        break;
   5270      }
   5271      if (aEndPoint.IsEndOfContainer() &&
   5272          aTreatEmptyTextNodes !=
   5273              TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries) {
   5274        AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(),
   5275                                               &pointToPutCaret);
   5276        nsresult rv = DeleteEmptyContentNodeWithTransaction(
   5277            MOZ_KnownLive(*aEndPoint.template ContainerAs<Text>()));
   5278        if (NS_FAILED(rv)) {
   5279          NS_WARNING("DeleteEmptyContentNodeWithTransaction() failed");
   5280          return Err(rv);
   5281        }
   5282        trackPointToPutCaret.FlushAndStopTracking();
   5283        return CaretPoint(std::move(pointToPutCaret));
   5284      }
   5285      AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(),
   5286                                             &pointToPutCaret);
   5287      Result<CaretPoint, nsresult> caretPointOrError =
   5288          DeleteTextWithTransaction(MOZ_KnownLive(textNode), 0,
   5289                                    aEndPoint.Offset());
   5290      if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   5291        NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
   5292        return caretPointOrError;
   5293      }
   5294      trackPointToPutCaret.FlushAndStopTracking();
   5295      caretPointOrError.unwrap().MoveCaretPointTo(
   5296          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   5297      return CaretPoint(std::move(pointToPutCaret));
   5298    }
   5299 
   5300    nsresult rv =
   5301        DeleteEmptyContentNodeWithTransaction(MOZ_KnownLive(textNode));
   5302    if (NS_FAILED(rv)) {
   5303      NS_WARNING("DeleteEmptyContentNodeWithTransaction() failed");
   5304      return Err(rv);
   5305    }
   5306  }
   5307 
   5308  return CaretPoint(std::move(pointToPutCaret));
   5309 }
   5310 
   5311 Result<EditorDOMPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   5312    AutoBlockElementsJoiner::JoinNodesDeepWithTransaction(
   5313        HTMLEditor& aHTMLEditor, nsIContent& aLeftContent,
   5314        nsIContent& aRightContent) {
   5315  // While the rightmost children and their descendants of the left node match
   5316  // the leftmost children and their descendants of the right node, join them
   5317  // up.
   5318 
   5319  nsCOMPtr<nsIContent> leftContentToJoin = &aLeftContent;
   5320  nsCOMPtr<nsIContent> rightContentToJoin = &aRightContent;
   5321  nsCOMPtr<nsINode> parentNode = aRightContent.GetParentNode();
   5322 
   5323  EditorDOMPoint ret;
   5324  while (leftContentToJoin && rightContentToJoin && parentNode &&
   5325         HTMLEditUtils::CanContentsBeJoined(*leftContentToJoin,
   5326                                            *rightContentToJoin)) {
   5327    // Do the join
   5328    Result<JoinNodesResult, nsresult> joinNodesResult =
   5329        aHTMLEditor.JoinNodesWithTransaction(*leftContentToJoin,
   5330                                             *rightContentToJoin);
   5331    if (MOZ_UNLIKELY(joinNodesResult.isErr())) {
   5332      NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
   5333      return joinNodesResult.propagateErr();
   5334    }
   5335 
   5336    ret = joinNodesResult.inspect().AtJoinedPoint<EditorDOMPoint>();
   5337    if (NS_WARN_IF(!ret.IsSet())) {
   5338      return Err(NS_ERROR_FAILURE);
   5339    }
   5340 
   5341    if (parentNode->IsText()) {
   5342      // We've joined all the way down to text nodes, we're done!
   5343      return ret;
   5344    }
   5345 
   5346    // Get new left and right nodes, and begin anew
   5347    rightContentToJoin = ret.GetCurrentChildAtOffset();
   5348    if (rightContentToJoin) {
   5349      leftContentToJoin = rightContentToJoin->GetPreviousSibling();
   5350    } else {
   5351      leftContentToJoin = nullptr;
   5352    }
   5353 
   5354    // Skip over non-editable nodes
   5355    while (leftContentToJoin && !EditorUtils::IsEditableContent(
   5356                                    *leftContentToJoin, EditorType::HTML)) {
   5357      leftContentToJoin = leftContentToJoin->GetPreviousSibling();
   5358    }
   5359    if (!leftContentToJoin) {
   5360      return ret;
   5361    }
   5362 
   5363    while (rightContentToJoin && !EditorUtils::IsEditableContent(
   5364                                     *rightContentToJoin, EditorType::HTML)) {
   5365      rightContentToJoin = rightContentToJoin->GetNextSibling();
   5366    }
   5367    if (!rightContentToJoin) {
   5368      return ret;
   5369    }
   5370  }
   5371 
   5372  if (!ret.IsSet()) {
   5373    NS_WARNING("HTMLEditor::JoinNodesDeepWithTransaction() joined no contents");
   5374    return Err(NS_ERROR_FAILURE);
   5375  }
   5376  return ret;
   5377 }
   5378 
   5379 Result<bool, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   5380    AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner::Prepare(
   5381        const HTMLEditor& aHTMLEditor, const Element& aEditingHost) {
   5382  mLeftBlockElement = HTMLEditUtils::GetInclusiveAncestorElement(
   5383      mInclusiveDescendantOfLeftBlockElement,
   5384      HTMLEditUtils::ClosestEditableBlockElementExceptHRElement,
   5385      BlockInlineCheck::UseComputedDisplayOutsideStyle);
   5386  mRightBlockElement = HTMLEditUtils::GetInclusiveAncestorElement(
   5387      mInclusiveDescendantOfRightBlockElement,
   5388      HTMLEditUtils::ClosestEditableBlockElementExceptHRElement,
   5389      BlockInlineCheck::UseComputedDisplayOutsideStyle);
   5390 
   5391  if (NS_WARN_IF(!IsSet())) {
   5392    mCanJoinBlocks = false;
   5393    return Err(NS_ERROR_UNEXPECTED);
   5394  }
   5395 
   5396  // Don't join the blocks if both of them are basic structure of the HTML
   5397  // document (Note that `<body>` can be joined with its children).
   5398  if (mLeftBlockElement->IsAnyOfHTMLElements(nsGkAtoms::html, nsGkAtoms::head,
   5399                                             nsGkAtoms::body) &&
   5400      mRightBlockElement->IsAnyOfHTMLElements(nsGkAtoms::html, nsGkAtoms::head,
   5401                                              nsGkAtoms::body)) {
   5402    mCanJoinBlocks = false;
   5403    return false;
   5404  }
   5405 
   5406  if (HTMLEditUtils::IsAnyTableElementExceptColumnElement(*mLeftBlockElement) ||
   5407      HTMLEditUtils::IsAnyTableElementExceptColumnElement(
   5408          *mRightBlockElement)) {
   5409    // Do not try to merge table elements, cancel the deletion.
   5410    mCanJoinBlocks = false;
   5411    return false;
   5412  }
   5413 
   5414  // Bail if both blocks the same
   5415  if (IsSameBlockElement()) {
   5416    mCanJoinBlocks = true;  // XXX Anyway, Run() will ingore this case.
   5417    mFallbackToDeleteLeafContent = true;
   5418    return true;
   5419  }
   5420 
   5421  // Joining a list item to its parent is a NOP.
   5422  if (HTMLEditUtils::IsListElement(*mLeftBlockElement) &&
   5423      HTMLEditUtils::IsListItemElement(*mRightBlockElement) &&
   5424      mRightBlockElement->GetParentNode() == mLeftBlockElement) {
   5425    mCanJoinBlocks = false;
   5426    return true;
   5427  }
   5428 
   5429  // Special rule here: if we are trying to join list items, and they are in
   5430  // different lists, join the lists instead.
   5431  if (HTMLEditUtils::IsListItemElement(*mLeftBlockElement) &&
   5432      HTMLEditUtils::IsListItemElement(*mRightBlockElement)) {
   5433    // XXX leftListElement and/or rightListElement may be not list elements.
   5434    Element* leftListElement = mLeftBlockElement->GetParentElement();
   5435    Element* rightListElement = mRightBlockElement->GetParentElement();
   5436    EditorDOMPoint atChildInBlock;
   5437    if (leftListElement && rightListElement &&
   5438        leftListElement != rightListElement &&
   5439        !EditorUtils::IsDescendantOf(*leftListElement, *mRightBlockElement,
   5440                                     &atChildInBlock) &&
   5441        !EditorUtils::IsDescendantOf(*rightListElement, *mLeftBlockElement,
   5442                                     &atChildInBlock)) {
   5443      // There are some special complications if the lists are descendants of
   5444      // the other lists' items.  Note that it is okay for them to be
   5445      // descendants of the other lists themselves, which is the usual case for
   5446      // sublists in our implementation.
   5447      MOZ_DIAGNOSTIC_ASSERT(!atChildInBlock.IsSet());
   5448      mLeftBlockElement = leftListElement;
   5449      mRightBlockElement = rightListElement;
   5450      mNewListElementTagNameOfRightListElement =
   5451          Some(leftListElement->NodeInfo()->NameAtom());
   5452    }
   5453  }
   5454 
   5455  if (!EditorUtils::IsDescendantOf(*mLeftBlockElement, *mRightBlockElement,
   5456                                   &mPointContainingTheOtherBlockElement)) {
   5457    (void)EditorUtils::IsDescendantOf(*mRightBlockElement, *mLeftBlockElement,
   5458                                      &mPointContainingTheOtherBlockElement);
   5459  }
   5460 
   5461  if (mPointContainingTheOtherBlockElement.GetContainer() ==
   5462      mRightBlockElement) {
   5463    mPrecedingInvisibleBRElement =
   5464        WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
   5465            {WSRunScanner::Option::OnlyEditableNodes},
   5466            EditorDOMPoint::AtEndOf(mLeftBlockElement));
   5467    // `WhiteSpaceVisibilityKeeper::
   5468    // MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`
   5469    // returns ignored when:
   5470    // - No preceding invisible `<br>` element and
   5471    // - mNewListElementTagNameOfRightListElement is nothing and
   5472    // - There is no content to move from right block element.
   5473    if (!mPrecedingInvisibleBRElement) {
   5474      if (CanMergeLeftAndRightBlockElements()) {
   5475        // Always marked as handled in this case.
   5476        mFallbackToDeleteLeafContent = false;
   5477      } else {
   5478        // Marked as handled only when it actually moves a content node.
   5479        Result<bool, nsresult> firstLineHasContent =
   5480            AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
   5481                mPointContainingTheOtherBlockElement
   5482                    .NextPoint<EditorDOMPoint>(),
   5483                aEditingHost);
   5484        mFallbackToDeleteLeafContent =
   5485            firstLineHasContent.isOk() && !firstLineHasContent.inspect();
   5486      }
   5487    } else {
   5488      // Marked as handled when deleting the invisible `<br>` element.
   5489      mFallbackToDeleteLeafContent = false;
   5490    }
   5491  } else if (mPointContainingTheOtherBlockElement.GetContainer() ==
   5492             mLeftBlockElement) {
   5493    mPrecedingInvisibleBRElement =
   5494        WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
   5495            {WSRunScanner::Option::OnlyEditableNodes},
   5496            mPointContainingTheOtherBlockElement);
   5497    // `WhiteSpaceVisibilityKeeper::
   5498    // MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()`
   5499    // returns ignored when:
   5500    // - No preceding invisible `<br>` element and
   5501    // - mNewListElementTagNameOfRightListElement is some and
   5502    // - The right block element has no children
   5503    // or,
   5504    // - No preceding invisible `<br>` element and
   5505    // - mNewListElementTagNameOfRightListElement is nothing and
   5506    // - There is no content to move from right block element.
   5507    if (!mPrecedingInvisibleBRElement) {
   5508      if (CanMergeLeftAndRightBlockElements()) {
   5509        // Marked as handled only when it actualy moves a content node.
   5510        Result<bool, nsresult> rightBlockHasContent =
   5511            aHTMLEditor.CanMoveChildren(*mRightBlockElement,
   5512                                        *mLeftBlockElement);
   5513        mFallbackToDeleteLeafContent =
   5514            rightBlockHasContent.isOk() && !rightBlockHasContent.inspect();
   5515      } else {
   5516        // Marked as handled only when it actually moves a content node.
   5517        Result<bool, nsresult> firstLineHasContent =
   5518            AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
   5519                EditorDOMPoint(mRightBlockElement, 0u), aEditingHost);
   5520        mFallbackToDeleteLeafContent =
   5521            firstLineHasContent.isOk() && !firstLineHasContent.inspect();
   5522      }
   5523    } else {
   5524      // Marked as handled when deleting the invisible `<br>` element.
   5525      mFallbackToDeleteLeafContent = false;
   5526    }
   5527  } else {
   5528    mPrecedingInvisibleBRElement =
   5529        WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
   5530            {WSRunScanner::Option::OnlyEditableNodes},
   5531            EditorDOMPoint::AtEndOf(mLeftBlockElement));
   5532    // `WhiteSpaceVisibilityKeeper::
   5533    // MergeFirstLineOfRightBlockElementIntoLeftBlockElement()` always
   5534    // return "handled".
   5535    mFallbackToDeleteLeafContent = false;
   5536  }
   5537 
   5538  mCanJoinBlocks = true;
   5539  return true;
   5540 }
   5541 
   5542 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
   5543    AutoInclusiveAncestorBlockElementsJoiner::ComputeRangeToDelete(
   5544        const HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCaretPoint,
   5545        nsRange& aRangeToDelete) const {
   5546  MOZ_ASSERT(mLeftBlockElement);
   5547  MOZ_ASSERT(mRightBlockElement);
   5548 
   5549  if (IsSameBlockElement()) {
   5550    if (!aCaretPoint.IsSet()) {
   5551      return NS_OK;  // The ranges are not collapsed, keep them as-is.
   5552    }
   5553    nsresult rv = aRangeToDelete.CollapseTo(aCaretPoint.ToRawRangeBoundary());
   5554    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::CollapseTo() failed");
   5555    return rv;
   5556  }
   5557 
   5558  EditorDOMPoint pointContainingTheOtherBlock;
   5559  if (!EditorUtils::IsDescendantOf(*mLeftBlockElement, *mRightBlockElement,
   5560                                   &pointContainingTheOtherBlock)) {
   5561    (void)EditorUtils::IsDescendantOf(*mRightBlockElement, *mLeftBlockElement,
   5562                                      &pointContainingTheOtherBlock);
   5563  }
   5564  EditorDOMRange range =
   5565      WSRunScanner::GetRangeForDeletingBlockElementBoundaries(
   5566          {WSRunScanner::Option::OnlyEditableNodes}, *mLeftBlockElement,
   5567          *mRightBlockElement, pointContainingTheOtherBlock);
   5568  if (!range.IsPositioned()) {
   5569    NS_WARNING(
   5570        "WSRunScanner::GetRangeForDeletingBlockElementBoundaries() failed");
   5571    return NS_ERROR_FAILURE;
   5572  }
   5573  if (!aCaretPoint.IsSet()) {
   5574    // Don't shrink the original range.
   5575    bool noNeedToChangeStart = false;
   5576    const EditorDOMPoint atStart(aRangeToDelete.StartRef());
   5577    if (atStart.IsBefore(range.StartRef())) {
   5578      // If the range starts from end of a container, and computed block
   5579      // boundaries range starts from an invisible `<br>` element,  we
   5580      // may need to shrink the range.
   5581      Element* editingHost = aHTMLEditor.ComputeEditingHost();
   5582      NS_WARNING_ASSERTION(editingHost, "There was no editing host");
   5583      nsIContent* nextContent =
   5584          atStart.IsEndOfContainer() && range.StartRef().GetChild() &&
   5585                  HTMLEditUtils::IsInvisibleBRElement(
   5586                      *range.StartRef().GetChild())
   5587              ? HTMLEditUtils::GetNextContent(
   5588                    *atStart.ContainerAs<nsIContent>(),
   5589                    {WalkTreeOption::IgnoreDataNodeExceptText,
   5590                     WalkTreeOption::StopAtBlockBoundary},
   5591                    BlockInlineCheck::UseComputedDisplayOutsideStyle,
   5592                    editingHost)
   5593              : nullptr;
   5594      if (!nextContent || nextContent != range.StartRef().GetChild()) {
   5595        noNeedToChangeStart = true;
   5596        range.SetStart(EditorRawDOMPoint(aRangeToDelete.StartRef()));
   5597      }
   5598    }
   5599    if (range.EndRef().IsBefore(EditorRawDOMPoint(aRangeToDelete.EndRef()))) {
   5600      if (noNeedToChangeStart) {
   5601        return NS_OK;  // We don't need to modify the range.
   5602      }
   5603      range.SetEnd(EditorRawDOMPoint(aRangeToDelete.EndRef()));
   5604    }
   5605  }
   5606  nsresult rv =
   5607      aRangeToDelete.SetStartAndEnd(range.StartRef().ToRawRangeBoundary(),
   5608                                    range.EndRef().ToRawRangeBoundary());
   5609  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   5610                       "AutoClonedRangeArray::SetStartAndEnd() failed");
   5611  return rv;
   5612 }
   5613 
   5614 Result<DeleteRangeResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   5615    AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner::Run(
   5616        HTMLEditor& aHTMLEditor, const Element& aEditingHost) {
   5617  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   5618  MOZ_ASSERT(mLeftBlockElement);
   5619  MOZ_ASSERT(mRightBlockElement);
   5620 
   5621  if (IsSameBlockElement() || !mCanJoinBlocks) {
   5622    return DeleteRangeResult::IgnoredResult();
   5623  }
   5624 
   5625  const auto ConvertMoveNodeResultToDeleteRangeResult =
   5626      [](const EditorDOMPoint& aStartOfRightContent,
   5627         MoveNodeResult&& aMoveNodeResult, const Element& aEditingHost)
   5628          MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
   5629      -> Result<DeleteRangeResult, nsresult> {
   5630    aMoveNodeResult.IgnoreCaretPointSuggestion();
   5631    if (MOZ_UNLIKELY(aMoveNodeResult.Ignored())) {
   5632      return DeleteRangeResult::IgnoredResult();
   5633    }
   5634    EditorDOMRange movedLineRange = aMoveNodeResult.UnwrapMovedContentRange();
   5635    EditorDOMPoint maybeDeepStartOfRightContent;
   5636    if (MOZ_LIKELY(movedLineRange.IsPositioned())) {
   5637      if (const Element* const firstMovedElement =
   5638              movedLineRange.StartRef().GetChildAs<Element>()) {
   5639        maybeDeepStartOfRightContent =
   5640            HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
   5641                *firstMovedElement,
   5642                {EditablePointOption::RecognizeInvisibleWhiteSpaces,
   5643                 EditablePointOption::StopAtComment});
   5644      } else {
   5645        maybeDeepStartOfRightContent = movedLineRange.StartRef();
   5646      }
   5647    } else {
   5648      maybeDeepStartOfRightContent = aStartOfRightContent;
   5649    }
   5650    if (NS_WARN_IF(
   5651            !maybeDeepStartOfRightContent.IsSetAndValidInComposedDoc())) {
   5652      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   5653    }
   5654    // We should put caret to end of preceding text node if there is.
   5655    // Then, users can type text into it like the other browsers.
   5656    auto pointToPutCaret = [&]() -> EditorDOMPoint {
   5657      const WSScanResult maybePreviousText =
   5658          WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   5659              {}, maybeDeepStartOfRightContent, &aEditingHost);
   5660      if (maybePreviousText.IsContentEditable() &&
   5661          maybePreviousText.InVisibleOrCollapsibleCharacters()) {
   5662        return maybePreviousText.PointAfterReachedContent<EditorDOMPoint>();
   5663      }
   5664      return maybeDeepStartOfRightContent;
   5665    }();
   5666    return DeleteRangeResult(std::move(movedLineRange),
   5667                             std::move(pointToPutCaret));
   5668  };
   5669 
   5670  // If the left block element is in the right block element, move the hard
   5671  // line including the right block element to end of the left block.
   5672  // However, if we are merging list elements, we don't join them.
   5673  if (mPointContainingTheOtherBlockElement.GetContainer() ==
   5674      mRightBlockElement) {
   5675    EditorDOMPoint startOfRightContent =
   5676        mPointContainingTheOtherBlockElement.NextPoint();
   5677    if (const Element* const element =
   5678            startOfRightContent.GetChildAs<Element>()) {
   5679      startOfRightContent =
   5680          HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
   5681              *element, {EditablePointOption::RecognizeInvisibleWhiteSpaces,
   5682                         EditablePointOption::StopAtComment});
   5683    }
   5684    AutoTrackDOMPoint trackStartOfRightBlock(aHTMLEditor.RangeUpdaterRef(),
   5685                                             &startOfRightContent);
   5686    Result<MoveNodeResult, nsresult> moveFirstLineResult =
   5687        WhiteSpaceVisibilityKeeper::
   5688            MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
   5689                aHTMLEditor, MOZ_KnownLive(*mLeftBlockElement),
   5690                MOZ_KnownLive(*mRightBlockElement),
   5691                mPointContainingTheOtherBlockElement,
   5692                mNewListElementTagNameOfRightListElement,
   5693                MOZ_KnownLive(mPrecedingInvisibleBRElement), aEditingHost);
   5694    if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) {
   5695      NS_WARNING(
   5696          "WhiteSpaceVisibilityKeeper::"
   5697          "MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement() "
   5698          "failed");
   5699      return moveFirstLineResult.propagateErr();
   5700    }
   5701 
   5702    trackStartOfRightBlock.FlushAndStopTracking();
   5703    return ConvertMoveNodeResultToDeleteRangeResult(
   5704        startOfRightContent, moveFirstLineResult.unwrap(), aEditingHost);
   5705  }
   5706 
   5707  // If the right block element is in the left block element:
   5708  // - move list item elements in the right block element to where the left
   5709  //   list element is
   5710  // - or first hard line in the right block element to where:
   5711  //   - the left block element is.
   5712  //   - or the given left content in the left block is.
   5713  if (mPointContainingTheOtherBlockElement.GetContainer() ==
   5714      mLeftBlockElement) {
   5715    EditorDOMPoint startOfRightContent =
   5716        HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
   5717            *mRightBlockElement,
   5718            {EditablePointOption::RecognizeInvisibleWhiteSpaces,
   5719             EditablePointOption::StopAtComment});
   5720    AutoTrackDOMPoint trackStartOfRightBlock(aHTMLEditor.RangeUpdaterRef(),
   5721                                             &startOfRightContent);
   5722    Result<MoveNodeResult, nsresult> moveFirstLineResult =
   5723        WhiteSpaceVisibilityKeeper::
   5724            MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
   5725                aHTMLEditor, MOZ_KnownLive(*mLeftBlockElement),
   5726                MOZ_KnownLive(*mRightBlockElement),
   5727                mPointContainingTheOtherBlockElement,
   5728                MOZ_KnownLive(*mInclusiveDescendantOfLeftBlockElement),
   5729                mNewListElementTagNameOfRightListElement,
   5730                MOZ_KnownLive(mPrecedingInvisibleBRElement), aEditingHost);
   5731    if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) {
   5732      NS_WARNING(
   5733          "WhiteSpaceVisibilityKeeper::"
   5734          "MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement() "
   5735          "failed");
   5736      return moveFirstLineResult.propagateErr();
   5737    }
   5738    trackStartOfRightBlock.FlushAndStopTracking();
   5739    return ConvertMoveNodeResultToDeleteRangeResult(
   5740        startOfRightContent, moveFirstLineResult.unwrap(), aEditingHost);
   5741  }
   5742 
   5743  // Normal case.  Blocks are siblings, or at least close enough.  An example
   5744  // of the latter is <p>paragraph</p><ul><li>one<li>two<li>three</ul>.  The
   5745  // first li and the p are not true siblings, but we still want to join them
   5746  // if you backspace from li into p.
   5747  MOZ_ASSERT(!mPointContainingTheOtherBlockElement.IsSet());
   5748  EditorDOMPoint startOfRightContent =
   5749      HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
   5750          *mRightBlockElement,
   5751          {EditablePointOption::RecognizeInvisibleWhiteSpaces,
   5752           EditablePointOption::StopAtComment});
   5753  AutoTrackDOMPoint trackStartOfRightBlock(aHTMLEditor.RangeUpdaterRef(),
   5754                                           &startOfRightContent);
   5755  Result<MoveNodeResult, nsresult> moveFirstLineResult =
   5756      WhiteSpaceVisibilityKeeper::
   5757          MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
   5758              aHTMLEditor, MOZ_KnownLive(*mLeftBlockElement),
   5759              MOZ_KnownLive(*mRightBlockElement),
   5760              mNewListElementTagNameOfRightListElement,
   5761              MOZ_KnownLive(mPrecedingInvisibleBRElement), aEditingHost);
   5762  if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) {
   5763    NS_WARNING(
   5764        "WhiteSpaceVisibilityKeeper::"
   5765        "MergeFirstLineOfRightBlockElementIntoLeftBlockElement() failed");
   5766    return moveFirstLineResult.propagateErr();
   5767  }
   5768  trackStartOfRightBlock.FlushAndStopTracking();
   5769  return ConvertMoveNodeResultToDeleteRangeResult(
   5770      startOfRightContent, moveFirstLineResult.unwrap(), aEditingHost);
   5771 }
   5772 
   5773 // static
   5774 Result<bool, nsresult>
   5775 HTMLEditor::AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
   5776    const EditorDOMPoint& aPointInHardLine, const Element& aEditingHost) {
   5777  if (NS_WARN_IF(!aPointInHardLine.IsSet()) ||
   5778      NS_WARN_IF(aPointInHardLine.IsInNativeAnonymousSubtree())) {
   5779    return Err(NS_ERROR_INVALID_ARG);
   5780  }
   5781 
   5782  RefPtr<nsRange> oneLineRange = AutoClonedRangeArray::
   5783      CreateRangeWrappingStartAndEndLinesContainingBoundaries(
   5784          aPointInHardLine, aPointInHardLine,
   5785          EditSubAction::eMergeBlockContents,
   5786          BlockInlineCheck::UseComputedDisplayOutsideStyle, aEditingHost);
   5787  if (!oneLineRange || oneLineRange->Collapsed() ||
   5788      !oneLineRange->IsPositioned() ||
   5789      !oneLineRange->GetStartContainer()->IsContent() ||
   5790      !oneLineRange->GetEndContainer()->IsContent()) {
   5791    return false;
   5792  }
   5793 
   5794  // If there is only a padding `<br>` element in a empty block, it's selected
   5795  // by `UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement()`.
   5796  // However, it won't be moved.  Although it'll be deleted,
   5797  // AutoMoveOneLineHandler returns "ignored".  Therefore, we should return
   5798  // `false` in this case.
   5799  if (nsIContent* childContent = oneLineRange->GetChildAtStartOffset()) {
   5800    if (childContent->IsHTMLElement(nsGkAtoms::br) &&
   5801        childContent->GetParent()) {
   5802      if (const Element* blockElement =
   5803              HTMLEditUtils::GetInclusiveAncestorElement(
   5804                  *childContent->GetParent(),
   5805                  HTMLEditUtils::ClosestBlockElement,
   5806                  BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
   5807        if (HTMLEditUtils::IsEmptyNode(
   5808                *blockElement,
   5809                {EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
   5810          return false;
   5811        }
   5812      }
   5813    }
   5814  }
   5815 
   5816  EditorRawDOMPoint startPoint(oneLineRange->StartRef());
   5817  EditorRawDOMPoint endPoint(oneLineRange->EndRef());
   5818  // If the range contains only block start boundaries, there is no content to
   5819  // move.
   5820  if (nsIContent* const startContent = startPoint.GetChild()) {
   5821    if (HTMLEditUtils::IsBlockElement(
   5822            *startContent, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
   5823      const WSScanResult prevThing =
   5824          WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary({}, endPoint);
   5825      if (prevThing.ReachedCurrentBlockBoundary() &&
   5826          prevThing.ElementPtr()->IsInclusiveDescendantOf(startContent)) {
   5827        return false;
   5828      }
   5829    }
   5830  }
   5831 
   5832  nsINode* commonAncestor = oneLineRange->GetClosestCommonInclusiveAncestor();
   5833  // Currently, we move non-editable content nodes too.
   5834  if (!startPoint.IsEndOfContainer()) {
   5835    return true;
   5836  }
   5837  if (!endPoint.IsStartOfContainer()) {
   5838    return true;
   5839  }
   5840  if (startPoint.GetContainer() != commonAncestor) {
   5841    while (true) {
   5842      EditorRawDOMPoint pointInParent(startPoint.GetContainerAs<nsIContent>());
   5843      if (NS_WARN_IF(!pointInParent.IsInContentNode())) {
   5844        return Err(NS_ERROR_FAILURE);
   5845      }
   5846      if (pointInParent.GetContainer() == commonAncestor) {
   5847        startPoint = pointInParent;
   5848        break;
   5849      }
   5850      if (!pointInParent.IsEndOfContainer()) {
   5851        return true;
   5852      }
   5853    }
   5854  }
   5855  if (endPoint.GetContainer() != commonAncestor) {
   5856    while (true) {
   5857      EditorRawDOMPoint pointInParent(endPoint.GetContainerAs<nsIContent>());
   5858      if (NS_WARN_IF(!pointInParent.IsInContentNode())) {
   5859        return Err(NS_ERROR_FAILURE);
   5860      }
   5861      if (pointInParent.GetContainer() == commonAncestor) {
   5862        endPoint = pointInParent;
   5863        break;
   5864      }
   5865      if (!pointInParent.IsStartOfContainer()) {
   5866        return true;
   5867      }
   5868    }
   5869  }
   5870  // If start point and end point in the common ancestor are direct siblings,
   5871  // there is no content to move or delete.
   5872  // E.g., `<b>abc<br>[</b><i>]<br>def</i>`.
   5873  return startPoint.GetNextSiblingOfChild() != endPoint.GetChild();
   5874 }
   5875 
   5876 nsresult HTMLEditor::AutoMoveOneLineHandler::Prepare(
   5877    HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointInHardLine,
   5878    const Element& aEditingHost) {
   5879  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   5880  MOZ_ASSERT(aPointInHardLine.IsInContentNode());
   5881  MOZ_ASSERT(mPointToInsert.IsSetAndValid());
   5882 
   5883  MOZ_LOG(gOneLineMoverLog, LogLevel::Info,
   5884          ("Prepare(aHTMLEditor=%p, aPointInHardLine=%s, aEditingHost=%s), "
   5885           "mPointToInsert=%s, mMoveToEndOfContainer=%s",
   5886           &aHTMLEditor, ToString(aPointInHardLine).c_str(),
   5887           ToString(aEditingHost).c_str(), ToString(mPointToInsert).c_str(),
   5888           ForceMoveToEndOfContainer() ? "MoveToEndOfContainer::Yes"
   5889                                       : "MoveToEndOfContainer::No"));
   5890 
   5891  if (NS_WARN_IF(mPointToInsert.IsInNativeAnonymousSubtree())) {
   5892    MOZ_LOG(
   5893        gOneLineMoverLog, LogLevel::Error,
   5894        ("Failed because mPointToInsert was in a native anonymous subtree"));
   5895    return Err(NS_ERROR_INVALID_ARG);
   5896  }
   5897 
   5898  mSrcInclusiveAncestorBlock =
   5899      aPointInHardLine.IsInContentNode()
   5900          ? HTMLEditUtils::GetInclusiveAncestorElement(
   5901                *aPointInHardLine.ContainerAs<nsIContent>(),
   5902                HTMLEditUtils::ClosestBlockElement,
   5903                BlockInlineCheck::UseComputedDisplayOutsideStyle)
   5904          : nullptr;
   5905  mDestInclusiveAncestorBlock =
   5906      mPointToInsert.IsInContentNode()
   5907          ? HTMLEditUtils::GetInclusiveAncestorElement(
   5908                *mPointToInsert.ContainerAs<nsIContent>(),
   5909                HTMLEditUtils::ClosestBlockElement,
   5910                BlockInlineCheck::UseComputedDisplayOutsideStyle)
   5911          : nullptr;
   5912  mMovingToParentBlock =
   5913      mDestInclusiveAncestorBlock && mSrcInclusiveAncestorBlock &&
   5914      mDestInclusiveAncestorBlock != mSrcInclusiveAncestorBlock &&
   5915      mSrcInclusiveAncestorBlock->IsInclusiveDescendantOf(
   5916          mDestInclusiveAncestorBlock);
   5917  mTopmostSrcAncestorBlockInDestBlock =
   5918      mMovingToParentBlock
   5919          ? AutoMoveOneLineHandler::
   5920                GetMostDistantInclusiveAncestorBlockInSpecificAncestorElement(
   5921                    *mSrcInclusiveAncestorBlock, *mDestInclusiveAncestorBlock)
   5922          : nullptr;
   5923  MOZ_ASSERT_IF(mMovingToParentBlock, mTopmostSrcAncestorBlockInDestBlock);
   5924 
   5925  mPreserveWhiteSpaceStyle =
   5926      AutoMoveOneLineHandler::ConsiderWhetherPreserveWhiteSpaceStyle(
   5927          aPointInHardLine.GetContainerAs<nsIContent>(),
   5928          mDestInclusiveAncestorBlock);
   5929 
   5930  AutoClonedRangeArray rangesToWrapTheLine(aPointInHardLine);
   5931  rangesToWrapTheLine.ExtendRangesToWrapLines(
   5932      EditSubAction::eMergeBlockContents,
   5933      BlockInlineCheck::UseComputedDisplayOutsideStyle,
   5934      mTopmostSrcAncestorBlockInDestBlock ? *mTopmostSrcAncestorBlockInDestBlock
   5935                                          : aEditingHost);
   5936  MOZ_ASSERT(rangesToWrapTheLine.Ranges().Length() <= 1u);
   5937  mLineRange = EditorDOMRange(rangesToWrapTheLine.FirstRangeRef());
   5938 
   5939  MOZ_LOG(gOneLineMoverLog, LogLevel::Info,
   5940          ("mSrcInclusiveAncestorBlock=%s, mDestInclusiveAncestorBlock=%s, "
   5941           "mMovingToParentBlock=%s, mTopmostSrcAncestorBlockInDestBlock=%s, "
   5942           "mPreserveWhiteSpaceStyle=%s, mLineRange=%s",
   5943           mSrcInclusiveAncestorBlock
   5944               ? ToString(*mSrcInclusiveAncestorBlock).c_str()
   5945               : "nullptr",
   5946           mDestInclusiveAncestorBlock
   5947               ? ToString(*mDestInclusiveAncestorBlock).c_str()
   5948               : "nullptr",
   5949           mMovingToParentBlock ? "true" : "false",
   5950           mTopmostSrcAncestorBlockInDestBlock
   5951               ? ToString(*mTopmostSrcAncestorBlockInDestBlock).c_str()
   5952               : "nullptr",
   5953           ToString(mPreserveWhiteSpaceStyle).c_str(),
   5954           ToString(mLineRange).c_str()));
   5955 
   5956  return NS_OK;
   5957 }
   5958 
   5959 Result<CaretPoint, nsresult>
   5960 HTMLEditor::AutoMoveOneLineHandler::SplitToMakeTheLineIsolated(
   5961    HTMLEditor& aHTMLEditor, const nsIContent& aNewContainer,
   5962    const Element& aEditingHost,
   5963    nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents) const {
   5964  AutoClonedRangeArray rangesToWrapTheLine(mLineRange);
   5965  Result<EditorDOMPoint, nsresult> splitResult =
   5966      rangesToWrapTheLine
   5967          .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
   5968              aHTMLEditor, BlockInlineCheck::UseComputedDisplayOutsideStyle,
   5969              aEditingHost, &aNewContainer);
   5970  if (MOZ_UNLIKELY(splitResult.isErr())) {
   5971    NS_WARNING(
   5972        "AutoClonedRangeArray::"
   5973        "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() failed");
   5974    return Err(splitResult.unwrapErr());
   5975  }
   5976  EditorDOMPoint pointToPutCaret;
   5977  if (splitResult.inspect().IsSet()) {
   5978    pointToPutCaret = splitResult.unwrap();
   5979  }
   5980  nsresult rv = rangesToWrapTheLine.CollectEditTargetNodes(
   5981      aHTMLEditor, aOutArrayOfContents, EditSubAction::eMergeBlockContents,
   5982      AutoClonedRangeArray::CollectNonEditableNodes::Yes);
   5983  if (NS_FAILED(rv)) {
   5984    NS_WARNING(
   5985        "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::"
   5986        "eMergeBlockContents, CollectNonEditableNodes::Yes) failed");
   5987    return Err(rv);
   5988  }
   5989  return CaretPoint(pointToPutCaret);
   5990 }
   5991 
   5992 // static
   5993 Element* HTMLEditor::AutoMoveOneLineHandler::
   5994    GetMostDistantInclusiveAncestorBlockInSpecificAncestorElement(
   5995        Element& aBlockElement, const Element& aAncestorElement) {
   5996  MOZ_ASSERT(aBlockElement.IsInclusiveDescendantOf(&aAncestorElement));
   5997  MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
   5998      aBlockElement, BlockInlineCheck::UseComputedDisplayOutsideStyle));
   5999 
   6000  if (&aBlockElement == &aAncestorElement) {
   6001    return nullptr;
   6002  }
   6003 
   6004  Element* lastBlockAncestor = &aBlockElement;
   6005  for (Element* element : aBlockElement.InclusiveAncestorsOfType<Element>()) {
   6006    if (element == &aAncestorElement) {
   6007      return lastBlockAncestor;
   6008    }
   6009    if (HTMLEditUtils::IsBlockElement(
   6010            *lastBlockAncestor,
   6011            BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
   6012      lastBlockAncestor = element;
   6013    }
   6014  }
   6015  return nullptr;
   6016 }
   6017 
   6018 // static
   6019 HTMLEditor::PreserveWhiteSpaceStyle
   6020 HTMLEditor::AutoMoveOneLineHandler::ConsiderWhetherPreserveWhiteSpaceStyle(
   6021    const nsIContent* aContentInLine,
   6022    const Element* aInclusiveAncestorBlockOfInsertionPoint) {
   6023  if (MOZ_UNLIKELY(!aInclusiveAncestorBlockOfInsertionPoint)) {
   6024    return PreserveWhiteSpaceStyle::No;
   6025  }
   6026 
   6027  // If we move content from or to <pre>, we don't need to preserve the
   6028  // white-space style for compatibility with both our traditional behavior
   6029  // and the other browsers.
   6030 
   6031  // TODO: If `white-space` is specified by non-UA stylesheet, we should
   6032  // preserve it even if the right block is <pre> for compatibility with the
   6033  // other browsers.
   6034  const auto IsInclusiveDescendantOfPre = [](const nsIContent& aContent) {
   6035    // If the content has different `white-space` style from <pre>, we
   6036    // shouldn't treat it as a descendant of <pre> because web apps or
   6037    // the user intent to treat the white-spaces in aContent not as `pre`.
   6038    if (EditorUtils::GetComputedWhiteSpaceStyles(aContent).valueOr(std::pair(
   6039            StyleWhiteSpaceCollapse::Collapse, StyleTextWrapMode::Wrap)) !=
   6040        std::pair(StyleWhiteSpaceCollapse::Preserve,
   6041                  StyleTextWrapMode::Nowrap)) {
   6042      return false;
   6043    }
   6044    for (const Element* element :
   6045         aContent.InclusiveAncestorsOfType<Element>()) {
   6046      if (element->IsHTMLElement(nsGkAtoms::pre)) {
   6047        return true;
   6048      }
   6049    }
   6050    return false;
   6051  };
   6052  if (IsInclusiveDescendantOfPre(*aInclusiveAncestorBlockOfInsertionPoint) ||
   6053      MOZ_UNLIKELY(!aContentInLine) ||
   6054      IsInclusiveDescendantOfPre(*aContentInLine)) {
   6055    return PreserveWhiteSpaceStyle::No;
   6056  }
   6057  return PreserveWhiteSpaceStyle::Yes;
   6058 }
   6059 
   6060 Result<MoveNodeResult, nsresult> HTMLEditor::AutoMoveOneLineHandler::Run(
   6061    HTMLEditor& aHTMLEditor, const Element& aEditingHost) {
   6062  EditorDOMPoint pointToInsert(NextInsertionPointRef());
   6063  MOZ_ASSERT(pointToInsert.IsInContentNode());
   6064 
   6065  MOZ_LOG(
   6066      gOneLineMoverLog, LogLevel::Info,
   6067      ("Run(aHTMLEditor=%p, aEditingHost=%s), pointToInsert=%s", &aHTMLEditor,
   6068       ToString(aEditingHost).c_str(), ToString(pointToInsert).c_str()));
   6069 
   6070  EditorDOMPoint pointToPutCaret;
   6071  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
   6072  {
   6073    AutoTrackDOMPoint tackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
   6074                                        &pointToInsert);
   6075 
   6076    Result<CaretPoint, nsresult> splitAtLineEdgesResult =
   6077        SplitToMakeTheLineIsolated(
   6078            aHTMLEditor,
   6079            MOZ_KnownLive(*pointToInsert.ContainerAs<nsIContent>()),
   6080            aEditingHost, arrayOfContents);
   6081    if (MOZ_UNLIKELY(splitAtLineEdgesResult.isErr())) {
   6082      NS_WARNING("AutoMoveOneLineHandler::SplitToMakeTheLineIsolated() failed");
   6083      MOZ_LOG(gOneLineMoverLog, LogLevel::Error,
   6084              ("Run: SplitToMakeTheLineIsolated() failed"));
   6085      return splitAtLineEdgesResult.propagateErr();
   6086    }
   6087    splitAtLineEdgesResult.unwrap().MoveCaretPointTo(
   6088        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   6089    MOZ_LOG(gOneLineMoverLog, LogLevel::Verbose,
   6090            ("Run: pointToPutCaret=%s", ToString(pointToPutCaret).c_str()));
   6091 
   6092    Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
   6093        aHTMLEditor.MaybeSplitElementsAtEveryBRElement(
   6094            arrayOfContents, EditSubAction::eMergeBlockContents);
   6095    if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
   6096      NS_WARNING(
   6097          "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
   6098          "eMergeBlockContents) failed");
   6099      MOZ_LOG(gOneLineMoverLog, LogLevel::Error,
   6100              ("Run: MaybeSplitElementsAtEveryBRElement() failed"));
   6101      return splitAtBRElementsResult.propagateErr();
   6102    }
   6103    if (splitAtBRElementsResult.inspect().IsSet()) {
   6104      pointToPutCaret = splitAtBRElementsResult.unwrap();
   6105    }
   6106    MOZ_LOG(gOneLineMoverLog, LogLevel::Verbose,
   6107            ("Run: pointToPutCaret=%s", ToString(pointToPutCaret).c_str()));
   6108  }
   6109 
   6110  if (!pointToInsert.IsSetAndValid()) {
   6111    MOZ_LOG(gOneLineMoverLog, LogLevel::Error,
   6112            ("Run: Failed because pointToInsert pointed invalid position"));
   6113    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   6114  }
   6115 
   6116  if (aHTMLEditor.AllowsTransactionsToChangeSelection() &&
   6117      pointToPutCaret.IsSet()) {
   6118    nsresult rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret);
   6119    if (NS_FAILED(rv)) {
   6120      NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   6121      MOZ_LOG(gOneLineMoverLog, LogLevel::Error,
   6122              ("Run: Failed because of "
   6123               "aHTMLEditor.CollapseSelectionTo(pointToPutCaret) failure"));
   6124      return Err(rv);
   6125    }
   6126  }
   6127 
   6128  if (arrayOfContents.IsEmpty()) {
   6129    MOZ_LOG(gOneLineMoverLog, LogLevel::Info,
   6130            ("Run: Did nothing because of no content to be moved"));
   6131    return MoveNodeResult::IgnoredResult(std::move(pointToInsert));
   6132  }
   6133 
   6134  // Track the range which contains the moved contents.
   6135  if (ForceMoveToEndOfContainer()) {
   6136    pointToInsert = NextInsertionPointRef();
   6137  }
   6138  EditorDOMRange movedContentRange(pointToInsert);
   6139  MoveNodeResult moveContentsInLineResult =
   6140      MoveNodeResult::IgnoredResult(pointToInsert);
   6141  for (const OwningNonNull<nsIContent>& content : arrayOfContents) {
   6142    MOZ_LOG(gOneLineMoverLog, LogLevel::Info,
   6143            ("Run: content=%s, pointToInsert=%s, movedContentRange=%s, "
   6144             "mPointToInsert=%s",
   6145             ToString(content.ref()).c_str(), ToString(pointToInsert).c_str(),
   6146             ToString(movedContentRange).c_str(),
   6147             ToString(mPointToInsert).c_str()));
   6148    {
   6149      AutoEditorDOMRangeChildrenInvalidator lockOffsets(movedContentRange);
   6150      AutoTrackDOMRange trackMovedContentRange(aHTMLEditor.RangeUpdaterRef(),
   6151                                               &movedContentRange);
   6152      // If the content is a block element, move all children of it to the
   6153      // new container, and then, remove the (probably) empty block element.
   6154      if (HTMLEditUtils::IsBlockElement(
   6155              content, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
   6156        MOZ_LOG(gOneLineMoverLog, LogLevel::Info,
   6157                ("Run: Unwrapping children of content because of a block"));
   6158        AutoTrackDOMMoveNodeResult trackMoveContentsInLineResult(
   6159            aHTMLEditor.RangeUpdaterRef(), &moveContentsInLineResult);
   6160        Result<MoveNodeResult, nsresult> moveChildrenResult =
   6161            aHTMLEditor.MoveChildrenWithTransaction(
   6162                MOZ_KnownLive(*content->AsElement()), pointToInsert,
   6163                mPreserveWhiteSpaceStyle, RemoveIfCommentNode::Yes);
   6164        if (MOZ_UNLIKELY(moveChildrenResult.isErr())) {
   6165          NS_WARNING("HTMLEditor::MoveChildrenWithTransaction() failed");
   6166          MOZ_LOG(gOneLineMoverLog, LogLevel::Error,
   6167                  ("Run: MoveChildrenWithTransaction() failed"));
   6168          moveContentsInLineResult.IgnoreCaretPointSuggestion();
   6169          return moveChildrenResult;
   6170        }
   6171        trackMoveContentsInLineResult.FlushAndStopTracking();
   6172        moveContentsInLineResult |= moveChildrenResult.inspect();
   6173        {
   6174          AutoTrackDOMMoveNodeResult trackMoveContentsInLineResult(
   6175              aHTMLEditor.RangeUpdaterRef(), &moveContentsInLineResult);
   6176          // MOZ_KnownLive due to bug 1620312
   6177          nsresult rv =
   6178              aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(content));
   6179          if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   6180            MOZ_LOG(gOneLineMoverLog, LogLevel::Error,
   6181                    ("Run: Aborted because DeleteNodeWithTransaction() caused "
   6182                     "destroying the editor"));
   6183            moveContentsInLineResult.IgnoreCaretPointSuggestion();
   6184            return Err(NS_ERROR_EDITOR_DESTROYED);
   6185          }
   6186          if (NS_FAILED(rv)) {
   6187            NS_WARNING(
   6188                "EditorBase::DeleteNodeWithTransaction() failed, but ignored");
   6189            MOZ_LOG(
   6190                gOneLineMoverLog, LogLevel::Warning,
   6191                ("Run: Failed to delete content but the error was ignored"));
   6192          }
   6193        }
   6194      }
   6195      // If the moving content is a comment node or an empty inline node, we
   6196      // don't want it to appear in the dist paragraph.
   6197      else if (content->IsComment() ||
   6198               (content->IsText() && !content->AsText()->TextDataLength()) ||
   6199               HTMLEditUtils::IsEmptyInlineContainer(
   6200                   content,
   6201                   {EmptyCheckOption::TreatSingleBRElementAsVisible,
   6202                    EmptyCheckOption::TreatListItemAsVisible,
   6203                    EmptyCheckOption::TreatTableCellAsVisible,
   6204                    EmptyCheckOption::TreatNonEditableContentAsInvisible},
   6205                   BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
   6206        nsCOMPtr<nsIContent> emptyContent =
   6207            HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
   6208                content, BlockInlineCheck::UseComputedDisplayOutsideStyle,
   6209                &aEditingHost, pointToInsert.ContainerAs<nsIContent>());
   6210        if (!emptyContent) {
   6211          emptyContent = content;
   6212        }
   6213        MOZ_LOG(gOneLineMoverLog, LogLevel::Info,
   6214                ("Run: Deleting content because of %s%s",
   6215                 content->IsComment() ? "a comment node"
   6216                 : content->IsText()  ? "an empty text node"
   6217                                      : "an empty inline container",
   6218                 content != emptyContent
   6219                     ? nsPrintfCString(" (deleting topmost empty ancestor: %s)",
   6220                                       ToString(*emptyContent).c_str())
   6221                           .get()
   6222                     : ""));
   6223        AutoTrackDOMMoveNodeResult trackMoveContentsInLineResult(
   6224            aHTMLEditor.RangeUpdaterRef(), &moveContentsInLineResult);
   6225        nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(*emptyContent);
   6226        if (NS_FAILED(rv)) {
   6227          NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   6228          MOZ_LOG(gOneLineMoverLog, LogLevel::Error,
   6229                  ("Run: DeleteNodeWithTransaction() failed"));
   6230          moveContentsInLineResult.IgnoreCaretPointSuggestion();
   6231          return Err(rv);
   6232        }
   6233      } else {
   6234        MOZ_LOG(gOneLineMoverLog, LogLevel::Info, ("Run: Moving content"));
   6235        AutoTrackDOMMoveNodeResult trackMoveContentsInLineResult(
   6236            aHTMLEditor.RangeUpdaterRef(), &moveContentsInLineResult);
   6237        // MOZ_KnownLive due to bug 1620312
   6238        Result<MoveNodeResult, nsresult> moveNodeOrChildrenResult =
   6239            aHTMLEditor.MoveNodeOrChildrenWithTransaction(
   6240                MOZ_KnownLive(content), pointToInsert, mPreserveWhiteSpaceStyle,
   6241                RemoveIfCommentNode::Yes);
   6242        if (MOZ_UNLIKELY(moveNodeOrChildrenResult.isErr())) {
   6243          NS_WARNING("HTMLEditor::MoveNodeOrChildrenWithTransaction() failed");
   6244          MOZ_LOG(gOneLineMoverLog, LogLevel::Error,
   6245                  ("Run: MoveNodeOrChildrenWithTransaction() failed"));
   6246          moveContentsInLineResult.IgnoreCaretPointSuggestion();
   6247          return moveNodeOrChildrenResult;
   6248        }
   6249        trackMoveContentsInLineResult.FlushAndStopTracking();
   6250        moveContentsInLineResult |= moveNodeOrChildrenResult.inspect();
   6251      }
   6252    }
   6253    MOZ_LOG(gOneLineMoverLog, LogLevel::Info,
   6254            ("Run: movedContentRange=%s, mPointToInsert=%s",
   6255             ToString(movedContentRange).c_str(),
   6256             ToString(mPointToInsert).c_str()));
   6257    moveContentsInLineResult.ForceToMarkAsHandled();
   6258    if (NS_WARN_IF(!movedContentRange.IsPositioned())) {
   6259      MOZ_LOG(gOneLineMoverLog, LogLevel::Error,
   6260              ("Run: Failed because movedContentRange was not positioned"));
   6261      moveContentsInLineResult.IgnoreCaretPointSuggestion();
   6262      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   6263    }
   6264    // For backward compatibility, we should move contents to end of the
   6265    // container if the instance is created without specific insertion point.
   6266    if (ForceMoveToEndOfContainer()) {
   6267      pointToInsert = NextInsertionPointRef();
   6268      MOZ_ASSERT(pointToInsert.IsSet());
   6269      MOZ_ASSERT(movedContentRange.StartRef().EqualsOrIsBefore(pointToInsert));
   6270      movedContentRange.SetEnd(pointToInsert);
   6271      MOZ_LOG(gOneLineMoverLog, LogLevel::Debug,
   6272              ("Run: Updated movedContentRange end to next insertion point"));
   6273    }
   6274    // And also if pointToInsert has been made invalid with removing preceding
   6275    // children, we should move the content to the end of the container.
   6276    else if (aHTMLEditor.MaybeNodeRemovalsObservedByDevTools() &&
   6277             MOZ_UNLIKELY(!moveContentsInLineResult.NextInsertionPointRef()
   6278                               .IsSetAndValid())) {
   6279      mPointToInsert.SetToEndOf(mPointToInsert.GetContainer());
   6280      pointToInsert = NextInsertionPointRef();
   6281      movedContentRange.SetEnd(pointToInsert);
   6282      MOZ_LOG(gOneLineMoverLog, LogLevel::Debug,
   6283              ("Run: Updated mPointToInsert to end of container and updated "
   6284               "movedContentRange"));
   6285    } else {
   6286      MOZ_DIAGNOSTIC_ASSERT(
   6287          moveContentsInLineResult.NextInsertionPointRef().IsSet());
   6288      mPointToInsert = moveContentsInLineResult.NextInsertionPointRef();
   6289      pointToInsert = NextInsertionPointRef();
   6290      if (!aHTMLEditor.MaybeNodeRemovalsObservedByDevTools() ||
   6291          movedContentRange.EndRef().IsBefore(pointToInsert)) {
   6292        MOZ_ASSERT(pointToInsert.IsSet());
   6293        MOZ_ASSERT(
   6294            movedContentRange.StartRef().EqualsOrIsBefore(pointToInsert));
   6295        movedContentRange.SetEnd(pointToInsert);
   6296        MOZ_LOG(gOneLineMoverLog, LogLevel::Debug,
   6297                ("Run: Updated mPointToInsert and updated movedContentRange"));
   6298      } else {
   6299        MOZ_LOG(gOneLineMoverLog, LogLevel::Debug,
   6300                ("Run: Updated only mPointToInsert"));
   6301      }
   6302    }
   6303  }
   6304 
   6305  // Nothing has been moved, we don't need to clean up unnecessary <br> element.
   6306  // And also if we're not moving content into a block, we can quit right now.
   6307  if (moveContentsInLineResult.Ignored() ||
   6308      MOZ_UNLIKELY(!mDestInclusiveAncestorBlock)) {
   6309    MOZ_LOG(gOneLineMoverLog, LogLevel::Info,
   6310            (moveContentsInLineResult.Ignored()
   6311                 ? "Run: Did nothing for any children"
   6312                 : "Run: Finished (not dest block)"));
   6313    return std::move(moveContentsInLineResult);
   6314  }
   6315 
   6316  // If we couldn't track the range to clean up, we should just stop cleaning up
   6317  // because returning error from here may change the behavior of web apps using
   6318  // mutation event listeners.
   6319  if (MOZ_UNLIKELY(!movedContentRange.IsPositioned() ||
   6320                   movedContentRange.Collapsed())) {
   6321    MOZ_LOG(gOneLineMoverLog, LogLevel::Info,
   6322            (!movedContentRange.IsPositioned()
   6323                 ? "Run: Finished (Couldn't track moved line)"
   6324                 : "Run: Finished (Moved line was empty)"));
   6325    return std::move(moveContentsInLineResult);
   6326  }
   6327 
   6328  {
   6329    AutoTrackDOMMoveNodeResult trackMoveContentsInLineResult(
   6330        aHTMLEditor.RangeUpdaterRef(), &moveContentsInLineResult);
   6331    nsresult rv = DeleteUnnecessaryTrailingLineBreakInMovedLineEnd(
   6332        aHTMLEditor, movedContentRange, aEditingHost);
   6333    if (NS_FAILED(rv)) {
   6334      NS_WARNING(
   6335          "AutoMoveOneLineHandler::"
   6336          "DeleteUnnecessaryTrailingLineBreakInMovedLineEnd() failed");
   6337      MOZ_LOG(
   6338          gOneLineMoverLog, LogLevel::Error,
   6339          ("Run: DeleteUnnecessaryTrailingLineBreakInMovedLineEnd() failed"));
   6340      moveContentsInLineResult.IgnoreCaretPointSuggestion();
   6341      return Err(rv);
   6342    }
   6343  }
   6344 
   6345  MOZ_LOG(gOneLineMoverLog, LogLevel::Info, ("Run: Finished"));
   6346  return std::move(moveContentsInLineResult);
   6347 }
   6348 
   6349 nsresult HTMLEditor::AutoMoveOneLineHandler::
   6350    DeleteUnnecessaryTrailingLineBreakInMovedLineEnd(
   6351        HTMLEditor& aHTMLEditor, const EditorDOMRange& aMovedContentRange,
   6352        const Element& aEditingHost) const {
   6353  MOZ_ASSERT(mDestInclusiveAncestorBlock);
   6354  MOZ_ASSERT(aMovedContentRange.IsPositioned());
   6355  MOZ_ASSERT(!aMovedContentRange.Collapsed());
   6356 
   6357  // If we didn't preserve white-space for backward compatibility and
   6358  // white-space becomes not preformatted, we need to clean it up the last text
   6359  // node if it ends with a preformatted line break.
   6360  if (mPreserveWhiteSpaceStyle == PreserveWhiteSpaceStyle::No) {
   6361    const RefPtr<Text> textNodeEndingWithUnnecessaryLineBreak = [&]() -> Text* {
   6362      Text* lastTextNode = Text::FromNodeOrNull(
   6363          mMovingToParentBlock
   6364              ? HTMLEditUtils::GetPreviousContent(
   6365                    *mTopmostSrcAncestorBlockInDestBlock,
   6366                    {WalkTreeOption::StopAtBlockBoundary},
   6367                    BlockInlineCheck::UseComputedDisplayOutsideStyle,
   6368                    mDestInclusiveAncestorBlock)
   6369              : HTMLEditUtils::GetLastLeafContent(
   6370                    *mDestInclusiveAncestorBlock,
   6371                    {LeafNodeType::LeafNodeOrNonEditableNode}));
   6372      if (!lastTextNode ||
   6373          !HTMLEditUtils::IsSimplyEditableNode(*lastTextNode)) {
   6374        return nullptr;
   6375      }
   6376      const CharacterDataBuffer& characterDataBuffer =
   6377          lastTextNode->DataBuffer();
   6378      const char16_t lastCh =
   6379          characterDataBuffer.GetLength()
   6380              ? characterDataBuffer.CharAt(characterDataBuffer.GetLength() - 1u)
   6381              : 0;
   6382      return lastCh == HTMLEditUtils::kNewLine &&
   6383                     !EditorUtils::IsNewLinePreformatted(*lastTextNode)
   6384                 ? lastTextNode
   6385                 : nullptr;
   6386    }();
   6387    if (textNodeEndingWithUnnecessaryLineBreak) {
   6388      if (textNodeEndingWithUnnecessaryLineBreak->TextDataLength() == 1u) {
   6389        const RefPtr<Element> inlineElement =
   6390            HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
   6391                *textNodeEndingWithUnnecessaryLineBreak,
   6392                BlockInlineCheck::UseComputedDisplayOutsideStyle,
   6393                &aEditingHost);
   6394        nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(
   6395            inlineElement ? static_cast<nsIContent&>(*inlineElement)
   6396                          : static_cast<nsIContent&>(
   6397                                *textNodeEndingWithUnnecessaryLineBreak));
   6398        if (NS_FAILED(rv)) {
   6399          NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   6400          return Err(rv);
   6401        }
   6402      } else {
   6403        Result<CaretPoint, nsresult> caretPointOrError =
   6404            aHTMLEditor.DeleteTextWithTransaction(
   6405                *textNodeEndingWithUnnecessaryLineBreak,
   6406                textNodeEndingWithUnnecessaryLineBreak->TextDataLength() - 1u,
   6407                1u);
   6408        if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   6409          NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
   6410          return caretPointOrError.propagateErr();
   6411        }
   6412        nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo(
   6413            aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
   6414                          SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   6415                          SuggestCaret::AndIgnoreTrivialError});
   6416        if (NS_FAILED(rv)) {
   6417          NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
   6418          return Err(rv);
   6419        }
   6420        NS_WARNING_ASSERTION(
   6421            rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   6422            "CaretPoint::SuggestCaretPointTo() failed, but ignored");
   6423      }
   6424    }
   6425  }
   6426 
   6427  const Maybe<EditorLineBreak> lastLineBreak =
   6428      mMovingToParentBlock
   6429          ? HTMLEditUtils::GetUnnecessaryLineBreak<EditorLineBreak>(
   6430                *mTopmostSrcAncestorBlockInDestBlock,
   6431                ScanLineBreak::BeforeBlock)
   6432          : HTMLEditUtils::GetUnnecessaryLineBreak<EditorLineBreak>(
   6433                *mDestInclusiveAncestorBlock, ScanLineBreak::AtEndOfBlock);
   6434  if (lastLineBreak.isNothing() ||
   6435      !lastLineBreak->IsDeletableFromComposedDoc()) {
   6436    return NS_OK;
   6437  }
   6438  const auto atUnnecessaryLineBreak = lastLineBreak->To<EditorRawDOMPoint>();
   6439  if (NS_WARN_IF(!atUnnecessaryLineBreak.IsSet())) {
   6440    return NS_ERROR_FAILURE;
   6441  }
   6442  // If the found unnecessary line break is not what we moved above, we
   6443  // shouldn't remove it.  E.g., the web app may have inserted it intentionally.
   6444  MOZ_ASSERT(aMovedContentRange.StartRef().IsSetAndValid());
   6445  MOZ_ASSERT(aMovedContentRange.EndRef().IsSetAndValid());
   6446  if (!aMovedContentRange.Contains(atUnnecessaryLineBreak)) {
   6447    return NS_OK;
   6448  }
   6449 
   6450  AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
   6451  Result<EditorDOMPoint, nsresult> lineBreakPointOrError =
   6452      aHTMLEditor.DeleteLineBreakWithTransaction(
   6453          lastLineBreak.ref(),
   6454          aEditingHost.IsContentEditablePlainTextOnly() ? nsIEditor::eNoStrip
   6455                                                        : nsIEditor::eStrip,
   6456          aEditingHost);
   6457  if (MOZ_UNLIKELY(lineBreakPointOrError.isErr())) {
   6458    NS_WARNING("HTMLEditor::DeleteLineBreakWithTransaction() failed");
   6459    return lineBreakPointOrError.propagateErr();
   6460  }
   6461  return NS_OK;
   6462 }
   6463 
   6464 Result<bool, nsresult> HTMLEditor::CanMoveNodeOrChildren(
   6465    const nsIContent& aContent, const nsINode& aNewContainer) const {
   6466  if (HTMLEditUtils::CanNodeContain(aNewContainer, aContent)) {
   6467    return true;
   6468  }
   6469  if (aContent.IsElement()) {
   6470    return CanMoveChildren(*aContent.AsElement(), aNewContainer);
   6471  }
   6472  return true;
   6473 }
   6474 
   6475 Result<MoveNodeResult, nsresult> HTMLEditor::MoveNodeOrChildrenWithTransaction(
   6476    nsIContent& aContentToMove, const EditorDOMPoint& aPointToInsert,
   6477    PreserveWhiteSpaceStyle aPreserveWhiteSpaceStyle,
   6478    RemoveIfCommentNode aRemoveIfCommentNode) {
   6479  MOZ_ASSERT(IsEditActionDataAvailable());
   6480  MOZ_ASSERT(aPointToInsert.IsInContentNode());
   6481 
   6482  const auto destWhiteSpaceStyles =
   6483      [&]() -> Maybe<std::pair<StyleWhiteSpaceCollapse, StyleTextWrapMode>> {
   6484    if (aPreserveWhiteSpaceStyle == PreserveWhiteSpaceStyle::No ||
   6485        !aPointToInsert.IsInContentNode()) {
   6486      return Nothing();
   6487    }
   6488    auto styles = EditorUtils::GetComputedWhiteSpaceStyles(
   6489        *aPointToInsert.ContainerAs<nsIContent>());
   6490    if (NS_WARN_IF(styles.isSome() &&
   6491                   styles.value().first ==
   6492                       StyleWhiteSpaceCollapse::PreserveSpaces)) {
   6493      return Nothing();
   6494    }
   6495    return styles;
   6496  }();
   6497  const auto srcWhiteSpaceStyles =
   6498      [&]() -> Maybe<std::pair<StyleWhiteSpaceCollapse, StyleTextWrapMode>> {
   6499    if (aPreserveWhiteSpaceStyle == PreserveWhiteSpaceStyle::No) {
   6500      return Nothing();
   6501    }
   6502    auto styles = EditorUtils::GetComputedWhiteSpaceStyles(aContentToMove);
   6503    if (NS_WARN_IF(styles.isSome() &&
   6504                   styles.value().first ==
   6505                       StyleWhiteSpaceCollapse::PreserveSpaces)) {
   6506      return Nothing();
   6507    }
   6508    return styles;
   6509  }();
   6510  // Get the `white-space` shorthand form for the given collapse + mode pair.
   6511  const auto GetWhiteSpaceStyleValue =
   6512      [](std::pair<StyleWhiteSpaceCollapse, StyleTextWrapMode> aStyles) {
   6513        if (aStyles.second == StyleTextWrapMode::Wrap) {
   6514          switch (aStyles.first) {
   6515            case StyleWhiteSpaceCollapse::Collapse:
   6516              return u"normal"_ns;
   6517            case StyleWhiteSpaceCollapse::Preserve:
   6518              return u"pre-wrap"_ns;
   6519            case StyleWhiteSpaceCollapse::PreserveBreaks:
   6520              return u"pre-line"_ns;
   6521            case StyleWhiteSpaceCollapse::PreserveSpaces:
   6522              return u"preserve-spaces"_ns;
   6523            case StyleWhiteSpaceCollapse::BreakSpaces:
   6524              return u"break-spaces"_ns;
   6525          }
   6526        } else {
   6527          switch (aStyles.first) {
   6528            case StyleWhiteSpaceCollapse::Collapse:
   6529              return u"nowrap"_ns;
   6530            case StyleWhiteSpaceCollapse::Preserve:
   6531              return u"pre"_ns;
   6532            case StyleWhiteSpaceCollapse::PreserveBreaks:
   6533              return u"nowrap preserve-breaks"_ns;
   6534            case StyleWhiteSpaceCollapse::PreserveSpaces:
   6535              return u"nowrap preserve-spaces"_ns;
   6536            case StyleWhiteSpaceCollapse::BreakSpaces:
   6537              return u"nowrap break-spaces"_ns;
   6538          }
   6539        }
   6540        MOZ_ASSERT_UNREACHABLE("all values should be handled above!");
   6541        return u"normal"_ns;
   6542      };
   6543 
   6544  if (aRemoveIfCommentNode == RemoveIfCommentNode::Yes &&
   6545      aContentToMove.IsComment()) {
   6546    EditorDOMPoint pointToInsert(aPointToInsert);
   6547    {
   6548      AutoTrackDOMPoint trackPointToInsert(RangeUpdaterRef(), &pointToInsert);
   6549      nsresult rv = DeleteNodeWithTransaction(aContentToMove);
   6550      if (NS_FAILED(rv)) {
   6551        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   6552        return Err(rv);
   6553      }
   6554    }
   6555    if (NS_WARN_IF(!pointToInsert.IsSetAndValid())) {
   6556      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   6557    }
   6558    return MoveNodeResult::HandledResult(std::move(pointToInsert));
   6559  }
   6560 
   6561  // Check if this node can go into the destination node
   6562  if (HTMLEditUtils::CanNodeContain(*aPointToInsert.GetContainer(),
   6563                                    aContentToMove)) {
   6564    EditorDOMPoint pointToInsert(aPointToInsert);
   6565    // Preserve white-space in the new position with using `style` attribute.
   6566    // This is additional path from point of view of our traditional behavior.
   6567    // Therefore, ignore errors especially if we got unexpected DOM tree.
   6568    if (destWhiteSpaceStyles.isSome() && srcWhiteSpaceStyles.isSome() &&
   6569        destWhiteSpaceStyles.value() != srcWhiteSpaceStyles.value()) {
   6570      // Set `white-space` with `style` attribute if it's nsStyledElement.
   6571      if (nsStyledElement* styledElement =
   6572              nsStyledElement::FromNode(&aContentToMove)) {
   6573        DebugOnly<nsresult> rvIgnored =
   6574            CSSEditUtils::SetCSSPropertyWithTransaction(
   6575                *this, MOZ_KnownLive(*styledElement), *nsGkAtoms::white_space,
   6576                GetWhiteSpaceStyleValue(srcWhiteSpaceStyles.value()));
   6577        if (NS_WARN_IF(Destroyed())) {
   6578          return Err(NS_ERROR_EDITOR_DESTROYED);
   6579        }
   6580        NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   6581                             "CSSEditUtils::SetCSSPropertyWithTransaction("
   6582                             "nsGkAtoms::white_space) failed, but ignored");
   6583      }
   6584      // Otherwise, if the dest container can have <span> element and <span>
   6585      // element can have the moving content node, we should insert it.
   6586      else if (HTMLEditUtils::CanNodeContain(*aPointToInsert.GetContainer(),
   6587                                             *nsGkAtoms::span) &&
   6588               HTMLEditUtils::CanNodeContain(*nsGkAtoms::span,
   6589                                             aContentToMove)) {
   6590        RefPtr<Element> newSpanElement = CreateHTMLContent(nsGkAtoms::span);
   6591        if (NS_WARN_IF(!newSpanElement)) {
   6592          return Err(NS_ERROR_FAILURE);
   6593        }
   6594        nsAutoString styleAttrValue(u"white-space: "_ns);
   6595        styleAttrValue.Append(
   6596            GetWhiteSpaceStyleValue(srcWhiteSpaceStyles.value()));
   6597        IgnoredErrorResult error;
   6598        newSpanElement->SetAttr(nsGkAtoms::style, styleAttrValue, error);
   6599        NS_WARNING_ASSERTION(!error.Failed(),
   6600                             "Element::SetAttr(nsGkAtoms::span) failed");
   6601        if (MOZ_LIKELY(!error.Failed())) {
   6602          Result<CreateElementResult, nsresult> insertSpanElementResult =
   6603              InsertNodeWithTransaction<Element>(*newSpanElement,
   6604                                                 aPointToInsert);
   6605          if (MOZ_UNLIKELY(insertSpanElementResult.isErr())) {
   6606            if (NS_WARN_IF(insertSpanElementResult.inspectErr() ==
   6607                           NS_ERROR_EDITOR_DESTROYED)) {
   6608              return Err(NS_ERROR_EDITOR_DESTROYED);
   6609            }
   6610            NS_WARNING(
   6611                "HTMLEditor::InsertNodeWithTransaction() failed, but ignored");
   6612          } else {
   6613            // We should move the node into the new <span> to preserve the
   6614            // style.
   6615            pointToInsert.Set(newSpanElement, 0u);
   6616            // We should put caret after aContentToMove after moving it so that
   6617            // we do not need the suggested caret point here.
   6618            insertSpanElementResult.inspect().IgnoreCaretPointSuggestion();
   6619          }
   6620        }
   6621      }
   6622    }
   6623    // If it can, move it there.
   6624    Result<MoveNodeResult, nsresult> moveNodeResult =
   6625        MoveNodeWithTransaction(aContentToMove, pointToInsert);
   6626    NS_WARNING_ASSERTION(moveNodeResult.isOk(),
   6627                         "HTMLEditor::MoveNodeWithTransaction() failed");
   6628    // XXX This is odd to override the handled state here, but stopping this
   6629    //     hits an NS_ASSERTION in WhiteSpaceVisibilityKeeper::
   6630    //     MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement.
   6631    if (moveNodeResult.isOk()) {
   6632      MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
   6633      unwrappedMoveNodeResult.ForceToMarkAsHandled();
   6634      return unwrappedMoveNodeResult;
   6635    }
   6636    return moveNodeResult;
   6637  }
   6638 
   6639  // If it can't, move its children (if any), and then delete it.
   6640  auto moveNodeResult =
   6641      [&]() MOZ_CAN_RUN_SCRIPT -> Result<MoveNodeResult, nsresult> {
   6642    if (!aContentToMove.IsElement()) {
   6643      return MoveNodeResult::HandledResult(aPointToInsert);
   6644    }
   6645    Result<MoveNodeResult, nsresult> moveChildrenResult =
   6646        MoveChildrenWithTransaction(MOZ_KnownLive(*aContentToMove.AsElement()),
   6647                                    aPointToInsert, aPreserveWhiteSpaceStyle,
   6648                                    aRemoveIfCommentNode);
   6649    NS_WARNING_ASSERTION(moveChildrenResult.isOk(),
   6650                         "HTMLEditor::MoveChildrenWithTransaction() failed");
   6651    return moveChildrenResult;
   6652  }();
   6653  if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
   6654    return moveNodeResult;  // Already warned in the lambda.
   6655  }
   6656 
   6657  MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
   6658  {
   6659    AutoTrackDOMMoveNodeResult trackMoveNodeResult(RangeUpdaterRef(),
   6660                                                   &unwrappedMoveNodeResult);
   6661    nsresult rv = DeleteNodeWithTransaction(aContentToMove);
   6662    if (NS_FAILED(rv)) {
   6663      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   6664      unwrappedMoveNodeResult.IgnoreCaretPointSuggestion();
   6665      return Err(rv);
   6666    }
   6667  }
   6668  if (!MaybeNodeRemovalsObservedByDevTools()) {
   6669    return std::move(unwrappedMoveNodeResult);
   6670  }
   6671  // Mutation event listener may make `offset` value invalid with
   6672  // removing some previous children while we call
   6673  // `DeleteNodeWithTransaction()` so that we should adjust it here.
   6674  if (unwrappedMoveNodeResult.NextInsertionPointRef()
   6675          .IsSetAndValidInComposedDoc()) {
   6676    return std::move(unwrappedMoveNodeResult);
   6677  }
   6678  unwrappedMoveNodeResult |= MoveNodeResult::HandledResult(
   6679      EditorDOMPoint::AtEndOf(*aPointToInsert.GetContainer()));
   6680  return std::move(unwrappedMoveNodeResult);
   6681 }
   6682 
   6683 Result<bool, nsresult> HTMLEditor::CanMoveChildren(
   6684    const Element& aElement, const nsINode& aNewContainer) const {
   6685  if (NS_WARN_IF(&aElement == &aNewContainer)) {
   6686    return Err(NS_ERROR_FAILURE);
   6687  }
   6688  for (nsIContent* childContent = aElement.GetFirstChild(); childContent;
   6689       childContent = childContent->GetNextSibling()) {
   6690    Result<bool, nsresult> result =
   6691        CanMoveNodeOrChildren(*childContent, aNewContainer);
   6692    if (result.isErr() || result.inspect()) {
   6693      return result;
   6694    }
   6695  }
   6696  return false;
   6697 }
   6698 
   6699 Result<MoveNodeResult, nsresult> HTMLEditor::MoveChildrenWithTransaction(
   6700    Element& aElement, const EditorDOMPoint& aPointToInsert,
   6701    PreserveWhiteSpaceStyle aPreserveWhiteSpaceStyle,
   6702    RemoveIfCommentNode aRemoveIfCommentNode) {
   6703  MOZ_ASSERT(aPointToInsert.IsSet());
   6704 
   6705  if (NS_WARN_IF(&aElement == aPointToInsert.GetContainer())) {
   6706    return Err(NS_ERROR_INVALID_ARG);
   6707  }
   6708 
   6709  MoveNodeResult moveChildrenResult =
   6710      MoveNodeResult::IgnoredResult(aPointToInsert);
   6711  while (nsCOMPtr<nsIContent> firstChild = aElement.GetFirstChild()) {
   6712    AutoTrackDOMMoveNodeResult trackMoveChildrenResult(RangeUpdaterRef(),
   6713                                                       &moveChildrenResult);
   6714    Result<MoveNodeResult, nsresult> moveNodeOrChildrenResult =
   6715        MoveNodeOrChildrenWithTransaction(
   6716            *firstChild, moveChildrenResult.NextInsertionPointRef(),
   6717            aPreserveWhiteSpaceStyle, aRemoveIfCommentNode);
   6718    if (MOZ_UNLIKELY(moveNodeOrChildrenResult.isErr())) {
   6719      NS_WARNING("HTMLEditor::MoveNodeOrChildrenWithTransaction() failed");
   6720      moveChildrenResult.IgnoreCaretPointSuggestion();
   6721      return moveNodeOrChildrenResult;
   6722    }
   6723    trackMoveChildrenResult.FlushAndStopTracking();
   6724    moveChildrenResult |= moveNodeOrChildrenResult.inspect();
   6725  }
   6726  return moveChildrenResult;
   6727 }
   6728 
   6729 nsresult HTMLEditor::MoveAllChildren(nsINode& aContainer,
   6730                                     const EditorRawDOMPoint& aPointToInsert) {
   6731  if (!aContainer.HasChildren()) {
   6732    return NS_OK;
   6733  }
   6734  nsIContent* const firstChild = aContainer.GetFirstChild();
   6735  if (NS_WARN_IF(!firstChild)) {
   6736    return NS_ERROR_FAILURE;
   6737  }
   6738  nsIContent* const lastChild = aContainer.GetLastChild();
   6739  if (NS_WARN_IF(!lastChild)) {
   6740    return NS_ERROR_FAILURE;
   6741  }
   6742  nsresult rv = MoveChildrenBetween(
   6743      // MOZ_KnownLive is okay for here because MoveChildrenBetween() will grab
   6744      // all children with an array of strong pointer.  Therefore, we don't need
   6745      // to guarantee the lifetime of firstChild and lastChild here unless we'd
   6746      // start to refer after this call.
   6747      MOZ_KnownLive(*firstChild), MOZ_KnownLive(*lastChild), aPointToInsert);
   6748  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   6749                       "HTMLEditor::MoveChildrenBetween() failed");
   6750  return rv;
   6751 }
   6752 
   6753 nsresult HTMLEditor::MoveChildrenBetween(
   6754    nsIContent& aFirstChild, nsIContent& aLastChild,
   6755    const EditorRawDOMPoint& aPointToInsert) {
   6756  nsCOMPtr<nsINode> oldContainer = aFirstChild.GetParentNode();
   6757  if (NS_WARN_IF(oldContainer != aLastChild.GetParentNode()) ||
   6758      NS_WARN_IF(!aPointToInsert.IsInContentNode()) ||
   6759      NS_WARN_IF(!aPointToInsert.CanContainerHaveChildren())) {
   6760    return NS_ERROR_INVALID_ARG;
   6761  }
   6762 
   6763  // First, store all children which should be moved to the new container.
   6764  AutoTArray<nsCOMPtr<nsIContent>, 10> children;
   6765  for (nsIContent* child = &aFirstChild; child;
   6766       child = child->GetNextSibling()) {
   6767    children.AppendElement(child);
   6768    if (child == &aLastChild) {
   6769      break;
   6770    }
   6771  }
   6772 
   6773  if (NS_WARN_IF(children.LastElement() != &aLastChild)) {
   6774    return NS_ERROR_INVALID_ARG;
   6775  }
   6776 
   6777  const nsCOMPtr<nsIContent> newContainer =
   6778      aPointToInsert.ContainerAs<nsIContent>();
   6779  nsCOMPtr<nsIContent> nextNode = aPointToInsert.GetChild();
   6780  for (size_t i = children.Length(); i > 0; --i) {
   6781    nsCOMPtr<nsIContent>& child = children[i - 1];
   6782    if (child->GetParentNode() != oldContainer) {
   6783      // If the child has been moved to different container, we shouldn't
   6784      // touch it.
   6785      continue;
   6786    }
   6787    if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*child))) {
   6788      return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
   6789    }
   6790    {
   6791      nsresult rv = AutoNodeAPIWrapper(*this, *oldContainer)
   6792                        .RemoveChild(MOZ_KnownLive(*child));
   6793      if (NS_FAILED(rv)) {
   6794        NS_WARNING("AutoNodeAPIWrapper::RemoveChild() failed");
   6795        return rv;
   6796      }
   6797    }
   6798    if (NS_WARN_IF(nextNode && nextNode->GetParentNode() != newContainer) ||
   6799        NS_WARN_IF(newContainer->IsInComposedDoc() &&
   6800                   !HTMLEditUtils::IsSimplyEditableNode(*newContainer))) {
   6801      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   6802    }
   6803    {
   6804      nsresult rv = AutoNodeAPIWrapper(*this, *newContainer)
   6805                        .InsertBefore(MOZ_KnownLive(*child), nextNode);
   6806      if (NS_FAILED(rv)) {
   6807        NS_WARNING("AutoNodeAPIWrapper::InsertBefore() failed");
   6808        return rv;
   6809      }
   6810    }
   6811    // If the child was inserted or appended properly, the following children
   6812    // should be inserted before it.  Otherwise, keep using current position.
   6813    if (child->GetParentNode() == newContainer) {
   6814      nextNode = child;
   6815    }
   6816  }
   6817  return NS_OK;
   6818 }
   6819 
   6820 nsresult HTMLEditor::MovePreviousSiblings(
   6821    nsIContent& aChild, const EditorRawDOMPoint& aPointToInsert) {
   6822  if (NS_WARN_IF(!aChild.GetParentNode())) {
   6823    return NS_ERROR_INVALID_ARG;
   6824  }
   6825  nsIContent* const firstChild = aChild.GetParentNode()->GetFirstChild();
   6826  if (NS_WARN_IF(!firstChild)) {
   6827    return NS_ERROR_FAILURE;
   6828  }
   6829  nsIContent* const lastChild =
   6830      &aChild == firstChild ? firstChild : aChild.GetPreviousSibling();
   6831  if (NS_WARN_IF(!lastChild)) {
   6832    return NS_ERROR_FAILURE;
   6833  }
   6834  nsresult rv = MoveChildrenBetween(
   6835      // MOZ_KnownLive is okay for here because MoveChildrenBetween() will grab
   6836      // all children with an array of strong pointer.  Therefore, we don't need
   6837      // to guarantee the lifetime of firstChild and lastChild here unless we'd
   6838      // start to refer after this call.
   6839      MOZ_KnownLive(*firstChild), MOZ_KnownLive(*lastChild), aPointToInsert);
   6840  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   6841                       "HTMLEditor::MoveChildrenBetween() failed");
   6842  return rv;
   6843 }
   6844 
   6845 nsresult HTMLEditor::MoveInclusiveNextSiblings(
   6846    nsIContent& aChild, const EditorRawDOMPoint& aPointToInsert) {
   6847  if (NS_WARN_IF(!aChild.GetParentNode())) {
   6848    return NS_ERROR_INVALID_ARG;
   6849  }
   6850  nsIContent* const lastChild = aChild.GetParentNode()->GetLastChild();
   6851  if (NS_WARN_IF(!lastChild)) {
   6852    return NS_ERROR_FAILURE;
   6853  }
   6854  nsresult rv = MoveChildrenBetween(
   6855      // MOZ_KnownLive is okay for here because MoveChildrenBetween() will grab
   6856      // all children with an array of strong pointer.  Therefore, we don't need
   6857      // to guarantee the lifetime of lastChild here unless we'd start to refer
   6858      // after this call.
   6859      aChild, MOZ_KnownLive(*lastChild), aPointToInsert);
   6860  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   6861                       "HTMLEditor::MoveChildrenBetween() failed");
   6862  return rv;
   6863 }
   6864 
   6865 Result<DeleteRangeResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   6866    AutoBlockElementsJoiner::DeleteContentButKeepTableStructure(
   6867        HTMLEditor& aHTMLEditor, nsIContent& aContent) {
   6868  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   6869 
   6870  if (!HTMLEditUtils::IsAnyTableElementExceptTableElementAndColumElement(
   6871          aContent)) {
   6872    nsCOMPtr<nsINode> parentNode = aContent.GetParentNode();
   6873    if (NS_WARN_IF(!parentNode)) {
   6874      return Err(NS_ERROR_FAILURE);
   6875    }
   6876    nsCOMPtr<nsIContent> nextSibling = aContent.GetNextSibling();
   6877    nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aContent);
   6878    if (NS_FAILED(rv)) {
   6879      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   6880      return Err(rv);
   6881    }
   6882    if (NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parentNode) ||
   6883        NS_WARN_IF(!parentNode->IsInComposedDoc())) {
   6884      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   6885    }
   6886    return DeleteRangeResult(
   6887        EditorDOMRange(nextSibling ? EditorDOMPoint(nextSibling)
   6888                                   : EditorDOMPoint::AtEndOf(*parentNode)),
   6889        EditorDOMPoint());
   6890  }
   6891 
   6892  // XXX For performance, this should just call
   6893  //     DeleteContentButKeepTableStructure() while there are children in
   6894  //     aContent.  If we need to avoid infinite loop because mutation event
   6895  //     listeners can add unexpected nodes into aContent, we should just loop
   6896  //     only original count of the children.
   6897  AutoTArray<OwningNonNull<nsIContent>, 10> childList;
   6898  for (nsIContent* child = aContent.GetFirstChild(); child;
   6899       child = child->GetNextSibling()) {
   6900    childList.AppendElement(*child);
   6901  }
   6902 
   6903  for (const auto& child : childList) {
   6904    // MOZ_KnownLive because 'childList' is guaranteed to
   6905    // keep it alive.
   6906    Result<DeleteRangeResult, nsresult> deleteChildResult =
   6907        DeleteContentButKeepTableStructure(aHTMLEditor, MOZ_KnownLive(child));
   6908    if (MOZ_UNLIKELY(deleteChildResult.isErr())) {
   6909      NS_WARNING("HTMLEditor::DeleteContentButKeepTableStructure() failed");
   6910      return deleteChildResult.propagateErr();
   6911    }
   6912    deleteChildResult.unwrap().IgnoreCaretPointSuggestion();
   6913  }
   6914  if (NS_WARN_IF(!aContent.IsInComposedDoc())) {
   6915    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   6916  }
   6917 
   6918  // Insert a <br> into new empty table cell or caption because we don't have a
   6919  // change to do it for the middle of the range.  Note that this does not
   6920  // handle first cell/caption and end cell/caption at the deleting range. They
   6921  // should be handled by upper level because we may need to delete unnecessary
   6922  // new empty inline ancestors in the cells/captions.
   6923  if (!HTMLEditUtils::IsTableCellOrCaptionElement(aContent) ||
   6924      aContent.GetChildCount()) {
   6925    return DeleteRangeResult(EditorDOMRange(EditorDOMPoint(&aContent, 0u),
   6926                                            EditorDOMPoint::AtEndOf(aContent)),
   6927                             EditorDOMPoint());
   6928  }
   6929  Result<CreateLineBreakResult, nsresult> insertLineBreakResultOrError =
   6930      aHTMLEditor.InsertLineBreak(WithTransaction::Yes,
   6931                                  LineBreakType::BRElement,
   6932                                  EditorDOMPoint(&aContent, 0));
   6933  if (MOZ_UNLIKELY(insertLineBreakResultOrError.isErr())) {
   6934    NS_WARNING(
   6935        "HTMLEditor::InsertLineBreak(WithTransaction::Yes, "
   6936        "LineBreakType::BRElement) failed");
   6937    return insertLineBreakResultOrError.propagateErr();
   6938  }
   6939  CreateLineBreakResult insertLineBreakResult =
   6940      insertLineBreakResultOrError.unwrap();
   6941  insertLineBreakResult.IgnoreCaretPointSuggestion();
   6942  return DeleteRangeResult(EditorDOMRange(EditorDOMPoint(&aContent, 0u)),
   6943                           EditorDOMPoint());
   6944 }
   6945 
   6946 nsresult HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty(
   6947    nsIContent& aContent) {
   6948  MOZ_ASSERT(IsEditActionDataAvailable());
   6949 
   6950  // The element must be `<blockquote type="cite">` or
   6951  // `<span _moz_quote="true">`.
   6952  RefPtr<Element> mailCiteElement =
   6953      GetMostDistantAncestorMailCiteElement(aContent);
   6954  if (!mailCiteElement) {
   6955    return NS_OK;
   6956  }
   6957  bool seenBR = false;
   6958  if (!HTMLEditUtils::IsEmptyNode(
   6959          *mailCiteElement,
   6960          {EmptyCheckOption::TreatListItemAsVisible,
   6961           EmptyCheckOption::TreatTableCellAsVisible,
   6962           EmptyCheckOption::TreatNonEditableContentAsInvisible},
   6963          &seenBR)) {
   6964    return NS_OK;
   6965  }
   6966  EditorDOMPoint atEmptyMailCiteElement(mailCiteElement);
   6967  {
   6968    AutoEditorDOMPointChildInvalidator lockOffset(atEmptyMailCiteElement);
   6969    nsresult rv = DeleteNodeWithTransaction(*mailCiteElement);
   6970    if (NS_FAILED(rv)) {
   6971      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   6972      return rv;
   6973    }
   6974  }
   6975 
   6976  if (!atEmptyMailCiteElement.IsSet() || !seenBR) {
   6977    NS_WARNING_ASSERTION(
   6978        atEmptyMailCiteElement.IsSet(),
   6979        "Mutation event listener might changed the DOM tree during "
   6980        "EditorBase::DeleteNodeWithTransaction(), but ignored");
   6981    return NS_OK;
   6982  }
   6983 
   6984  Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError =
   6985      InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement,
   6986                      atEmptyMailCiteElement);
   6987  if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) {
   6988    NS_WARNING(
   6989        "HTMLEditor::InsertLineBreak(WithTransaction::Yes, "
   6990        "LineBreakType::BRElement) failed");
   6991    return insertBRElementResultOrError.unwrapErr();
   6992  }
   6993  CreateLineBreakResult insertBRElementResult =
   6994      insertBRElementResultOrError.unwrap();
   6995  MOZ_ASSERT(insertBRElementResult.Handled());
   6996  nsresult rv = insertBRElementResult.SuggestCaretPointTo(
   6997      *this, {SuggestCaret::AndIgnoreTrivialError});
   6998  if (NS_FAILED(rv)) {
   6999    NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
   7000    return rv;
   7001  }
   7002  NS_WARNING_ASSERTION(rv == NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   7003                       "CaretPoint::SuggestCaretPointTo() failed, but ignored");
   7004  return NS_OK;
   7005 }
   7006 
   7007 Element* HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::
   7008    ScanEmptyBlockInclusiveAncestor(const HTMLEditor& aHTMLEditor,
   7009                                    nsIContent& aStartContent) {
   7010  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   7011  MOZ_ASSERT(!mEmptyInclusiveAncestorBlockElement);
   7012 
   7013  // If we are inside an empty block, delete it.
   7014  // Note: do NOT delete table elements this way.
   7015  // Note: do NOT delete non-editable block element.
   7016  Element* editableBlockElement = HTMLEditUtils::GetInclusiveAncestorElement(
   7017      aStartContent, HTMLEditUtils::ClosestEditableBlockElement,
   7018      BlockInlineCheck::UseComputedDisplayOutsideStyle);
   7019  if (!editableBlockElement) {
   7020    return nullptr;
   7021  }
   7022  // XXX Perhaps, this is slow loop.  If empty blocks are nested, then,
   7023  //     each block checks whether it's empty or not.  However, descendant
   7024  //     blocks are checked again and again by IsEmptyNode().  Perhaps, it
   7025  //     should be able to take "known empty element" for avoiding same checks.
   7026  while (editableBlockElement &&
   7027         HTMLEditUtils::IsRemovableFromParentNode(*editableBlockElement) &&
   7028         !HTMLEditUtils::IsAnyTableElementExceptColumnElement(
   7029             *editableBlockElement) &&
   7030         HTMLEditUtils::IsEmptyNode(*editableBlockElement)) {
   7031    // If the removable empty list item is a child of editing host list element,
   7032    // we should not delete it.
   7033    if (HTMLEditUtils::IsListItemElement(*editableBlockElement)) {
   7034      Element* const parentElement = editableBlockElement->GetParentElement();
   7035      if (parentElement && HTMLEditUtils::IsListElement(*parentElement) &&
   7036          !HTMLEditUtils::IsRemovableFromParentNode(*parentElement) &&
   7037          HTMLEditUtils::IsEmptyNode(*parentElement)) {
   7038        break;
   7039      }
   7040    }
   7041    mEmptyInclusiveAncestorBlockElement = editableBlockElement;
   7042    editableBlockElement = HTMLEditUtils::GetAncestorElement(
   7043        *mEmptyInclusiveAncestorBlockElement,
   7044        HTMLEditUtils::ClosestEditableBlockElement,
   7045        BlockInlineCheck::UseComputedDisplayOutsideStyle);
   7046  }
   7047  if (!mEmptyInclusiveAncestorBlockElement) {
   7048    return nullptr;
   7049  }
   7050 
   7051  // XXX Because of not checking whether found block element is editable
   7052  //     in the above loop, empty ediable block element may be overwritten
   7053  //     with empty non-editable clock element.  Therefore, we fail to
   7054  //     remove the found empty nodes.
   7055  if (NS_WARN_IF(!mEmptyInclusiveAncestorBlockElement->IsEditable()) ||
   7056      NS_WARN_IF(!mEmptyInclusiveAncestorBlockElement->GetParentElement())) {
   7057    mEmptyInclusiveAncestorBlockElement = nullptr;
   7058  }
   7059  return mEmptyInclusiveAncestorBlockElement;
   7060 }
   7061 
   7062 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::
   7063    ComputeTargetRanges(const HTMLEditor& aHTMLEditor,
   7064                        nsIEditor::EDirection aDirectionAndAmount,
   7065                        const Element& aEditingHost,
   7066                        AutoClonedSelectionRangeArray& aRangesToDelete) const {
   7067  MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement);
   7068 
   7069  // We'll delete `mEmptyInclusiveAncestorBlockElement` node from the tree, but
   7070  // we should return the range from start/end of next/previous editable content
   7071  // to end/start of the element for compatiblity with the other browsers.
   7072  switch (aDirectionAndAmount) {
   7073    case nsIEditor::eNone:
   7074      break;
   7075    case nsIEditor::ePrevious:
   7076    case nsIEditor::ePreviousWord:
   7077    case nsIEditor::eToBeginningOfLine: {
   7078      EditorRawDOMPoint startPoint =
   7079          HTMLEditUtils::GetPreviousEditablePoint<EditorRawDOMPoint>(
   7080              *mEmptyInclusiveAncestorBlockElement, &aEditingHost,
   7081              // We'ill delete invisible white-spaces later.
   7082              InvisibleWhiteSpaces::Ignore,
   7083              // In this case, we won't join table cells so that we should
   7084              // get a range which is in a table cell even if it's in a
   7085              // table.
   7086              TableBoundary::NoCrossAnyTableElement);
   7087      if (!startPoint.IsSet()) {
   7088        NS_WARNING(
   7089            "HTMLEditUtils::GetPreviousEditablePoint() didn't return a valid "
   7090            "point");
   7091        return NS_ERROR_FAILURE;
   7092      }
   7093      nsresult rv = aRangesToDelete.SetStartAndEnd(
   7094          startPoint,
   7095          EditorRawDOMPoint::AtEndOf(mEmptyInclusiveAncestorBlockElement));
   7096      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   7097                           "AutoClonedRangeArray::SetStartAndEnd() failed");
   7098      return rv;
   7099    }
   7100    case nsIEditor::eNext:
   7101    case nsIEditor::eNextWord:
   7102    case nsIEditor::eToEndOfLine: {
   7103      EditorRawDOMPoint endPoint =
   7104          HTMLEditUtils::GetNextEditablePoint<EditorRawDOMPoint>(
   7105              *mEmptyInclusiveAncestorBlockElement, &aEditingHost,
   7106              // We'ill delete invisible white-spaces.
   7107              InvisibleWhiteSpaces::Ignore,
   7108              // In this case, we won't join table cells so that we should
   7109              // get a range which is in a table cell even if it's in a
   7110              // table.
   7111              TableBoundary::NoCrossAnyTableElement);
   7112      if (!endPoint.IsSet()) {
   7113        NS_WARNING(
   7114            "HTMLEditUtils::GetNextEditablePoint() didn't return a valid "
   7115            "point");
   7116        return NS_ERROR_FAILURE;
   7117      }
   7118      nsresult rv = aRangesToDelete.SetStartAndEnd(
   7119          EditorRawDOMPoint(mEmptyInclusiveAncestorBlockElement, 0), endPoint);
   7120      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   7121                           "AutoClonedRangeArray::SetStartAndEnd() failed");
   7122      return rv;
   7123    }
   7124    default:
   7125      MOZ_ASSERT_UNREACHABLE("Handle the nsIEditor::EDirection value");
   7126      break;
   7127  }
   7128  // No direction, let's select the element to be deleted.
   7129  nsresult rv =
   7130      aRangesToDelete.SelectNode(*mEmptyInclusiveAncestorBlockElement);
   7131  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   7132                       "AutoClonedRangeArray::SelectNode() failed");
   7133  return rv;
   7134 }
   7135 
   7136 Result<CreateLineBreakResult, nsresult>
   7137 HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::
   7138    MaybeInsertBRElementBeforeEmptyListItemElement(HTMLEditor& aHTMLEditor) {
   7139  MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement);
   7140  MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement->GetParentElement());
   7141  MOZ_ASSERT(
   7142      HTMLEditUtils::IsListItemElement(*mEmptyInclusiveAncestorBlockElement));
   7143 
   7144  // If the found empty block is a list item element and its grand parent
   7145  // (i.e., parent of list element) is NOT a list element, insert <br>
   7146  // element before the list element which has the empty list item.
   7147  // This odd list structure may occur if `Document.execCommand("indent")`
   7148  // is performed for list items.
   7149  // XXX Chrome does not remove empty list elements when last content in
   7150  //     last list item is deleted.  We should follow it since current
   7151  //     behavior is annoying when you type new list item with selecting
   7152  //     all list items.
   7153  if (!HTMLEditUtils::IsFirstChild(*mEmptyInclusiveAncestorBlockElement,
   7154                                   {WalkTreeOption::IgnoreNonEditableNode})) {
   7155    return CreateLineBreakResult::NotHandled();
   7156  }
   7157 
   7158  const EditorDOMPoint atParentOfEmptyListItem(
   7159      mEmptyInclusiveAncestorBlockElement->GetParentElement());
   7160  if (NS_WARN_IF(!atParentOfEmptyListItem.IsInContentNode())) {
   7161    return Err(NS_ERROR_FAILURE);
   7162  }
   7163  if (HTMLEditUtils::IsListElement(
   7164          *atParentOfEmptyListItem.ContainerAs<nsIContent>())) {
   7165    return CreateLineBreakResult::NotHandled();
   7166  }
   7167  Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError =
   7168      aHTMLEditor.InsertLineBreak(WithTransaction::Yes,
   7169                                  LineBreakType::BRElement,
   7170                                  atParentOfEmptyListItem);
   7171  if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) {
   7172    NS_WARNING(
   7173        "HTMLEditor::InsertLineBreak(WithTransaction::Yes, "
   7174        "LineBreakType::BRElement) failed");
   7175    return insertBRElementResultOrError.propagateErr();
   7176  }
   7177  CreateLineBreakResult insertBRElementResult =
   7178      insertBRElementResultOrError.unwrap();
   7179  nsresult rv = insertBRElementResult.SuggestCaretPointTo(
   7180      aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
   7181                    SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   7182                    SuggestCaret::AndIgnoreTrivialError});
   7183  if (NS_FAILED(rv)) {
   7184    NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
   7185    return Err(rv);
   7186  }
   7187  MOZ_ASSERT(insertBRElementResult.Handled());
   7188  return std::move(insertBRElementResult);
   7189 }
   7190 
   7191 Result<CaretPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   7192    AutoEmptyBlockAncestorDeleter::GetNewCaretPosition(
   7193        const HTMLEditor& aHTMLEditor,
   7194        nsIEditor::EDirection aDirectionAndAmount,
   7195        const Element& aEditingHost) const {
   7196  MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement);
   7197  MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement->GetParentElement());
   7198  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   7199 
   7200  switch (aDirectionAndAmount) {
   7201    case nsIEditor::eNext:
   7202    case nsIEditor::eNextWord:
   7203    case nsIEditor::eToEndOfLine: {
   7204      // Collapse Selection to next node of after empty block element
   7205      // if there is.  Otherwise, to just after the empty block.
   7206      nsIContent* const nextContentOfEmptyBlock = [&]() -> nsIContent* {
   7207        for (EditorRawDOMPoint scanStartPoint =
   7208                 EditorRawDOMPoint::After(mEmptyInclusiveAncestorBlockElement);
   7209             scanStartPoint.IsInContentNode();) {
   7210          nsIContent* const nextContent = HTMLEditUtils::GetNextContent(
   7211              scanStartPoint, {}, BlockInlineCheck::Unused, &aEditingHost);
   7212          // Let's ignore invisible `Text`.
   7213          if (nextContent && nextContent->IsText() &&
   7214              !HTMLEditUtils::IsVisibleTextNode(*nextContent->AsText())) {
   7215            scanStartPoint = EditorRawDOMPoint::After(*nextContent);
   7216            continue;
   7217          }
   7218          return nextContent;
   7219        }
   7220        return nullptr;
   7221      }();
   7222      if (nextContentOfEmptyBlock) {
   7223        EditorDOMPoint pt = HTMLEditUtils::GetGoodCaretPointFor<EditorDOMPoint>(
   7224            *nextContentOfEmptyBlock, aDirectionAndAmount);
   7225        if (!pt.IsSet()) {
   7226          NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed");
   7227          return Err(NS_ERROR_FAILURE);
   7228        }
   7229        return CaretPoint(std::move(pt));
   7230      }
   7231      EditorDOMPoint afterEmptyBlock =
   7232          EditorDOMPoint::After(mEmptyInclusiveAncestorBlockElement);
   7233      if (NS_WARN_IF(!afterEmptyBlock.IsSet())) {
   7234        return Err(NS_ERROR_FAILURE);
   7235      }
   7236      return CaretPoint(std::move(afterEmptyBlock));
   7237    }
   7238    case nsIEditor::ePrevious:
   7239    case nsIEditor::ePreviousWord:
   7240    case nsIEditor::eToBeginningOfLine: {
   7241      // Collapse Selection to previous editable node of the empty block
   7242      // if there is.
   7243      nsIContent* const previousContentOfEmptyBlock = [&]() -> nsIContent* {
   7244        for (EditorRawDOMPoint scanStartPoint =
   7245                 EditorRawDOMPoint(mEmptyInclusiveAncestorBlockElement);
   7246             scanStartPoint.IsInContentNode();) {
   7247          nsIContent* const previousContent = HTMLEditUtils::GetPreviousContent(
   7248              scanStartPoint, {WalkTreeOption::IgnoreNonEditableNode},
   7249              BlockInlineCheck::Unused, &aEditingHost);
   7250          // Let's ignore invisible `Text`.
   7251          if (previousContent && previousContent->IsText() &&
   7252              !HTMLEditUtils::IsVisibleTextNode(*previousContent->AsText())) {
   7253            scanStartPoint = EditorRawDOMPoint(previousContent, 0u);
   7254            continue;
   7255          }
   7256          return previousContent;
   7257        }
   7258        return nullptr;
   7259      }();
   7260      if (previousContentOfEmptyBlock) {
   7261        const EditorRawDOMPoint atEndOfPreviousContent =
   7262            HTMLEditUtils::GetGoodCaretPointFor<EditorRawDOMPoint>(
   7263                *previousContentOfEmptyBlock, aDirectionAndAmount);
   7264        if (!atEndOfPreviousContent.IsSet()) {
   7265          NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed");
   7266          return Err(NS_ERROR_FAILURE);
   7267        }
   7268        // If the previous content is between a preceding line break and the
   7269        // block boundary of current empty block, let's move caret to the line
   7270        // break if there is no visible things between them.
   7271        const Maybe<EditorRawLineBreak> precedingLineBreak =
   7272            HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem<
   7273                EditorRawLineBreak>(atEndOfPreviousContent, aEditingHost);
   7274        return precedingLineBreak.isSome()
   7275                   ? CaretPoint(precedingLineBreak->To<EditorDOMPoint>())
   7276                   : CaretPoint(atEndOfPreviousContent.To<EditorDOMPoint>());
   7277      }
   7278      // Otherwise, let's put caret next to the deleting block.
   7279      auto afterEmptyBlock =
   7280          EditorDOMPoint::After(*mEmptyInclusiveAncestorBlockElement);
   7281      if (NS_WARN_IF(!afterEmptyBlock.IsSet())) {
   7282        return Err(NS_ERROR_FAILURE);
   7283      }
   7284      return CaretPoint(std::move(afterEmptyBlock));
   7285    }
   7286    case nsIEditor::eNone: {
   7287      // Collapse selection at the removing block when we are replacing
   7288      // selected content.
   7289      EditorDOMPoint atEmptyBlock(mEmptyInclusiveAncestorBlockElement);
   7290      if (NS_WARN_IF(!atEmptyBlock.IsSet())) {
   7291        return Err(NS_ERROR_FAILURE);
   7292      }
   7293      return CaretPoint(std::move(atEmptyBlock));
   7294    }
   7295    default:
   7296      MOZ_CRASH(
   7297          "AutoEmptyBlockAncestorDeleter doesn't support this action yet");
   7298      return Err(NS_ERROR_FAILURE);
   7299  }
   7300 }
   7301 
   7302 Result<DeleteRangeResult, nsresult>
   7303 HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::Run(
   7304    HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   7305    const Element& aEditingHost) {
   7306  MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement);
   7307  MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement->GetParentElement());
   7308  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   7309 
   7310  {
   7311    Result<DeleteRangeResult, nsresult> replaceSubListResultOrError =
   7312        MaybeReplaceSubListWithNewListItem(aHTMLEditor);
   7313    if (MOZ_UNLIKELY(replaceSubListResultOrError.isErr())) {
   7314      NS_WARNING(
   7315          "AutoEmptyBlockAncestorDeleter::MaybeReplaceSubListWithNewListItem() "
   7316          "failed");
   7317      return replaceSubListResultOrError.propagateErr();
   7318    }
   7319    if (replaceSubListResultOrError.inspect().Handled()) {
   7320      return replaceSubListResultOrError;
   7321    }
   7322  }
   7323 
   7324  auto caretPointOrError = [&]() MOZ_CAN_RUN_SCRIPT MOZ_NEVER_INLINE_DEBUG
   7325      -> Result<CaretPoint, nsresult> {
   7326    if (HTMLEditUtils::IsListItemElement(
   7327            *mEmptyInclusiveAncestorBlockElement)) {
   7328      Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError =
   7329          MaybeInsertBRElementBeforeEmptyListItemElement(aHTMLEditor);
   7330      if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) {
   7331        NS_WARNING(
   7332            "AutoEmptyBlockAncestorDeleter::"
   7333            "MaybeInsertBRElementBeforeEmptyListItemElement() failed");
   7334        return insertBRElementResultOrError.propagateErr();
   7335      }
   7336      CreateLineBreakResult insertBRElementResult =
   7337          insertBRElementResultOrError.unwrap();
   7338      // If a `<br>` element is inserted, caret should be moved to after it.
   7339      // XXX This comment is wrong, we're suggesting the line break position...
   7340      MOZ_ASSERT_IF(insertBRElementResult.Handled(),
   7341                    insertBRElementResult->IsHTMLBRElement());
   7342      insertBRElementResult.IgnoreCaretPointSuggestion();
   7343      return CaretPoint(
   7344          insertBRElementResult.Handled()
   7345              ? insertBRElementResult.AtLineBreak<EditorDOMPoint>()
   7346              : EditorDOMPoint());
   7347    }
   7348    Result<CaretPoint, nsresult> caretPointOrError =
   7349        GetNewCaretPosition(aHTMLEditor, aDirectionAndAmount, aEditingHost);
   7350    NS_WARNING_ASSERTION(
   7351        caretPointOrError.isOk(),
   7352        "AutoEmptyBlockAncestorDeleter::GetNewCaretPosition() failed");
   7353    MOZ_ASSERT_IF(caretPointOrError.isOk(),
   7354                  caretPointOrError.inspect().HasCaretPointSuggestion());
   7355    return caretPointOrError;
   7356  }();
   7357  if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   7358    return caretPointOrError.propagateErr();
   7359  }
   7360  EditorDOMPoint pointToPutCaret =
   7361      caretPointOrError.unwrap().UnwrapCaretPoint();
   7362  const bool unwrapAncestorBlocks =
   7363      !HTMLEditUtils::IsListItemElement(*mEmptyInclusiveAncestorBlockElement) &&
   7364      pointToPutCaret.GetContainer() ==
   7365          mEmptyInclusiveAncestorBlockElement->GetParentNode();
   7366  EditorDOMPoint atEmptyInclusiveAncestorBlockElement(
   7367      mEmptyInclusiveAncestorBlockElement);
   7368  {
   7369    AutoTrackDOMPoint trackEmptyBlockPoint(
   7370        aHTMLEditor.RangeUpdaterRef(), &atEmptyInclusiveAncestorBlockElement);
   7371    AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
   7372                                           &pointToPutCaret);
   7373    Result<CaretPoint, nsresult> caretPointOrError =
   7374        WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
   7375            aHTMLEditor, MOZ_KnownLive(*mEmptyInclusiveAncestorBlockElement),
   7376            pointToPutCaret, aEditingHost);
   7377    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   7378      NS_WARNING(
   7379          "WhiteSpaceVisibilityKeeper::"
   7380          "DeleteContentNodeAndJoinTextNodesAroundIt() failed");
   7381      return caretPointOrError.propagateErr();
   7382    }
   7383    trackPointToPutCaret.Flush(StopTracking::Yes);
   7384    caretPointOrError.unwrap().MoveCaretPointTo(
   7385        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   7386    trackEmptyBlockPoint.Flush(StopTracking::Yes);
   7387    if (NS_WARN_IF(!atEmptyInclusiveAncestorBlockElement
   7388                        .IsInContentNodeAndValidInComposedDoc()) ||
   7389        NS_WARN_IF(pointToPutCaret.IsSet() &&
   7390                   !pointToPutCaret.IsInContentNodeAndValidInComposedDoc())) {
   7391      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   7392    }
   7393  }
   7394  EditorDOMPoint pointToInsertLineBreak =
   7395      std::move(atEmptyInclusiveAncestorBlockElement);
   7396  DeleteRangeResult deleteNodeResult(pointToInsertLineBreak,
   7397                                     std::move(pointToPutCaret));
   7398  if ((aHTMLEditor.IsMailEditor() || aHTMLEditor.IsPlaintextMailComposer()) &&
   7399      MOZ_LIKELY(pointToInsertLineBreak.IsInContentNode())) {
   7400    AutoTrackDOMDeleteRangeResult trackDeleteNodeResult(
   7401        aHTMLEditor.RangeUpdaterRef(), &deleteNodeResult);
   7402    AutoTrackDOMPoint trackPointToInsertLineBreak(aHTMLEditor.RangeUpdaterRef(),
   7403                                                  &pointToInsertLineBreak);
   7404    nsresult rv = aHTMLEditor.DeleteMostAncestorMailCiteElementIfEmpty(
   7405        MOZ_KnownLive(*pointToInsertLineBreak.ContainerAs<nsIContent>()));
   7406    if (NS_FAILED(rv)) {
   7407      NS_WARNING(
   7408          "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed");
   7409      deleteNodeResult.IgnoreCaretPointSuggestion();
   7410      return Err(rv);
   7411    }
   7412    trackPointToInsertLineBreak.FlushAndStopTracking();
   7413    if (NS_WARN_IF(!pointToInsertLineBreak.IsSetAndValidInComposedDoc())) {
   7414      deleteNodeResult.IgnoreCaretPointSuggestion();
   7415      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   7416    }
   7417    trackDeleteNodeResult.FlushAndStopTracking();
   7418    deleteNodeResult |= DeleteRangeResult(
   7419        EditorDOMRange(pointToInsertLineBreak), EditorDOMPoint());
   7420  }
   7421  if (unwrapAncestorBlocks && aHTMLEditor.GetTopLevelEditSubAction() ==
   7422                                  EditSubAction::eDeleteSelectedContent) {
   7423    AutoTrackDOMDeleteRangeResult trackDeleteNodeResult(
   7424        aHTMLEditor.RangeUpdaterRef(), &deleteNodeResult);
   7425    Result<CreateLineBreakResult, nsresult> insertPaddingBRElementOrError =
   7426        aHTMLEditor.InsertPaddingBRElementIfNeeded(
   7427            pointToInsertLineBreak,
   7428            aEditingHost.IsContentEditablePlainTextOnly() ? nsIEditor::eNoStrip
   7429                                                          : nsIEditor::eStrip,
   7430            aEditingHost);
   7431    if (MOZ_UNLIKELY(insertPaddingBRElementOrError.isErr())) {
   7432      NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed");
   7433      deleteNodeResult.IgnoreCaretPointSuggestion();
   7434      return insertPaddingBRElementOrError.propagateErr();
   7435    }
   7436    insertPaddingBRElementOrError.unwrap().IgnoreCaretPointSuggestion();
   7437  }
   7438  MOZ_ASSERT(deleteNodeResult.Handled());
   7439  return std::move(deleteNodeResult);
   7440 }
   7441 
   7442 Result<DeleteRangeResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::
   7443    AutoEmptyBlockAncestorDeleter::MaybeReplaceSubListWithNewListItem(
   7444        HTMLEditor& aHTMLEditor) {
   7445  // If we're deleting sublist element and it's the last list item of its parent
   7446  // list, we should replace it with a list element.
   7447  if (!HTMLEditUtils::IsListElement(mEmptyInclusiveAncestorBlockElement)) {
   7448    return DeleteRangeResult::IgnoredResult();
   7449  }
   7450  const RefPtr<Element> parentElement =
   7451      mEmptyInclusiveAncestorBlockElement->GetParentElement();
   7452  if (!HTMLEditUtils::IsListElement(parentElement) ||
   7453      !HTMLEditUtils::IsEmptyNode(
   7454          *parentElement,
   7455          {EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
   7456    return DeleteRangeResult::IgnoredResult();
   7457  }
   7458 
   7459  const nsCOMPtr<nsINode> nextSibling =
   7460      mEmptyInclusiveAncestorBlockElement->GetNextSibling();
   7461  nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(
   7462      MOZ_KnownLive(*mEmptyInclusiveAncestorBlockElement));
   7463  if (NS_FAILED(rv)) {
   7464    NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   7465    return Err(rv);
   7466  }
   7467  if (NS_WARN_IF(nextSibling &&
   7468                 nextSibling->GetParentNode() != parentElement) ||
   7469      NS_WARN_IF(!parentElement->IsInComposedDoc())) {
   7470    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   7471  }
   7472  const auto pointAtDeletedNode = nextSibling
   7473                                      ? EditorDOMPoint(nextSibling)
   7474                                      : EditorDOMPoint::AtEndOf(*parentElement);
   7475  auto deleteNodeResult =
   7476      DeleteRangeResult(EditorDOMRange(pointAtDeletedNode), EditorDOMPoint());
   7477  AutoTrackDOMDeleteRangeResult trackDeleteNodeResult(
   7478      aHTMLEditor.RangeUpdaterRef(), &deleteNodeResult);
   7479  Result<CreateElementResult, nsresult> insertListItemResultOrError =
   7480      aHTMLEditor.CreateAndInsertElement(
   7481          WithTransaction::Yes,
   7482          parentElement->IsHTMLElement(nsGkAtoms::dl) ? *nsGkAtoms::dd
   7483                                                      : *nsGkAtoms::li,
   7484          pointAtDeletedNode,
   7485          [](HTMLEditor& aHTMLEditor, Element& aNewElement,
   7486             const EditorDOMPoint& aPointToInsert) -> nsresult {
   7487            RefPtr<Element> brElement =
   7488                aHTMLEditor.CreateHTMLContent(nsGkAtoms::br);
   7489            if (MOZ_UNLIKELY(!brElement)) {
   7490              NS_WARNING(
   7491                  "EditorBase::CreateHTMLContent(nsGkAtoms::br) failed, but "
   7492                  "ignored");
   7493              return NS_OK;  // Just gives up to insert <br>
   7494            }
   7495            IgnoredErrorResult error;
   7496            aNewElement.AppendChild(*brElement, error);
   7497            NS_WARNING_ASSERTION(!error.Failed(),
   7498                                 "nsINode::AppendChild() failed, but ignored");
   7499            return NS_OK;
   7500          });
   7501  if (MOZ_UNLIKELY(insertListItemResultOrError.isErr())) {
   7502    NS_WARNING("HTMLEditor::CreateAndInsertElement() failed");
   7503    deleteNodeResult.IgnoreCaretPointSuggestion();
   7504    return insertListItemResultOrError.propagateErr();
   7505  }
   7506  trackDeleteNodeResult.FlushAndStopTracking();
   7507  CreateElementResult insertListItemResult =
   7508      insertListItemResultOrError.unwrap();
   7509  insertListItemResult.IgnoreCaretPointSuggestion();
   7510  deleteNodeResult |=
   7511      CaretPoint(EditorDOMPoint(insertListItemResult.GetNewNode(), 0u));
   7512  MOZ_ASSERT(deleteNodeResult.Handled());
   7513  return std::move(deleteNodeResult);
   7514 }
   7515 
   7516 template <typename EditorDOMRangeType>
   7517 Result<EditorRawDOMRange, nsresult>
   7518 HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete(
   7519    const HTMLEditor& aHTMLEditor,
   7520    const LimitersAndCaretData& aLimitersAndCaretData,
   7521    const EditorDOMRangeType& aRangeToDelete) const {
   7522  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   7523  MOZ_ASSERT(!aRangeToDelete.Collapsed());
   7524  MOZ_ASSERT(aRangeToDelete.IsPositioned());
   7525 
   7526  const nsIContent* commonAncestor = nsIContent::FromNodeOrNull(
   7527      nsContentUtils::GetClosestCommonInclusiveAncestor(
   7528          aRangeToDelete.StartRef().GetContainer(),
   7529          aRangeToDelete.EndRef().GetContainer()));
   7530  if (MOZ_UNLIKELY(NS_WARN_IF(!commonAncestor))) {
   7531    return Err(NS_ERROR_FAILURE);
   7532  }
   7533 
   7534  // Editing host may be nested and outer one could have focus.  Let's use
   7535  // the closest editing host instead.
   7536  const RefPtr<Element> closestEditingHost =
   7537      aHTMLEditor.ComputeEditingHost(*commonAncestor, LimitInBodyElement::No);
   7538  if (NS_WARN_IF(!closestEditingHost)) {
   7539    return Err(NS_ERROR_FAILURE);
   7540  }
   7541 
   7542  // Look for the common ancestor's block element in the editing host.  It's
   7543  // fine that we get non-editable block element which is ancestor of inline
   7544  // editing host because the following code checks editing host too.
   7545  const RefPtr<Element> closestBlockAncestorOrInlineEditingHost = [&]() {
   7546    // Note that if non-closest editing host has focus, found block may be
   7547    // non-editable.
   7548    if (Element* const maybeEditableBlockElement =
   7549            HTMLEditUtils::GetInclusiveAncestorElement(
   7550                *commonAncestor, HTMLEditUtils::ClosestBlockElement,
   7551                BlockInlineCheck::UseComputedDisplayStyle,
   7552                closestEditingHost)) {
   7553      return maybeEditableBlockElement;
   7554    }
   7555    return closestEditingHost.get();
   7556  }();
   7557 
   7558  // Set up for loops and cache our root element
   7559  // If only one list element is selected, and if the list element is empty,
   7560  // we should delete only the list element.  Or if the list element is not
   7561  // empty, we should make the list has only one empty list item element.
   7562  if (const Element* maybeListElement =
   7563          HTMLEditUtils::GetElementIfOnlyOneSelected(aRangeToDelete)) {
   7564    if (HTMLEditUtils::IsListElement(*maybeListElement) &&
   7565        !HTMLEditUtils::IsEmptyAnyListElement(*maybeListElement)) {
   7566      EditorRawDOMRange range =
   7567          HTMLEditUtils::GetRangeSelectingAllContentInAllListItems<
   7568              EditorRawDOMRange>(*maybeListElement);
   7569      if (range.IsPositioned()) {
   7570        if (EditorUtils::IsEditableContent(
   7571                *range.StartRef().ContainerAs<nsIContent>(),
   7572                EditorType::HTML) &&
   7573            EditorUtils::IsEditableContent(
   7574                *range.EndRef().ContainerAs<nsIContent>(), EditorType::HTML)) {
   7575          return range;
   7576        }
   7577      }
   7578      // If the first and/or last list item is not editable, we need to do more
   7579      // complicated things probably, but we just delete the list element with
   7580      // invisible things around it for now since it must be rare case.
   7581    }
   7582    // Otherwise, if the list item is empty, we should delete it with invisible
   7583    // things around it.
   7584  }
   7585 
   7586  // Find previous visible things before start of selection
   7587  EditorRawDOMRange rangeToDelete(aRangeToDelete);
   7588  if (rangeToDelete.StartRef().GetContainer() !=
   7589      closestBlockAncestorOrInlineEditingHost) {
   7590    for (;;) {
   7591      const WSScanResult backwardScanFromStartResult =
   7592          WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   7593              {WSRunScanner::Option::OnlyEditableNodes},
   7594              rangeToDelete.StartRef());
   7595      if (!backwardScanFromStartResult.ReachedCurrentBlockBoundary() &&
   7596          !backwardScanFromStartResult.ReachedInlineEditingHostBoundary()) {
   7597        break;
   7598      }
   7599      // We want to keep looking up.  But stop if we are crossing table
   7600      // element boundaries, or if we hit the root.
   7601      if (HTMLEditUtils::IsAnyTableElementExceptColumnElement(
   7602              *backwardScanFromStartResult.GetContent()) ||
   7603          backwardScanFromStartResult.GetContent() ==
   7604              closestBlockAncestorOrInlineEditingHost ||
   7605          backwardScanFromStartResult.GetContent() == closestEditingHost) {
   7606        break;
   7607      }
   7608      // Don't cross list element boundary because we don't want to delete list
   7609      // element at start position unless it's empty.
   7610      if (HTMLEditUtils::IsListElement(
   7611              *backwardScanFromStartResult.GetContent()) &&
   7612          !HTMLEditUtils::IsEmptyAnyListElement(
   7613              *backwardScanFromStartResult.ElementPtr())) {
   7614        break;
   7615      }
   7616      // Don't cross flex-item/grid-item boundary to make new content inserted
   7617      // into it.
   7618      if (backwardScanFromStartResult.ContentIsElement() &&
   7619          HTMLEditUtils::IsFlexOrGridItem(
   7620              *backwardScanFromStartResult.ElementPtr())) {
   7621        break;
   7622      }
   7623      rangeToDelete.SetStart(backwardScanFromStartResult
   7624                                 .PointAtReachedContent<EditorRawDOMPoint>());
   7625    }
   7626    if (!aLimitersAndCaretData.NodeIsInLimiters(
   7627            rangeToDelete.StartRef().GetContainer())) {
   7628      NS_WARNING("Computed start container was out of selection limiter");
   7629      return Err(NS_ERROR_FAILURE);
   7630    }
   7631  }
   7632 
   7633  // Expand selection endpoint only if we don't pass an invisible `<br>`, or if
   7634  // we really needed to pass that `<br>` (i.e., its block is now totally
   7635  // selected).
   7636 
   7637  // Find next visible things after end of selection
   7638  EditorDOMPoint atFirstInvisibleBRElement;
   7639  if (rangeToDelete.EndRef().GetContainer() !=
   7640      closestBlockAncestorOrInlineEditingHost) {
   7641    for (;;) {
   7642      const WSScanResult forwardScanFromEndResult =
   7643          WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   7644              {}, rangeToDelete.EndRef(),
   7645              closestBlockAncestorOrInlineEditingHost);
   7646      if (forwardScanFromEndResult.ReachedBRElement()) {
   7647        if (HTMLEditUtils::IsVisibleBRElement(
   7648                *forwardScanFromEndResult.BRElementPtr())) {
   7649          break;
   7650        }
   7651        if (!atFirstInvisibleBRElement.IsSet()) {
   7652          atFirstInvisibleBRElement =
   7653              rangeToDelete.EndRef().To<EditorDOMPoint>();
   7654        }
   7655        rangeToDelete.SetEnd(
   7656            EditorRawDOMPoint::After(*forwardScanFromEndResult.BRElementPtr()));
   7657        continue;
   7658      }
   7659 
   7660      if (forwardScanFromEndResult.ReachedCurrentBlockBoundary() ||
   7661          forwardScanFromEndResult.ReachedInlineEditingHostBoundary()) {
   7662        MOZ_ASSERT(forwardScanFromEndResult.ContentIsElement());
   7663        // We want to keep looking up.  But stop if we are crossing table
   7664        // element boundaries, or if we hit the root.
   7665        if (HTMLEditUtils::IsAnyTableElementExceptColumnElement(
   7666                *forwardScanFromEndResult.GetContent()) ||
   7667            forwardScanFromEndResult.GetContent() ==
   7668                closestBlockAncestorOrInlineEditingHost) {
   7669          break;
   7670        }
   7671        // Don't cross flex-item/grid-item boundary to make new content inserted
   7672        // into it.
   7673        if (HTMLEditUtils::IsFlexOrGridItem(
   7674                *forwardScanFromEndResult.ElementPtr())) {
   7675          break;
   7676        }
   7677        rangeToDelete.SetEnd(
   7678            forwardScanFromEndResult
   7679                .PointAfterReachedContent<EditorRawDOMPoint>());
   7680        continue;
   7681      }
   7682 
   7683      break;
   7684    }
   7685 
   7686    if (!aLimitersAndCaretData.NodeIsInLimiters(
   7687            rangeToDelete.EndRef().GetContainer())) {
   7688      NS_WARNING("Computed end container was out of selection limiter");
   7689      return Err(NS_ERROR_FAILURE);
   7690    }
   7691  }
   7692 
   7693  // If range boundaries are in list element, and the positions are very
   7694  // start/end of first/last list item, we may need to shrink the ranges for
   7695  // preventing to remove only all list item elements.
   7696  {
   7697    EditorRawDOMRange rangeToDeleteListOrLeaveOneEmptyListItem =
   7698        AutoDeleteRangesHandler::
   7699            GetRangeToAvoidDeletingAllListItemsIfSelectingAllOverListElements(
   7700                rangeToDelete);
   7701    if (rangeToDeleteListOrLeaveOneEmptyListItem.IsPositioned()) {
   7702      rangeToDelete = std::move(rangeToDeleteListOrLeaveOneEmptyListItem);
   7703    }
   7704  }
   7705 
   7706  if (atFirstInvisibleBRElement.IsInContentNode()) {
   7707    // Find block node containing invisible `<br>` element.
   7708    if (const RefPtr<const Element> editableBlockContainingBRElement =
   7709            HTMLEditUtils::GetInclusiveAncestorElement(
   7710                *atFirstInvisibleBRElement.ContainerAs<nsIContent>(),
   7711                HTMLEditUtils::ClosestEditableBlockElement,
   7712                BlockInlineCheck::UseComputedDisplayStyle)) {
   7713      if (rangeToDelete.Contains(
   7714              EditorRawDOMPoint(editableBlockContainingBRElement))) {
   7715        return rangeToDelete;
   7716      }
   7717      // Otherwise, the new range should end at the invisible `<br>`.
   7718      if (!aLimitersAndCaretData.NodeIsInLimiters(
   7719              atFirstInvisibleBRElement.GetContainer())) {
   7720        NS_WARNING(
   7721            "Computed end container (`<br>` element) was out of selection "
   7722            "limiter");
   7723        return Err(NS_ERROR_FAILURE);
   7724      }
   7725      rangeToDelete.SetEnd(atFirstInvisibleBRElement);
   7726    }
   7727  }
   7728 
   7729  return rangeToDelete;
   7730 }
   7731 
   7732 // static
   7733 EditorRawDOMRange HTMLEditor::AutoDeleteRangesHandler::
   7734    GetRangeToAvoidDeletingAllListItemsIfSelectingAllOverListElements(
   7735        const EditorRawDOMRange& aRangeToDelete) {
   7736  MOZ_ASSERT(aRangeToDelete.IsPositionedAndValid());
   7737 
   7738  auto GetDeepestEditableStartPointOfList = [](Element& aListElement) {
   7739    Element* const firstListItemElement =
   7740        HTMLEditUtils::GetFirstListItemElement(aListElement);
   7741    if (MOZ_UNLIKELY(!firstListItemElement)) {
   7742      return EditorRawDOMPoint();
   7743    }
   7744    if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(*firstListItemElement,
   7745                                                     EditorType::HTML))) {
   7746      return EditorRawDOMPoint(firstListItemElement);
   7747    }
   7748    return HTMLEditUtils::GetDeepestEditableStartPointOf<EditorRawDOMPoint>(
   7749        *firstListItemElement,
   7750        {EditablePointOption::RecognizeInvisibleWhiteSpaces,
   7751         EditablePointOption::StopAtComment});
   7752  };
   7753 
   7754  auto GetDeepestEditableEndPointOfList = [](Element& aListElement) {
   7755    Element* const lastListItemElement =
   7756        HTMLEditUtils::GetLastListItemElement(aListElement);
   7757    if (MOZ_UNLIKELY(!lastListItemElement)) {
   7758      return EditorRawDOMPoint();
   7759    }
   7760    if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(*lastListItemElement,
   7761                                                     EditorType::HTML))) {
   7762      return EditorRawDOMPoint::After(*lastListItemElement);
   7763    }
   7764    return HTMLEditUtils::GetDeepestEditableEndPointOf<EditorRawDOMPoint>(
   7765        *lastListItemElement,
   7766        {EditablePointOption::RecognizeInvisibleWhiteSpaces,
   7767         EditablePointOption::StopAtComment});
   7768  };
   7769 
   7770  Element* const startListElement =
   7771      aRangeToDelete.StartRef().IsInContentNode()
   7772          ? HTMLEditUtils::GetClosestInclusiveAncestorAnyListElement(
   7773                *aRangeToDelete.StartRef().ContainerAs<nsIContent>())
   7774          : nullptr;
   7775  Element* const endListElement =
   7776      aRangeToDelete.EndRef().IsInContentNode()
   7777          ? HTMLEditUtils::GetClosestInclusiveAncestorAnyListElement(
   7778                *aRangeToDelete.EndRef().ContainerAs<nsIContent>())
   7779          : nullptr;
   7780  if (!startListElement && !endListElement) {
   7781    return EditorRawDOMRange();
   7782  }
   7783 
   7784  // FIXME: If there are invalid children, we cannot handle first/last list item
   7785  // elements properly.  In that case, we should treat list elements and list
   7786  // item elements as normal block elements.
   7787  if (startListElement &&
   7788      NS_WARN_IF(!HTMLEditUtils::IsValidListElement(
   7789          *startListElement, HTMLEditUtils::TreatSubListElementAs::Valid))) {
   7790    return EditorRawDOMRange();
   7791  }
   7792  if (endListElement && startListElement != endListElement &&
   7793      NS_WARN_IF(!HTMLEditUtils::IsValidListElement(
   7794          *endListElement, HTMLEditUtils::TreatSubListElementAs::Valid))) {
   7795    return EditorRawDOMRange();
   7796  }
   7797 
   7798  const bool startListElementIsEmpty =
   7799      startListElement &&
   7800      HTMLEditUtils::IsEmptyAnyListElement(*startListElement);
   7801  const bool endListElementIsEmpty =
   7802      startListElement == endListElement
   7803          ? startListElementIsEmpty
   7804          : endListElement &&
   7805                HTMLEditUtils::IsEmptyAnyListElement(*endListElement);
   7806  // If both list elements are empty, we should not shrink the range since
   7807  // we want to delete the list.
   7808  if (startListElementIsEmpty && endListElementIsEmpty) {
   7809    return EditorRawDOMRange();
   7810  }
   7811 
   7812  // There may be invisible white-spaces and there are elements in the
   7813  // list items.  Therefore, we need to compare the deepest positions
   7814  // and range boundaries.
   7815  EditorRawDOMPoint deepestStartPointOfStartList =
   7816      startListElement ? GetDeepestEditableStartPointOfList(*startListElement)
   7817                       : EditorRawDOMPoint();
   7818  EditorRawDOMPoint deepestEndPointOfEndList =
   7819      endListElement ? GetDeepestEditableEndPointOfList(*endListElement)
   7820                     : EditorRawDOMPoint();
   7821  if (MOZ_UNLIKELY(!deepestStartPointOfStartList.IsSet() &&
   7822                   !deepestEndPointOfEndList.IsSet())) {
   7823    // FIXME: This does not work well if there is non-list-item contents in the
   7824    // list elements.  Perhaps, for fixing this invalid cases, we need to wrap
   7825    // the content into new list item like Chrome.
   7826    return EditorRawDOMRange();
   7827  }
   7828 
   7829  // We don't want to shrink the range into empty sublist.
   7830  if (deepestStartPointOfStartList.IsSet()) {
   7831    for (nsIContent* const maybeList :
   7832         deepestStartPointOfStartList.GetContainer()
   7833             ->InclusiveAncestorsOfType<nsIContent>()) {
   7834      if (aRangeToDelete.StartRef().GetContainer() == maybeList) {
   7835        break;
   7836      }
   7837      if (HTMLEditUtils::IsListElement(*maybeList) &&
   7838          HTMLEditUtils::IsEmptyAnyListElement(*maybeList->AsElement())) {
   7839        deepestStartPointOfStartList.Set(maybeList);
   7840      }
   7841    }
   7842  }
   7843  if (deepestEndPointOfEndList.IsSet()) {
   7844    for (nsIContent* const maybeList :
   7845         deepestEndPointOfEndList.GetContainer()
   7846             ->InclusiveAncestorsOfType<nsIContent>()) {
   7847      if (aRangeToDelete.EndRef().GetContainer() == maybeList) {
   7848        break;
   7849      }
   7850      if (HTMLEditUtils::IsListElement(*maybeList) &&
   7851          HTMLEditUtils::IsEmptyAnyListElement(*maybeList->AsElement())) {
   7852        deepestEndPointOfEndList.SetAfter(maybeList);
   7853      }
   7854    }
   7855  }
   7856 
   7857  const EditorRawDOMPoint deepestEndPointOfStartList =
   7858      startListElement ? GetDeepestEditableEndPointOfList(*startListElement)
   7859                       : EditorRawDOMPoint();
   7860  MOZ_ASSERT_IF(deepestStartPointOfStartList.IsSet(),
   7861                deepestEndPointOfStartList.IsSet());
   7862  MOZ_ASSERT_IF(!deepestStartPointOfStartList.IsSet(),
   7863                !deepestEndPointOfStartList.IsSet());
   7864 
   7865  const bool rangeStartsFromBeginningOfStartList =
   7866      deepestStartPointOfStartList.IsSet() &&
   7867      aRangeToDelete.StartRef().EqualsOrIsBefore(deepestStartPointOfStartList);
   7868  const bool rangeEndsByEndingOfStartListOrLater =
   7869      !deepestEndPointOfStartList.IsSet() ||
   7870      deepestEndPointOfStartList.EqualsOrIsBefore(aRangeToDelete.EndRef());
   7871  const bool rangeEndsByEndingOfEndList =
   7872      deepestEndPointOfEndList.IsSet() &&
   7873      deepestEndPointOfEndList.EqualsOrIsBefore(aRangeToDelete.EndRef());
   7874 
   7875  EditorRawDOMRange newRangeToDelete;
   7876  // If all over the list element at start boundary is selected, we should
   7877  // shrink the range to start from the first list item to avoid to delete
   7878  // all list items.
   7879  if (!startListElementIsEmpty && rangeStartsFromBeginningOfStartList &&
   7880      rangeEndsByEndingOfStartListOrLater) {
   7881    newRangeToDelete.SetStart(EditorRawDOMPoint(
   7882        deepestStartPointOfStartList.ContainerAs<nsIContent>(), 0u));
   7883  }
   7884  // If all over the list element at end boundary is selected, and...
   7885  if (!endListElementIsEmpty && rangeEndsByEndingOfEndList) {
   7886    // If the range starts before the range at end boundary of the range,
   7887    // we want to delete the list completely, thus, we should extend the
   7888    // range to contain the list element.
   7889    if (aRangeToDelete.StartRef().IsBefore(
   7890            EditorRawDOMPoint(endListElement, 0u))) {
   7891      newRangeToDelete.SetEnd(EditorRawDOMPoint::After(*endListElement));
   7892      MOZ_ASSERT_IF(newRangeToDelete.StartRef().IsSet(),
   7893                    newRangeToDelete.IsPositionedAndValid());
   7894    }
   7895    // Otherwise, if the range starts in the end list element, we shouldn't
   7896    // delete the list.  Therefore, we should shrink the range to end by end
   7897    // of the last list item element to avoid to delete all list items.
   7898    else {
   7899      newRangeToDelete.SetEnd(EditorRawDOMPoint::AtEndOf(
   7900          *deepestEndPointOfEndList.ContainerAs<nsIContent>()));
   7901      MOZ_ASSERT_IF(newRangeToDelete.StartRef().IsSet(),
   7902                    newRangeToDelete.IsPositionedAndValid());
   7903    }
   7904  }
   7905 
   7906  if (!newRangeToDelete.StartRef().IsSet() &&
   7907      !newRangeToDelete.EndRef().IsSet()) {
   7908    return EditorRawDOMRange();
   7909  }
   7910 
   7911  if (!newRangeToDelete.StartRef().IsSet()) {
   7912    newRangeToDelete.SetStart(aRangeToDelete.StartRef());
   7913    MOZ_ASSERT(newRangeToDelete.IsPositionedAndValid());
   7914  }
   7915  if (!newRangeToDelete.EndRef().IsSet()) {
   7916    newRangeToDelete.SetEnd(aRangeToDelete.EndRef());
   7917    MOZ_ASSERT(newRangeToDelete.IsPositionedAndValid());
   7918  }
   7919 
   7920  return newRangeToDelete;
   7921 }
   7922 
   7923 }  // namespace mozilla