tor-browser

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

HTMLEditSubActionHandler.cpp (471669B)


      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 "EditorBase.h"
      8 #include "HTMLEditor.h"
      9 #include "HTMLEditorInlines.h"
     10 #include "HTMLEditorNestedClasses.h"
     11 
     12 #include <utility>
     13 
     14 #include "AutoClonedRangeArray.h"
     15 #include "AutoSelectionRestorer.h"
     16 #include "CSSEditUtils.h"
     17 #include "EditAction.h"
     18 #include "EditorDOMPoint.h"
     19 #include "EditorLineBreak.h"
     20 #include "EditorUtils.h"
     21 #include "HTMLEditHelpers.h"
     22 #include "HTMLEditUtils.h"
     23 #include "PendingStyles.h"  // for SpecifiedStyle
     24 #include "WhiteSpaceVisibilityKeeper.h"
     25 #include "WSRunScanner.h"
     26 
     27 #include "ErrorList.h"
     28 #include "mozilla/Assertions.h"
     29 #include "mozilla/Attributes.h"
     30 #include "mozilla/AutoRestore.h"
     31 #include "mozilla/ContentIterator.h"
     32 #include "mozilla/EditorForwards.h"
     33 #include "mozilla/IntegerRange.h"
     34 #include "mozilla/Logging.h"
     35 #include "mozilla/MathAlgorithms.h"
     36 #include "mozilla/Maybe.h"
     37 #include "mozilla/OwningNonNull.h"
     38 #include "mozilla/PresShell.h"
     39 #include "mozilla/StaticPrefs_editor.h"
     40 #include "mozilla/TextComposition.h"
     41 #include "mozilla/UniquePtr.h"
     42 #include "mozilla/dom/AncestorIterator.h"
     43 #include "mozilla/dom/Element.h"
     44 #include "mozilla/dom/ElementInlines.h"
     45 #include "mozilla/dom/HTMLBRElement.h"
     46 #include "mozilla/dom/RangeBinding.h"
     47 #include "mozilla/dom/Selection.h"
     48 #include "mozilla/dom/StaticRange.h"
     49 #include "nsAtom.h"
     50 #include "nsCRT.h"
     51 #include "nsCRTGlue.h"
     52 #include "nsComponentManagerUtils.h"
     53 #include "nsContentUtils.h"
     54 #include "nsDebug.h"
     55 #include "nsError.h"
     56 #include "nsFrameSelection.h"
     57 #include "nsGkAtoms.h"
     58 #include "nsIContent.h"
     59 #include "nsIFrame.h"
     60 #include "nsINode.h"
     61 #include "nsLiteralString.h"
     62 #include "nsPrintfCString.h"
     63 #include "nsRange.h"
     64 #include "nsReadableUtils.h"
     65 #include "nsString.h"
     66 #include "nsStringFwd.h"
     67 #include "nsStyledElement.h"
     68 #include "nsTArray.h"
     69 #include "nsTextNode.h"
     70 #include "nsThreadUtils.h"
     71 
     72 class nsISupports;
     73 
     74 namespace mozilla {
     75 
     76 extern LazyLogModule gTextInputLog;  // Defined in EditorBase.cpp
     77 
     78 using namespace dom;
     79 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
     80 using EmptyCheckOptions = HTMLEditUtils::EmptyCheckOptions;
     81 using LeafNodeType = HTMLEditUtils::LeafNodeType;
     82 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
     83 using WalkTextOption = HTMLEditUtils::WalkTextOption;
     84 using WalkTreeDirection = HTMLEditUtils::WalkTreeDirection;
     85 using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
     86 
     87 /********************************************************
     88 *  first some helpful functors we will use
     89 ********************************************************/
     90 
     91 static bool IsPendingStyleCachePreservingSubAction(
     92    EditSubAction aEditSubAction) {
     93  switch (aEditSubAction) {
     94    case EditSubAction::eDeleteSelectedContent:
     95    case EditSubAction::eInsertLineBreak:
     96    case EditSubAction::eInsertParagraphSeparator:
     97    case EditSubAction::eCreateOrChangeList:
     98    case EditSubAction::eIndent:
     99    case EditSubAction::eOutdent:
    100    case EditSubAction::eSetOrClearAlignment:
    101    case EditSubAction::eCreateOrRemoveBlock:
    102    case EditSubAction::eFormatBlockForHTMLCommand:
    103    case EditSubAction::eMergeBlockContents:
    104    case EditSubAction::eRemoveList:
    105    case EditSubAction::eCreateOrChangeDefinitionListItem:
    106    case EditSubAction::eInsertElement:
    107    case EditSubAction::eInsertQuotation:
    108    case EditSubAction::eInsertQuotedText:
    109      return true;
    110    default:
    111      return false;
    112  }
    113 }
    114 
    115 template already_AddRefed<nsRange>
    116 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
    117    const EditorDOMRange& aRange);
    118 template already_AddRefed<nsRange>
    119 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
    120    const EditorRawDOMRange& aRange);
    121 template already_AddRefed<nsRange>
    122 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
    123    const EditorDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint);
    124 template already_AddRefed<nsRange>
    125 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
    126    const EditorRawDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint);
    127 template already_AddRefed<nsRange>
    128 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
    129    const EditorDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint);
    130 template already_AddRefed<nsRange>
    131 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
    132    const EditorRawDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint);
    133 
    134 nsresult HTMLEditor::InitEditorContentAndSelection() {
    135  MOZ_ASSERT(IsEditActionDataAvailable());
    136 
    137  // We should do nothing with the result of GetRoot() if only a part of the
    138  // document is editable.
    139  if (!EntireDocumentIsEditable()) {
    140    return NS_OK;
    141  }
    142 
    143  nsresult rv = MaybeCreatePaddingBRElementForEmptyEditor();
    144  if (NS_FAILED(rv)) {
    145    NS_WARNING(
    146        "HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() failed");
    147    return rv;
    148  }
    149 
    150  // If the selection hasn't been set up yet, set it up collapsed to the end of
    151  // our editable content.
    152  // XXX I think that this shouldn't do it in `HTMLEditor` because it maybe
    153  //     removed by the web app and if they call `Selection::AddRange()` without
    154  //     checking the range count, it may cause multiple selection ranges.
    155  if (!SelectionRef().RangeCount()) {
    156    nsresult rv = CollapseSelectionToEndOfLastLeafNodeOfDocument();
    157    if (NS_FAILED(rv)) {
    158      NS_WARNING(
    159          "HTMLEditor::CollapseSelectionToEndOfLastLeafNodeOfDocument() "
    160          "failed");
    161      return rv;
    162    }
    163  }
    164 
    165  if (IsPlaintextMailComposer()) {
    166    // XXX Should we do this in HTMLEditor?  It's odd to guarantee that last
    167    //     empty line is visible only when it's in the plain text mode.
    168    nsresult rv = EnsurePaddingBRElementInMultilineEditor();
    169    if (NS_FAILED(rv)) {
    170      NS_WARNING(
    171          "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed");
    172      return rv;
    173    }
    174  }
    175 
    176  Element* bodyOrDocumentElement = GetRoot();
    177  if (NS_WARN_IF(!bodyOrDocumentElement && !GetDocument())) {
    178    return NS_ERROR_FAILURE;
    179  }
    180 
    181  if (!bodyOrDocumentElement) {
    182    return NS_OK;
    183  }
    184 
    185  // FIXME: This is odd to update the DOM for making users can put caret in
    186  // empty table cells and list items.  We should make it possible without
    187  // the hacky <br>.
    188  rv = InsertBRElementToEmptyListItemsAndTableCellsInRange(
    189      RawRangeBoundary(bodyOrDocumentElement, 0u),
    190      RawRangeBoundary(bodyOrDocumentElement,
    191                       bodyOrDocumentElement->GetChildCount()));
    192  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    193    return NS_ERROR_EDITOR_DESTROYED;
    194  }
    195  NS_WARNING_ASSERTION(
    196      NS_SUCCEEDED(rv),
    197      "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange() "
    198      "failed, but ignored");
    199  return NS_OK;
    200 }
    201 
    202 void HTMLEditor::OnStartToHandleTopLevelEditSubAction(
    203    EditSubAction aTopLevelEditSubAction,
    204    nsIEditor::EDirection aDirectionOfTopLevelEditSubAction, ErrorResult& aRv) {
    205  MOZ_ASSERT(IsEditActionDataAvailable());
    206  MOZ_ASSERT(!aRv.Failed());
    207 
    208  EditorBase::OnStartToHandleTopLevelEditSubAction(
    209      aTopLevelEditSubAction, aDirectionOfTopLevelEditSubAction, aRv);
    210 
    211  MOZ_ASSERT(GetTopLevelEditSubAction() == aTopLevelEditSubAction);
    212  MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() ==
    213             aDirectionOfTopLevelEditSubAction);
    214 
    215  if (NS_WARN_IF(Destroyed())) {
    216    aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
    217    return;
    218  }
    219 
    220  if (!mInitSucceeded) {
    221    return;  // We should do nothing if we're being initialized.
    222  }
    223 
    224  NS_WARNING_ASSERTION(
    225      !aRv.Failed(),
    226      "EditorBase::OnStartToHandleTopLevelEditSubAction() failed");
    227 
    228  // Let's work with the latest layout information after (maybe) dispatching
    229  // `beforeinput` event.
    230  RefPtr<Document> document = GetDocument();
    231  if (NS_WARN_IF(!document)) {
    232    aRv.Throw(NS_ERROR_UNEXPECTED);
    233    return;
    234  }
    235  document->FlushPendingNotifications(FlushType::Frames);
    236  if (NS_WARN_IF(Destroyed())) {
    237    aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
    238    return;
    239  }
    240 
    241  // Remember where our selection was before edit action took place:
    242  const auto atCompositionStart =
    243      GetFirstIMESelectionStartPoint<EditorRawDOMPoint>();
    244  if (atCompositionStart.IsSet()) {
    245    // If there is composition string, let's remember current composition
    246    // range.
    247    TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(
    248        atCompositionStart, GetLastIMESelectionEndPoint<EditorRawDOMPoint>());
    249  } else {
    250    // Get the selection location
    251    // XXX This may occur so that I think that we shouldn't throw exception
    252    //     in this case.
    253    if (NS_WARN_IF(!SelectionRef().RangeCount())) {
    254      aRv.Throw(NS_ERROR_UNEXPECTED);
    255      return;
    256    }
    257    if (const nsRange* range = SelectionRef().GetRangeAt(0)) {
    258      TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(*range);
    259    }
    260  }
    261 
    262  // Register with range updater to track this as we perturb the doc
    263  RangeUpdaterRef().RegisterRangeItem(
    264      *TopLevelEditSubActionDataRef().mSelectedRange);
    265 
    266  // Remember current inline styles for deletion and normal insertion ops
    267  const bool cacheInlineStyles = [&]() {
    268    switch (aTopLevelEditSubAction) {
    269      case EditSubAction::eInsertText:
    270      case EditSubAction::eInsertTextComingFromIME:
    271      case EditSubAction::eDeleteSelectedContent:
    272        return true;
    273      default:
    274        return IsPendingStyleCachePreservingSubAction(aTopLevelEditSubAction);
    275    }
    276  }();
    277  if (cacheInlineStyles) {
    278    const RefPtr<Element> editingHost =
    279        ComputeEditingHost(LimitInBodyElement::No);
    280    if (NS_WARN_IF(!editingHost)) {
    281      aRv.Throw(NS_ERROR_FAILURE);
    282      return;
    283    }
    284 
    285    nsIContent* const startContainer =
    286        HTMLEditUtils::GetContentToPreserveInlineStyles(
    287            TopLevelEditSubActionDataRef()
    288                .mSelectedRange->StartPoint<EditorRawDOMPoint>(),
    289            *editingHost);
    290    if (NS_WARN_IF(!startContainer)) {
    291      aRv.Throw(NS_ERROR_FAILURE);
    292      return;
    293    }
    294    if (const RefPtr<Element> startContainerElement =
    295            startContainer->GetAsElementOrParentElement()) {
    296      nsresult rv = CacheInlineStyles(*startContainerElement);
    297      if (NS_FAILED(rv)) {
    298        NS_WARNING("HTMLEditor::CacheInlineStyles() failed");
    299        aRv.Throw(rv);
    300        return;
    301      }
    302    }
    303  }
    304 
    305  // Stabilize the document against contenteditable count changes
    306  if (document->GetEditingState() == Document::EditingState::eContentEditable) {
    307    document->ChangeContentEditableCount(nullptr, +1);
    308    TopLevelEditSubActionDataRef().mRestoreContentEditableCount = true;
    309  }
    310 
    311  // Check that selection is in subtree defined by body node
    312  nsresult rv = EnsureSelectionInBodyOrDocumentElement();
    313  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    314    aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
    315    return;
    316  }
    317  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    318                       "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
    319                       "failed, but ignored");
    320 }
    321 
    322 nsresult HTMLEditor::OnEndHandlingTopLevelEditSubAction() {
    323  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
    324 
    325  nsresult rv;
    326  while (true) {
    327    if (NS_WARN_IF(Destroyed())) {
    328      rv = NS_ERROR_EDITOR_DESTROYED;
    329      break;
    330    }
    331 
    332    if (!mInitSucceeded) {
    333      rv = NS_OK;  // We should do nothing if we're being initialized.
    334      break;
    335    }
    336 
    337    // Do all the tricky stuff
    338    rv = OnEndHandlingTopLevelEditSubActionInternal();
    339    NS_WARNING_ASSERTION(
    340        NS_SUCCEEDED(rv),
    341        "HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() failied");
    342    // Perhaps, we need to do the following jobs even if the editor has been
    343    // destroyed since they adjust some states of HTML document but don't
    344    // modify the DOM tree nor Selection.
    345 
    346    // Free up selectionState range item
    347    if (TopLevelEditSubActionDataRef().mSelectedRange) {
    348      RangeUpdaterRef().DropRangeItem(
    349          *TopLevelEditSubActionDataRef().mSelectedRange);
    350    }
    351 
    352    // Reset the contenteditable count to its previous value
    353    if (TopLevelEditSubActionDataRef().mRestoreContentEditableCount) {
    354      Document* document = GetDocument();
    355      if (NS_WARN_IF(!document)) {
    356        rv = NS_ERROR_FAILURE;
    357        break;
    358      }
    359      if (document->GetEditingState() ==
    360          Document::EditingState::eContentEditable) {
    361        document->ChangeContentEditableCount(nullptr, -1);
    362      }
    363    }
    364    break;
    365  }
    366  DebugOnly<nsresult> rvIgnored =
    367      EditorBase::OnEndHandlingTopLevelEditSubAction();
    368  NS_WARNING_ASSERTION(
    369      NS_FAILED(rv) || NS_SUCCEEDED(rvIgnored),
    370      "EditorBase::OnEndHandlingTopLevelEditSubAction() failed, but ignored");
    371  MOZ_ASSERT(!GetTopLevelEditSubAction());
    372  MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() == eNone);
    373  return rv;
    374 }
    375 
    376 nsresult HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() {
    377  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
    378 
    379  // If we just maintained the DOM tree for consistent behavior even after
    380  // web apps modified the DOM, we should not touch the DOM in this
    381  // post-processor.
    382  if (GetTopLevelEditSubAction() ==
    383      EditSubAction::eMaintainWhiteSpaceVisibility) {
    384    return NS_OK;
    385  }
    386 
    387  nsresult rv = EnsureSelectionInBodyOrDocumentElement();
    388  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    389    return NS_ERROR_EDITOR_DESTROYED;
    390  }
    391  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    392                       "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
    393                       "failed, but ignored");
    394 
    395  if (GetTopLevelEditSubAction() ==
    396      EditSubAction::eCreatePaddingBRElementForEmptyEditor) {
    397    return NS_OK;
    398  }
    399 
    400  if (TopLevelEditSubActionDataRef().mChangedRange->IsPositioned() &&
    401      GetTopLevelEditSubAction() != EditSubAction::eUndo &&
    402      GetTopLevelEditSubAction() != EditSubAction::eRedo) {
    403    // don't let any txns in here move the selection around behind our back.
    404    // Note that this won't prevent explicit selection setting from working.
    405    AutoTransactionsConserveSelection dontChangeMySelection(*this);
    406 
    407    {
    408      EditorDOMRange changedRange(
    409          *TopLevelEditSubActionDataRef().mChangedRange);
    410      if (changedRange.IsPositioned() &&
    411          changedRange.EnsureNotInNativeAnonymousSubtree()) {
    412        bool isBlockLevelSubAction = false;
    413        switch (GetTopLevelEditSubAction()) {
    414          case EditSubAction::eInsertText:
    415          case EditSubAction::eInsertTextComingFromIME:
    416          case EditSubAction::eInsertLineBreak:
    417          case EditSubAction::eInsertParagraphSeparator:
    418          case EditSubAction::eDeleteText: {
    419            // XXX We should investigate whether this is really needed because
    420            //     it seems that the following code does not handle the
    421            //     white-spaces.
    422            RefPtr<nsRange> extendedChangedRange =
    423                CreateRangeIncludingAdjuscentWhiteSpaces(changedRange);
    424            if (extendedChangedRange) {
    425              MOZ_ASSERT(extendedChangedRange->IsPositioned());
    426              // Use extended range temporarily.
    427              TopLevelEditSubActionDataRef().mChangedRange =
    428                  std::move(extendedChangedRange);
    429            }
    430            break;
    431          }
    432          case EditSubAction::eCreateOrChangeList:
    433          case EditSubAction::eCreateOrChangeDefinitionListItem:
    434          case EditSubAction::eRemoveList:
    435          case EditSubAction::eFormatBlockForHTMLCommand:
    436          case EditSubAction::eCreateOrRemoveBlock:
    437          case EditSubAction::eIndent:
    438          case EditSubAction::eOutdent:
    439          case EditSubAction::eSetOrClearAlignment:
    440          case EditSubAction::eSetPositionToAbsolute:
    441          case EditSubAction::eSetPositionToStatic:
    442          case EditSubAction::eDecreaseZIndex:
    443          case EditSubAction::eIncreaseZIndex:
    444            isBlockLevelSubAction = true;
    445            [[fallthrough]];
    446          default: {
    447            Element* editingHost = ComputeEditingHost();
    448            if (MOZ_UNLIKELY(!editingHost)) {
    449              break;
    450            }
    451            RefPtr<nsRange> extendedChangedRange = AutoClonedRangeArray::
    452                CreateRangeWrappingStartAndEndLinesContainingBoundaries(
    453                    changedRange, GetTopLevelEditSubAction(),
    454                    isBlockLevelSubAction
    455                        ? BlockInlineCheck::UseHTMLDefaultStyle
    456                        : BlockInlineCheck::UseComputedDisplayOutsideStyle,
    457                    *editingHost);
    458            if (!extendedChangedRange) {
    459              break;
    460            }
    461            MOZ_ASSERT(extendedChangedRange->IsPositioned());
    462            // Use extended range temporarily.
    463            TopLevelEditSubActionDataRef().mChangedRange =
    464                std::move(extendedChangedRange);
    465            break;
    466          }
    467        }
    468      }
    469    }
    470 
    471    // if we did a ranged deletion or handling backspace key, make sure we have
    472    // a place to put caret.
    473    // Note we only want to do this if the overall operation was deletion,
    474    // not if deletion was done along the way for
    475    // EditSubAction::eInsertHTMLSource, EditSubAction::eInsertText, etc.
    476    // That's why this is here rather than DeleteSelectionAsSubAction().
    477    // However, we shouldn't insert <br> elements if we've already removed
    478    // empty block parents because users may want to disappear the line by
    479    // the deletion.
    480    // XXX We should make HandleDeleteSelection() store expected container
    481    //     for handling this here since we cannot trust current selection is
    482    //     collapsed at deleted point.
    483    if (GetTopLevelEditSubAction() == EditSubAction::eDeleteSelectedContent &&
    484        TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange &&
    485        !TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks) {
    486      const auto newCaretPosition =
    487          GetFirstSelectionStartPoint<EditorDOMPoint>();
    488      if (!newCaretPosition.IsSet()) {
    489        NS_WARNING("There was no selection range");
    490        return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
    491      }
    492      Result<CreateLineBreakResult, nsresult>
    493          insertPaddingBRElementResultOrError =
    494              InsertPaddingBRElementToMakeEmptyLineVisibleIfNeeded(
    495                  newCaretPosition);
    496      if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) {
    497        NS_WARNING(
    498            "HTMLEditor::"
    499            "InsertPaddingBRElementToMakeEmptyLineVisibleIfNeeded() failed");
    500        return insertPaddingBRElementResultOrError.unwrapErr();
    501      }
    502      nsresult rv =
    503          insertPaddingBRElementResultOrError.unwrap().SuggestCaretPointTo(
    504              *this, {SuggestCaret::OnlyIfHasSuggestion});
    505      if (NS_FAILED(rv)) {
    506        NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
    507        return rv;
    508      }
    509      NS_WARNING_ASSERTION(
    510          rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
    511          "CaretPoint::SuggestCaretPointTo() failed, but ignored");
    512    }
    513 
    514    // add in any needed <br>s, and remove any unneeded ones.
    515    nsresult rv = InsertBRElementToEmptyListItemsAndTableCellsInRange(
    516        TopLevelEditSubActionDataRef().mChangedRange->StartRef().AsRaw(),
    517        TopLevelEditSubActionDataRef().mChangedRange->EndRef().AsRaw());
    518    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    519      return NS_ERROR_EDITOR_DESTROYED;
    520    }
    521    NS_WARNING_ASSERTION(
    522        NS_SUCCEEDED(rv),
    523        "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange()"
    524        " failed, but ignored");
    525 
    526    // merge any adjacent text nodes
    527    switch (GetTopLevelEditSubAction()) {
    528      case EditSubAction::eInsertText:
    529      case EditSubAction::eInsertTextComingFromIME:
    530        break;
    531      default: {
    532        nsresult rv = CollapseAdjacentTextNodes(
    533            MOZ_KnownLive(*TopLevelEditSubActionDataRef().mChangedRange));
    534        if (NS_WARN_IF(Destroyed())) {
    535          return NS_ERROR_EDITOR_DESTROYED;
    536        }
    537        if (NS_FAILED(rv)) {
    538          NS_WARNING("HTMLEditor::CollapseAdjacentTextNodes() failed");
    539          return rv;
    540        }
    541        break;
    542      }
    543    }
    544 
    545    // Clean up any empty nodes in the changed range unless they are inserted
    546    // intentionally.
    547    if (TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements) {
    548      nsresult rv = RemoveEmptyNodesIn(
    549          EditorDOMRange(*TopLevelEditSubActionDataRef().mChangedRange));
    550      if (NS_FAILED(rv)) {
    551        NS_WARNING("HTMLEditor::RemoveEmptyNodesIn() failed");
    552        return rv;
    553      }
    554    }
    555 
    556    // Adjust selection for insert text, html paste, and delete actions if
    557    // we haven't removed new empty blocks.  Note that if empty block parents
    558    // are removed, Selection should've been adjusted by the method which
    559    // did it.
    560    if (!TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks &&
    561        SelectionRef().IsCollapsed()) {
    562      switch (GetTopLevelEditSubAction()) {
    563        case EditSubAction::eInsertText:
    564        case EditSubAction::eInsertTextComingFromIME:
    565        case EditSubAction::eInsertLineBreak:
    566        case EditSubAction::eInsertParagraphSeparator:
    567        case EditSubAction::ePasteHTMLContent:
    568        case EditSubAction::eInsertHTMLSource:
    569          // XXX AdjustCaretPositionAndEnsurePaddingBRElement() intentionally
    570          //     does not create padding `<br>` element for empty editor.
    571          //     Investigate which is better that whether this should does it
    572          //     or wait MaybeCreatePaddingBRElementForEmptyEditor().
    573          rv = AdjustCaretPositionAndEnsurePaddingBRElement(
    574              GetDirectionOfTopLevelEditSubAction());
    575          if (NS_FAILED(rv)) {
    576            NS_WARNING(
    577                "HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement() "
    578                "failed");
    579            return rv;
    580          }
    581          break;
    582        default:
    583          break;
    584      }
    585    }
    586 
    587    // check for any styles which were removed inappropriately
    588    bool reapplyCachedStyle;
    589    switch (GetTopLevelEditSubAction()) {
    590      case EditSubAction::eInsertText:
    591      case EditSubAction::eInsertTextComingFromIME:
    592      case EditSubAction::eDeleteSelectedContent:
    593        reapplyCachedStyle = true;
    594        break;
    595      default:
    596        reapplyCachedStyle =
    597            IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction());
    598        break;
    599    }
    600 
    601    // If the selection is in empty inline HTML elements, we should delete
    602    // them unless it's inserted intentionally.
    603    if (mPlaceholderBatch &&
    604        TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements &&
    605        SelectionRef().IsCollapsed() && SelectionRef().GetFocusNode()) {
    606      RefPtr<Element> mostDistantEmptyInlineAncestor = nullptr;
    607      for (Element* ancestor :
    608           SelectionRef().GetFocusNode()->InclusiveAncestorsOfType<Element>()) {
    609        if (!ancestor->IsHTMLElement() ||
    610            !HTMLEditUtils::IsRemovableFromParentNode(*ancestor) ||
    611            !HTMLEditUtils::IsEmptyInlineContainer(
    612                *ancestor, {EmptyCheckOption::TreatSingleBRElementAsVisible},
    613                BlockInlineCheck::UseComputedDisplayStyle)) {
    614          break;
    615        }
    616        mostDistantEmptyInlineAncestor = ancestor;
    617      }
    618      if (mostDistantEmptyInlineAncestor) {
    619        nsresult rv =
    620            DeleteNodeWithTransaction(*mostDistantEmptyInlineAncestor);
    621        if (NS_FAILED(rv)) {
    622          NS_WARNING(
    623              "EditorBase::DeleteNodeWithTransaction() failed at deleting "
    624              "empty inline ancestors");
    625          return rv;
    626        }
    627      }
    628    }
    629 
    630    // But the cached inline styles should be restored from type-in-state later.
    631    if (reapplyCachedStyle) {
    632      DebugOnly<nsresult> rvIgnored =
    633          mPendingStylesToApplyToNewContent->UpdateSelState(*this);
    634      NS_WARNING_ASSERTION(
    635          NS_SUCCEEDED(rvIgnored),
    636          "PendingStyles::UpdateSelState() failed, but ignored");
    637      rvIgnored = ReapplyCachedStyles();
    638      NS_WARNING_ASSERTION(
    639          NS_SUCCEEDED(rvIgnored),
    640          "HTMLEditor::ReapplyCachedStyles() failed, but ignored");
    641      TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
    642    }
    643  }
    644 
    645  rv = HandleInlineSpellCheck(
    646      TopLevelEditSubActionDataRef().mSelectedRange->StartPoint(),
    647      TopLevelEditSubActionDataRef().mChangedRange);
    648  if (NS_FAILED(rv)) {
    649    NS_WARNING("EditorBase::HandleInlineSpellCheck() failed");
    650    return rv;
    651  }
    652 
    653  // detect empty doc
    654  // XXX Need to investigate when the padding <br> element is removed because
    655  //     I don't see the <br> element with testing manually.  If it won't be
    656  //     used, we can get rid of this cost.
    657  rv = MaybeCreatePaddingBRElementForEmptyEditor();
    658  if (NS_FAILED(rv)) {
    659    NS_WARNING(
    660        "EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() failed");
    661    return rv;
    662  }
    663 
    664  // adjust selection HINT if needed
    665  if (!TopLevelEditSubActionDataRef().mDidExplicitlySetInterLine &&
    666      SelectionRef().IsCollapsed()) {
    667    SetSelectionInterlinePosition();
    668  }
    669 
    670  return NS_OK;
    671 }
    672 
    673 Result<EditActionResult, nsresult> HTMLEditor::CanHandleHTMLEditSubAction(
    674    CheckSelectionInReplacedElement
    675        aCheckSelectionInReplacedElement /* = ::Yes */) const {
    676  MOZ_ASSERT(IsEditActionDataAvailable());
    677 
    678  if (NS_WARN_IF(Destroyed())) {
    679    return Err(NS_ERROR_EDITOR_DESTROYED);
    680  }
    681 
    682  // If there is not selection ranges, we should ignore the result.
    683  if (!SelectionRef().RangeCount()) {
    684    return EditActionResult::CanceledResult();
    685  }
    686 
    687  const nsRange* range = SelectionRef().GetRangeAt(0);
    688  nsINode* selStartNode = range->GetStartContainer();
    689  if (NS_WARN_IF(!selStartNode) || NS_WARN_IF(!selStartNode->IsContent())) {
    690    return Err(NS_ERROR_FAILURE);
    691  }
    692 
    693  if (!HTMLEditUtils::IsSimplyEditableNode(*selStartNode)) {
    694    return EditActionResult::CanceledResult();
    695  }
    696 
    697  nsINode* selEndNode = range->GetEndContainer();
    698  if (NS_WARN_IF(!selEndNode) || NS_WARN_IF(!selEndNode->IsContent())) {
    699    return Err(NS_ERROR_FAILURE);
    700  }
    701 
    702  using ReplaceOrVoidElementOption = HTMLEditUtils::ReplaceOrVoidElementOption;
    703 
    704  if (selStartNode == selEndNode) {
    705    if (aCheckSelectionInReplacedElement ==
    706            CheckSelectionInReplacedElement::Yes &&
    707        HTMLEditUtils::GetInclusiveAncestorReplacedOrVoidElement(
    708            *selStartNode->AsContent(),
    709            ReplaceOrVoidElementOption::LookForOnlyNonVoidReplacedElement)) {
    710      return EditActionResult::CanceledResult();
    711    }
    712    return EditActionResult::IgnoredResult();
    713  }
    714 
    715  if (aCheckSelectionInReplacedElement != CheckSelectionInReplacedElement::No &&
    716      (HTMLEditUtils::GetInclusiveAncestorReplacedOrVoidElement(
    717           *selStartNode->AsContent(),
    718           ReplaceOrVoidElementOption::LookForOnlyNonVoidReplacedElement) ||
    719       HTMLEditUtils::GetInclusiveAncestorReplacedOrVoidElement(
    720           *selEndNode->AsContent(),
    721           ReplaceOrVoidElementOption::LookForOnlyNonVoidReplacedElement))) {
    722    return EditActionResult::CanceledResult();
    723  }
    724 
    725  if (!HTMLEditUtils::IsSimplyEditableNode(*selEndNode)) {
    726    return EditActionResult::CanceledResult();
    727  }
    728 
    729  // If anchor node is in an HTML element which has inert attribute, we should
    730  // do nothing.
    731  // XXX HTMLEditor typically uses first range instead of anchor/focus range.
    732  //     Therefore, referring first range here is more reasonable than
    733  //     anchor/focus range of Selection.
    734  nsIContent* const selAnchorContent = SelectionRef().GetDirection() == eDirNext
    735                                           ? nsIContent::FromNode(selStartNode)
    736                                           : nsIContent::FromNode(selEndNode);
    737  if (selAnchorContent &&
    738      HTMLEditUtils::ContentIsInert(*selAnchorContent->AsContent())) {
    739    return EditActionResult::CanceledResult();
    740  }
    741 
    742  // XXX What does it mean the common ancestor is editable?  I have no idea.
    743  //     It should be in same (active) editing host, and even if it's editable,
    744  //     there may be non-editable contents in the range.
    745  nsINode* commonAncestor = range->GetClosestCommonInclusiveAncestor();
    746  if (MOZ_UNLIKELY(!commonAncestor)) {
    747    NS_WARNING(
    748        "AbstractRange::GetClosestCommonInclusiveAncestor() returned nullptr");
    749    return Err(NS_ERROR_FAILURE);
    750  }
    751  return HTMLEditUtils::IsSimplyEditableNode(*commonAncestor)
    752             ? EditActionResult::IgnoredResult()
    753             : EditActionResult::CanceledResult();
    754 }
    755 
    756 MOZ_CAN_RUN_SCRIPT static nsStaticAtom& MarginPropertyAtomForIndent(
    757    nsIContent& aContent) {
    758  nsAutoString direction;
    759  DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedProperty(
    760      aContent, *nsGkAtoms::direction, direction);
    761  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
    762                       "CSSEditUtils::GetComputedProperty(nsGkAtoms::direction)"
    763                       " failed, but ignored");
    764  return direction.EqualsLiteral("rtl") ? *nsGkAtoms::marginRight
    765                                        : *nsGkAtoms::marginLeft;
    766 }
    767 
    768 nsresult HTMLEditor::EnsureCaretNotAfterInvisibleBRElement(
    769    const Element& aEditingHost) {
    770  MOZ_ASSERT(IsEditActionDataAvailable());
    771  MOZ_ASSERT(SelectionRef().IsCollapsed());
    772 
    773  // If we are after a padding `<br>` element for empty last line in the same
    774  // block, then move selection to be before it
    775  const nsRange* firstRange = SelectionRef().GetRangeAt(0);
    776  if (NS_WARN_IF(!firstRange)) {
    777    return NS_ERROR_FAILURE;
    778  }
    779 
    780  EditorRawDOMPoint atSelectionStart(firstRange->StartRef());
    781  if (NS_WARN_IF(!atSelectionStart.IsSet())) {
    782    return NS_ERROR_FAILURE;
    783  }
    784  MOZ_ASSERT(atSelectionStart.IsSetAndValid());
    785 
    786  if (!atSelectionStart.IsInContentNode()) {
    787    return NS_OK;
    788  }
    789 
    790  nsIContent* previousBRElement = HTMLEditUtils::GetPreviousContent(
    791      atSelectionStart, {}, BlockInlineCheck::UseComputedDisplayStyle,
    792      &aEditingHost);
    793  if (!previousBRElement || !previousBRElement->IsHTMLElement(nsGkAtoms::br) ||
    794      !previousBRElement->GetParent() ||
    795      !EditorUtils::IsEditableContent(*previousBRElement->GetParent(),
    796                                      EditorType::HTML) ||
    797      !HTMLEditUtils::IsInvisibleBRElement(*previousBRElement)) {
    798    return NS_OK;
    799  }
    800 
    801  const RefPtr<const Element> blockElementAtSelectionStart =
    802      HTMLEditUtils::GetInclusiveAncestorElement(
    803          *atSelectionStart.ContainerAs<nsIContent>(),
    804          HTMLEditUtils::ClosestBlockElement,
    805          BlockInlineCheck::UseComputedDisplayStyle);
    806  const RefPtr<const Element> parentBlockElementOfBRElement =
    807      HTMLEditUtils::GetAncestorElement(
    808          *previousBRElement, HTMLEditUtils::ClosestBlockElement,
    809          BlockInlineCheck::UseComputedDisplayStyle);
    810 
    811  if (!blockElementAtSelectionStart ||
    812      blockElementAtSelectionStart != parentBlockElementOfBRElement) {
    813    return NS_OK;
    814  }
    815 
    816  // If we are here then the selection is right after a padding <br>
    817  // element for empty last line that is in the same block as the
    818  // selection.  We need to move the selection start to be before the
    819  // padding <br> element.
    820  EditorRawDOMPoint atInvisibleBRElement(previousBRElement);
    821  nsresult rv = CollapseSelectionTo(atInvisibleBRElement);
    822  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    823                       "EditorBase::CollapseSelectionTo() failed");
    824  return rv;
    825 }
    826 
    827 nsresult HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() {
    828  MOZ_ASSERT(IsEditActionDataAvailable());
    829 
    830  if (mPaddingBRElementForEmptyEditor) {
    831    return NS_OK;
    832  }
    833 
    834  // XXX I think that we should not insert a <br> element if we're for a web
    835  // content.  Probably, this is required only by chrome editors such as
    836  // the mail composer of Thunderbird and the composer of SeaMonkey.
    837 
    838  const RefPtr<Element> bodyOrDocumentElement = GetRoot();
    839  if (!bodyOrDocumentElement) {
    840    return NS_OK;
    841  }
    842 
    843  // Skip adding the padding <br> element for empty editor if body
    844  // is read-only.
    845  if (!HTMLEditUtils::IsSimplyEditableNode(*bodyOrDocumentElement)) {
    846    return NS_OK;
    847  }
    848 
    849  // Now we've got the body element. Iterate over the body element's children,
    850  // looking for editable content. If no editable content is found, insert the
    851  // padding <br> element.
    852  EditorType editorType = GetEditorType();
    853  bool isRootEditable =
    854      EditorUtils::IsEditableContent(*bodyOrDocumentElement, editorType);
    855  for (nsIContent* child = bodyOrDocumentElement->GetFirstChild(); child;
    856       child = child->GetNextSibling()) {
    857    if (EditorUtils::IsPaddingBRElementForEmptyEditor(*child) ||
    858        !isRootEditable || EditorUtils::IsEditableContent(*child, editorType) ||
    859        HTMLEditUtils::IsBlockElement(
    860            *child, BlockInlineCheck::UseComputedDisplayStyle)) {
    861      return NS_OK;
    862    }
    863  }
    864 
    865  IgnoredErrorResult ignoredError;
    866  AutoEditSubActionNotifier startToHandleEditSubAction(
    867      *this, EditSubAction::eCreatePaddingBRElementForEmptyEditor,
    868      nsIEditor::eNone, ignoredError);
    869  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
    870    return ignoredError.StealNSResult();
    871  }
    872  NS_WARNING_ASSERTION(
    873      !ignoredError.Failed(),
    874      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
    875 
    876  Result<CreateElementResult, nsresult> insertPaddingBRElementResultOrError =
    877      InsertBRElement(WithTransaction::Yes,
    878                      BRElementType::PaddingForEmptyEditor,
    879                      EditorDOMPoint(bodyOrDocumentElement, 0u));
    880  if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) {
    881    NS_WARNING(
    882        "EditorBase::InsertBRElement(WithTransaction::Yes, "
    883        "BRElementType::PaddingForEmptyEditor) failed");
    884    return insertPaddingBRElementResultOrError.propagateErr();
    885  }
    886  CreateElementResult insertPaddingBRElementResult =
    887      insertPaddingBRElementResultOrError.unwrap();
    888  mPaddingBRElementForEmptyEditor =
    889      HTMLBRElement::FromNode(insertPaddingBRElementResult.GetNewNode());
    890  nsresult rv = insertPaddingBRElementResult.SuggestCaretPointTo(
    891      *this, {SuggestCaret::AndIgnoreTrivialError});
    892  if (NS_FAILED(rv)) {
    893    NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
    894    return rv;
    895  }
    896  NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
    897                       "CaretPoint::SuggestCaretPointTo() failed, but ignored");
    898  return NS_OK;
    899 }
    900 
    901 nsresult HTMLEditor::EnsureNoPaddingBRElementForEmptyEditor() {
    902  MOZ_ASSERT(IsEditActionDataAvailable());
    903 
    904  if (!mPaddingBRElementForEmptyEditor) {
    905    return NS_OK;
    906  }
    907 
    908  // If we're an HTML editor, a mutation event listener may recreate padding
    909  // <br> element for empty editor again during the call of
    910  // DeleteNodeWithTransaction().  So, move it first.
    911  RefPtr<HTMLBRElement> paddingBRElement(
    912      std::move(mPaddingBRElementForEmptyEditor));
    913  nsresult rv = DeleteNodeWithTransaction(*paddingBRElement);
    914  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    915                       "EditorBase::DeleteNodeWithTransaction() failed");
    916  return rv;
    917 }
    918 
    919 nsresult HTMLEditor::ReflectPaddingBRElementForEmptyEditor() {
    920  if (NS_WARN_IF(!mRootElement)) {
    921    NS_WARNING("Failed to handle padding BR element due to no root element");
    922    return NS_ERROR_FAILURE;
    923  }
    924  // The idea here is to see if the magic empty node has suddenly reappeared. If
    925  // it has, set our state so we remember it. There is a tradeoff between doing
    926  // here and at redo, or doing it everywhere else that might care.  Since undo
    927  // and redo are relatively rare, it makes sense to take the (small)
    928  // performance hit here.
    929  nsIContent* firstLeafChild = HTMLEditUtils::GetFirstLeafContent(
    930      *mRootElement, {LeafNodeType::OnlyLeafNode});
    931  if (firstLeafChild &&
    932      EditorUtils::IsPaddingBRElementForEmptyEditor(*firstLeafChild)) {
    933    mPaddingBRElementForEmptyEditor =
    934        static_cast<HTMLBRElement*>(firstLeafChild);
    935  } else {
    936    mPaddingBRElementForEmptyEditor = nullptr;
    937  }
    938  return NS_OK;
    939 }
    940 
    941 nsresult HTMLEditor::PrepareInlineStylesForCaret() {
    942  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
    943  MOZ_ASSERT(SelectionRef().IsCollapsed());
    944 
    945  // XXX This method works with the top level edit sub-action, but this
    946  //     must be wrong if we are handling nested edit action.
    947 
    948  if (TopLevelEditSubActionDataRef().mDidDeleteSelection) {
    949    switch (GetTopLevelEditSubAction()) {
    950      case EditSubAction::eInsertText:
    951      case EditSubAction::eInsertTextComingFromIME:
    952      case EditSubAction::eDeleteSelectedContent: {
    953        nsresult rv = ReapplyCachedStyles();
    954        if (NS_FAILED(rv)) {
    955          NS_WARNING("HTMLEditor::ReapplyCachedStyles() failed");
    956          return rv;
    957        }
    958        break;
    959      }
    960      default:
    961        break;
    962    }
    963  }
    964  // For most actions we want to clear the cached styles, but there are
    965  // exceptions
    966  if (!IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
    967    TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear();
    968  }
    969  return NS_OK;
    970 }
    971 
    972 Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
    973    const nsAString& aInsertionString, InsertTextFor aPurpose) {
    974  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
    975 
    976  MOZ_LOG(
    977      gTextInputLog, LogLevel::Info,
    978      ("%p HTMLEditor::HandleInsertText(aInsertionString=\"%s\", aPurpose=%s)",
    979       this, NS_ConvertUTF16toUTF8(aInsertionString).get(),
    980       ToString(aPurpose).c_str()));
    981 
    982  {
    983    Result<EditActionResult, nsresult> result =
    984        CanHandleHTMLEditSubAction(CheckSelectionInReplacedElement::No);
    985    if (MOZ_UNLIKELY(result.isErr())) {
    986      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
    987      return result;
    988    }
    989    if (result.inspect().Canceled()) {
    990      return result;
    991    }
    992  }
    993 
    994  UndefineCaretBidiLevel();
    995 
    996  // If the selection isn't collapsed, delete it.  Don't delete existing inline
    997  // tags, because we're hopefully going to insert text (bug 787432).
    998  if (!SelectionRef().IsCollapsed() &&
    999      !InsertingTextForExtantComposition(aPurpose)) {
   1000    nsresult rv =
   1001        DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
   1002    if (NS_FAILED(rv)) {
   1003      NS_WARNING(
   1004          "EditorBase::DeleteSelectionAsSubAction(nsIEditor::eNone, "
   1005          "nsIEditor::eNoStrip) failed");
   1006      return Err(rv);
   1007    }
   1008  }
   1009 
   1010  const RefPtr<Element> editingHost =
   1011      ComputeEditingHost(LimitInBodyElement::No);
   1012  if (NS_WARN_IF(!editingHost)) {
   1013    return Err(NS_ERROR_FAILURE);
   1014  }
   1015 
   1016  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
   1017  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   1018    return Err(NS_ERROR_EDITOR_DESTROYED);
   1019  }
   1020  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   1021                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
   1022                       "failed, but ignored");
   1023 
   1024  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
   1025    nsresult rv = EnsureCaretNotAfterInvisibleBRElement(*editingHost);
   1026    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   1027      return Err(NS_ERROR_EDITOR_DESTROYED);
   1028    }
   1029    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   1030                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
   1031                         "failed, but ignored");
   1032    if (NS_SUCCEEDED(rv)) {
   1033      nsresult rv = PrepareInlineStylesForCaret();
   1034      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   1035        return Err(NS_ERROR_EDITOR_DESTROYED);
   1036      }
   1037      NS_WARNING_ASSERTION(
   1038          NS_SUCCEEDED(rv),
   1039          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
   1040    }
   1041  }
   1042 
   1043  EditorDOMPoint pointToInsert = [&]() {
   1044    if (InsertingTextForExtantComposition(aPurpose)) {
   1045      auto compositionStartPoint =
   1046          GetFirstIMESelectionStartPoint<EditorDOMPoint>();
   1047      if (MOZ_LIKELY(compositionStartPoint.IsSet())) {
   1048        return compositionStartPoint;
   1049      }
   1050    }
   1051    return GetFirstSelectionStartPoint<EditorDOMPoint>();
   1052  }();
   1053 
   1054  MOZ_LOG(gTextInputLog, LogLevel::Info,
   1055          ("%p HTMLEditor::HandleInsertText(), pointToInsert=%s", this,
   1056           ToString(pointToInsert).c_str()));
   1057 
   1058  if (NS_WARN_IF(!pointToInsert.IsSet())) {
   1059    return Err(NS_ERROR_FAILURE);
   1060  }
   1061 
   1062  // for every property that is set, insert a new inline style node
   1063  // XXX I think that if this is second or later composition update, we should
   1064  // not change the style because we won't update composition with keeping
   1065  // inline elements in composing range.
   1066  Result<EditorDOMPoint, nsresult> setStyleResult =
   1067      CreateStyleForInsertText(pointToInsert, *editingHost);
   1068  if (MOZ_UNLIKELY(setStyleResult.isErr())) {
   1069    NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed");
   1070    return setStyleResult.propagateErr();
   1071  }
   1072  if (setStyleResult.inspect().IsSet()) {
   1073    pointToInsert = setStyleResult.unwrap();
   1074  }
   1075 
   1076  if (NS_WARN_IF(!pointToInsert.IsSetAndValid()) ||
   1077      NS_WARN_IF(!pointToInsert.IsInContentNode())) {
   1078    return Err(NS_ERROR_FAILURE);
   1079  }
   1080  MOZ_ASSERT(pointToInsert.IsSetAndValid());
   1081 
   1082  // If the point is not in an element which can contain text nodes, climb up
   1083  // the DOM tree.
   1084  pointToInsert = HTMLEditUtils::GetPossiblePointToInsert(
   1085      pointToInsert, *nsGkAtoms::textTagName, *editingHost);
   1086  if (NS_WARN_IF(!pointToInsert.IsSet())) {
   1087    return Err(NS_ERROR_FAILURE);
   1088  }
   1089  MOZ_ASSERT(pointToInsert.IsInContentNode());
   1090 
   1091  if (InsertingTextForComposition(aPurpose)) {
   1092    if (aInsertionString.IsEmpty()) {
   1093      // Right now the WhiteSpaceVisibilityKeeper code bails on empty strings,
   1094      // but IME needs the InsertTextWithTransaction() call to still happen
   1095      // since empty strings are meaningful there.
   1096      Result<InsertTextResult, nsresult> insertEmptyTextResultOrError =
   1097          InsertTextWithTransaction(aInsertionString, pointToInsert,
   1098                                    InsertTextTo::ExistingTextNodeIfAvailable);
   1099      if (MOZ_UNLIKELY(insertEmptyTextResultOrError.isErr())) {
   1100        NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
   1101        return insertEmptyTextResultOrError.propagateErr();
   1102      }
   1103      InsertTextResult insertEmptyTextResult =
   1104          insertEmptyTextResultOrError.unwrap();
   1105      // InsertTextWithTransaction() doesn not suggest caret position if it's
   1106      // called for IME composition. However, for the safety, let's ignore the
   1107      // caret position explicitly.
   1108      insertEmptyTextResult.IgnoreCaretPointSuggestion();
   1109      nsresult rv = EnsureNoFollowingUnnecessaryLineBreak(
   1110          insertEmptyTextResult.EndOfInsertedTextRef());
   1111      if (NS_FAILED(rv)) {
   1112        NS_WARNING(
   1113            "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
   1114        return Err(rv);
   1115      }
   1116      const EditorDOMPoint& endOfInsertedText =
   1117          insertEmptyTextResult.EndOfInsertedTextRef();
   1118      if (endOfInsertedText.IsInTextNode() &&
   1119          !endOfInsertedText.IsStartOfContainer()) {
   1120        nsresult rv = WhiteSpaceVisibilityKeeper::
   1121            NormalizeVisibleWhiteSpacesWithoutDeletingInvisibleWhiteSpaces(
   1122                *this, endOfInsertedText.AsInText().PreviousPoint());
   1123        if (NS_FAILED(rv)) {
   1124          NS_WARNING(
   1125              "WhiteSpaceVisibilityKeeper::"
   1126              "NormalizeVisibleWhiteSpacesWithoutDeletingInvisibleWhiteSpaces()"
   1127              " failed");
   1128          return Err(rv);
   1129        }
   1130        if (NS_WARN_IF(
   1131                !endOfInsertedText.IsInContentNodeAndValidInComposedDoc())) {
   1132          return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1133        }
   1134      }
   1135      // If we replaced non-empty composition string with an empty string,
   1136      // its preceding character may be a collapsible ASCII white-space.
   1137      // Therefore, we may need to insert a padding <br> after the white-space.
   1138      Result<CreateLineBreakResult, nsresult>
   1139          insertPaddingBRElementResultOrError = InsertPaddingBRElementIfNeeded(
   1140              insertEmptyTextResult.EndOfInsertedTextRef(), nsIEditor::eNoStrip,
   1141              *editingHost);
   1142      if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) {
   1143        NS_WARNING(
   1144            "HTMLEditor::InsertPaddingBRElementIfNeeded(eNoStrip) failed");
   1145        return insertPaddingBRElementResultOrError.propagateErr();
   1146      }
   1147      insertPaddingBRElementResultOrError.unwrap().IgnoreCaretPointSuggestion();
   1148      // Then, collapse caret after the empty text inserted position, i.e.,
   1149      // whether the removed composition string was.
   1150      if (AllowsTransactionsToChangeSelection()) {
   1151        nsresult rv = CollapseSelectionTo(endOfInsertedText);
   1152        if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   1153          return Err(rv);
   1154        }
   1155        NS_WARNING_ASSERTION(
   1156            rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   1157            "CaretPoint::SuggestCaretPointTo() failed, but ignored");
   1158      }
   1159      return EditActionResult::HandledResult();
   1160    }
   1161 
   1162    EditorDOMPoint endOfInsertedText;
   1163    {
   1164      AutoTrackDOMPoint trackPointToInsert(RangeUpdaterRef(), &pointToInsert);
   1165      const auto compositionEndPoint =
   1166          GetLastIMESelectionEndPoint<EditorDOMPoint>();
   1167      Result<InsertTextResult, nsresult> replaceTextResult =
   1168          WhiteSpaceVisibilityKeeper::InsertOrUpdateCompositionString(
   1169              *this, aInsertionString,
   1170              compositionEndPoint.IsSet()
   1171                  ? EditorDOMRange(pointToInsert, compositionEndPoint)
   1172                  : EditorDOMRange(pointToInsert),
   1173              aPurpose);
   1174      if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
   1175        NS_WARNING("WhiteSpaceVisibilityKeeper::ReplaceText() failed");
   1176        return replaceTextResult.propagateErr();
   1177      }
   1178      InsertTextResult unwrappedReplaceTextResult = replaceTextResult.unwrap();
   1179      nsresult rv = EnsureNoFollowingUnnecessaryLineBreak(
   1180          unwrappedReplaceTextResult.EndOfInsertedTextRef());
   1181      if (NS_FAILED(rv)) {
   1182        NS_WARNING(
   1183            "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
   1184        return Err(rv);
   1185      }
   1186      endOfInsertedText = unwrappedReplaceTextResult.EndOfInsertedTextRef();
   1187      if (InsertingTextForCommittingComposition(aPurpose)) {
   1188        // If we're committing the composition,
   1189        // WhiteSpaceVisibilityKeeper::InsertOrUpdateCompositionString() may
   1190        // replace the last character of the composition string when it's a
   1191        // white-space.  Then, Selection will be moved before the last
   1192        // character.  So, we need to adjust Selection here.
   1193        nsresult rv = unwrappedReplaceTextResult.SuggestCaretPointTo(
   1194            *this, {SuggestCaret::OnlyIfHasSuggestion,
   1195                    SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   1196                    SuggestCaret::AndIgnoreTrivialError});
   1197        if (NS_FAILED(rv)) {
   1198          NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
   1199          return Err(rv);
   1200        }
   1201        NS_WARNING_ASSERTION(
   1202            rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   1203            "CaretPoint::SuggestCaretPoint() failed, but ignored");
   1204      } else {
   1205        // CompositionTransaction should've set selection so that we should
   1206        // ignore caret suggestion.
   1207        unwrappedReplaceTextResult.IgnoreCaretPointSuggestion();
   1208      }
   1209    }
   1210 
   1211    if (!InsertingTextForCommittingComposition(aPurpose)) {
   1212      const auto newCompositionStartPoint =
   1213          GetFirstIMESelectionStartPoint<EditorDOMPoint>();
   1214      const auto newCompositionEndPoint =
   1215          GetLastIMESelectionEndPoint<EditorDOMPoint>();
   1216      if (NS_WARN_IF(!newCompositionStartPoint.IsSet()) ||
   1217          NS_WARN_IF(!newCompositionEndPoint.IsSet())) {
   1218        // Mutation event listener has changed the DOM tree...
   1219        return EditActionResult::HandledResult();
   1220      }
   1221      nsresult rv =
   1222          TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
   1223              newCompositionStartPoint.ToRawRangeBoundary(),
   1224              newCompositionEndPoint.ToRawRangeBoundary());
   1225      if (NS_FAILED(rv)) {
   1226        NS_WARNING("nsRange::SetStartAndEnd() failed");
   1227        return Err(rv);
   1228      }
   1229    } else {
   1230      if (NS_WARN_IF(!endOfInsertedText.IsSetAndValidInComposedDoc()) ||
   1231          NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) {
   1232        return EditActionResult::HandledResult();
   1233      }
   1234      nsresult rv =
   1235          TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
   1236              pointToInsert.ToRawRangeBoundary(),
   1237              endOfInsertedText.ToRawRangeBoundary());
   1238      if (NS_FAILED(rv)) {
   1239        NS_WARNING("nsRange::SetStartAndEnd() failed");
   1240        return Err(rv);
   1241      }
   1242    }
   1243    return EditActionResult::HandledResult();
   1244  }
   1245 
   1246  MOZ_ASSERT(!InsertingTextForComposition(aPurpose));
   1247 
   1248  // find where we are
   1249  EditorDOMPoint currentPoint(pointToInsert);
   1250 
   1251  // is our text going to be PREformatted?
   1252  // We remember this so that we know how to handle tabs.
   1253  const bool isWhiteSpaceCollapsible = !EditorUtils::IsWhiteSpacePreformatted(
   1254      *pointToInsert.ContainerAs<nsIContent>());
   1255  const Maybe<LineBreakType> lineBreakType = GetPreferredLineBreakType(
   1256      *pointToInsert.ContainerAs<nsIContent>(), *editingHost);
   1257  if (NS_WARN_IF(lineBreakType.isNothing())) {
   1258    return Err(NS_ERROR_FAILURE);
   1259  }
   1260 
   1261  // turn off the edit listener: we know how to
   1262  // build the "doc changed range" ourselves, and it's
   1263  // must faster to do it once here than to track all
   1264  // the changes one at a time.
   1265  AutoRestore<bool> disableListener(
   1266      EditSubActionDataRef().mAdjustChangedRangeFromListener);
   1267  EditSubActionDataRef().mAdjustChangedRangeFromListener = false;
   1268 
   1269  // don't change my selection in subtransactions
   1270  AutoTransactionsConserveSelection dontChangeMySelection(*this);
   1271  {
   1272    AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToInsert);
   1273 
   1274    const auto GetInsertTextTo = [](int32_t aInclusiveNextLinefeedOffset,
   1275                                    uint32_t aLineStartOffset) {
   1276      if (aInclusiveNextLinefeedOffset > 0) {
   1277        return aLineStartOffset > 0
   1278                   // If we'll insert a <br> and we're inserting 2nd or later
   1279                   // line, we should always create new `Text` since it'll be
   1280                   // between 2 <br> elements.
   1281                   ? InsertTextTo::AlwaysCreateNewTextNode
   1282                   // If we'll insert a <br> and we're inserting first line,
   1283                   // we should append text to preceding text node, but
   1284                   // we don't want to insert it to a a following text node
   1285                   // because of avoiding to split the `Text`.
   1286                   : InsertTextTo::ExistingTextNodeIfAvailableAndNotStart;
   1287      }
   1288      // If we're inserting the last line, the text should be inserted to
   1289      // start of the following `Text` if there is or middle of the `Text`
   1290      // at insertion position if we're inserting only the line.
   1291      return InsertTextTo::ExistingTextNodeIfAvailable;
   1292    };
   1293 
   1294    // for efficiency, break out the pre case separately.  This is because
   1295    // its a lot cheaper to search the input string for only newlines than
   1296    // it is to search for both tabs and newlines.
   1297    if (!isWhiteSpaceCollapsible || IsPlaintextMailComposer()) {
   1298      if (*lineBreakType == LineBreakType::Linefeed) {
   1299        // Both Chrome and us inserts a preformatted linefeed with its own
   1300        // `Text` node in various cases.  However, when inserting multiline
   1301        // text, we should insert a `Text` because Chrome does so and the
   1302        // comment field in https://discussions.apple.com/ handles the new
   1303        // `Text` to split each line into a paragraph.  At that time, it's
   1304        // not assumed that inserted text is split at every linefeed.
   1305        MOZ_ASSERT(*lineBreakType == LineBreakType::Linefeed);
   1306        Result<InsertTextResult, nsresult> insertTextResult =
   1307            InsertTextWithTransaction(
   1308                aInsertionString, currentPoint,
   1309                InsertTextTo::ExistingTextNodeIfAvailable);
   1310        if (MOZ_UNLIKELY(insertTextResult.isErr())) {
   1311          NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
   1312          return insertTextResult.propagateErr();
   1313        }
   1314        // Ignore the caret suggestion because of `dontChangeMySelection`
   1315        // above.
   1316        insertTextResult.inspect().IgnoreCaretPointSuggestion();
   1317        if (insertTextResult.inspect().Handled()) {
   1318          pointToInsert = currentPoint = insertTextResult.unwrap()
   1319                                             .EndOfInsertedTextRef()
   1320                                             .To<EditorDOMPoint>();
   1321        } else {
   1322          pointToInsert = currentPoint;
   1323        }
   1324      } else {
   1325        MOZ_ASSERT(*lineBreakType == LineBreakType::BRElement);
   1326        uint32_t nextOffset = 0;
   1327        while (nextOffset < aInsertionString.Length()) {
   1328          const uint32_t lineStartOffset = nextOffset;
   1329          const int32_t inclusiveNextLinefeedOffset =
   1330              aInsertionString.FindChar(nsCRT::LF, lineStartOffset);
   1331          const uint32_t lineLength =
   1332              inclusiveNextLinefeedOffset != -1
   1333                  ? static_cast<uint32_t>(inclusiveNextLinefeedOffset) -
   1334                        lineStartOffset
   1335                  : aInsertionString.Length() - lineStartOffset;
   1336          if (lineLength) {
   1337            // lineText does not include the preformatted line break.
   1338            const nsDependentSubstring lineText(aInsertionString,
   1339                                                lineStartOffset, lineLength);
   1340            Result<InsertTextResult, nsresult> insertTextResult =
   1341                InsertTextWithTransaction(
   1342                    lineText, currentPoint,
   1343                    GetInsertTextTo(inclusiveNextLinefeedOffset,
   1344                                    lineStartOffset));
   1345            if (MOZ_UNLIKELY(insertTextResult.isErr())) {
   1346              NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
   1347              return insertTextResult.propagateErr();
   1348            }
   1349            // Ignore the caret suggestion because of `dontChangeMySelection`
   1350            // above.
   1351            insertTextResult.inspect().IgnoreCaretPointSuggestion();
   1352            if (insertTextResult.inspect().Handled()) {
   1353              pointToInsert = currentPoint = insertTextResult.unwrap()
   1354                                                 .EndOfInsertedTextRef()
   1355                                                 .To<EditorDOMPoint>();
   1356            } else {
   1357              pointToInsert = currentPoint;
   1358            }
   1359            if (inclusiveNextLinefeedOffset < 0) {
   1360              break;  // We reached the last line
   1361            }
   1362          }
   1363          MOZ_ASSERT(inclusiveNextLinefeedOffset >= 0);
   1364          Result<CreateLineBreakResult, nsresult> insertLineBreakResultOrError =
   1365              InsertLineBreak(WithTransaction::Yes, *lineBreakType,
   1366                              currentPoint);
   1367          if (MOZ_UNLIKELY(insertLineBreakResultOrError.isErr())) {
   1368            NS_WARNING(nsPrintfCString("HTMLEditor::InsertLineBreak("
   1369                                       "WithTransaction::Yes, %s) failed",
   1370                                       ToString(*lineBreakType).c_str())
   1371                           .get());
   1372            return insertLineBreakResultOrError.propagateErr();
   1373          }
   1374          CreateLineBreakResult insertLineBreakResult =
   1375              insertLineBreakResultOrError.unwrap();
   1376          // We don't want to update selection here because we've blocked
   1377          // InsertNodeTransaction updating selection with
   1378          // dontChangeMySelection.
   1379          insertLineBreakResult.IgnoreCaretPointSuggestion();
   1380          MOZ_ASSERT(!AllowsTransactionsToChangeSelection());
   1381 
   1382          nextOffset = inclusiveNextLinefeedOffset + 1;
   1383          pointToInsert =
   1384              insertLineBreakResult.AfterLineBreak<EditorDOMPoint>();
   1385          currentPoint.SetAfter(&insertLineBreakResult.LineBreakContentRef());
   1386          if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc()) ||
   1387              NS_WARN_IF(!currentPoint.IsSetAndValidInComposedDoc())) {
   1388            return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1389          }
   1390        }
   1391      }
   1392    } else {
   1393      uint32_t nextOffset = 0;
   1394      while (nextOffset < aInsertionString.Length()) {
   1395        const uint32_t lineStartOffset = nextOffset;
   1396        const int32_t inclusiveNextLinefeedOffset =
   1397            aInsertionString.FindChar(nsCRT::LF, lineStartOffset);
   1398        const uint32_t lineLength =
   1399            inclusiveNextLinefeedOffset != -1
   1400                ? static_cast<uint32_t>(inclusiveNextLinefeedOffset) -
   1401                      lineStartOffset
   1402                : aInsertionString.Length() - lineStartOffset;
   1403 
   1404        if (lineLength) {
   1405          auto insertTextResult =
   1406              [&]() MOZ_CAN_RUN_SCRIPT -> Result<InsertTextResult, nsresult> {
   1407            // lineText does not include the preformatted line break.
   1408            const nsDependentSubstring lineText(aInsertionString,
   1409                                                lineStartOffset, lineLength);
   1410            if (!lineText.Contains(u'\t')) {
   1411              return WhiteSpaceVisibilityKeeper::InsertText(
   1412                  *this, lineText, currentPoint,
   1413                  GetInsertTextTo(inclusiveNextLinefeedOffset,
   1414                                  lineStartOffset));
   1415            }
   1416            nsAutoString formattedLineText(lineText);
   1417            formattedLineText.ReplaceSubstring(u"\t"_ns, u"    "_ns);
   1418            return WhiteSpaceVisibilityKeeper::InsertText(
   1419                *this, formattedLineText, currentPoint,
   1420                GetInsertTextTo(inclusiveNextLinefeedOffset, lineStartOffset));
   1421          }();
   1422          if (MOZ_UNLIKELY(insertTextResult.isErr())) {
   1423            NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
   1424            return insertTextResult.propagateErr();
   1425          }
   1426          // Ignore the caret suggestion because of `dontChangeMySelection`
   1427          // above.
   1428          insertTextResult.inspect().IgnoreCaretPointSuggestion();
   1429          if (insertTextResult.inspect().Handled()) {
   1430            pointToInsert = currentPoint =
   1431                insertTextResult.unwrap().EndOfInsertedTextRef();
   1432          } else {
   1433            pointToInsert = currentPoint;
   1434          }
   1435          if (inclusiveNextLinefeedOffset < 0) {
   1436            break;  // We reached the last line
   1437          }
   1438        }
   1439 
   1440        Result<CreateLineBreakResult, nsresult> insertLineBreakResultOrError =
   1441            WhiteSpaceVisibilityKeeper::InsertLineBreak(*lineBreakType, *this,
   1442                                                        currentPoint);
   1443        if (MOZ_UNLIKELY(insertLineBreakResultOrError.isErr())) {
   1444          NS_WARNING(
   1445              nsPrintfCString(
   1446                  "WhiteSpaceVisibilityKeeper::InsertLineBreak(%s) failed",
   1447                  ToString(*lineBreakType).c_str())
   1448                  .get());
   1449          return insertLineBreakResultOrError.propagateErr();
   1450        }
   1451        CreateLineBreakResult insertLineBreakResult =
   1452            insertLineBreakResultOrError.unwrap();
   1453        // TODO: Some methods called for handling non-preformatted text use
   1454        //       ComputeEditingHost().  Therefore, they depend on the latest
   1455        //       selection.  So we cannot skip updating selection here.
   1456        nsresult rv = insertLineBreakResult.SuggestCaretPointTo(
   1457            *this, {SuggestCaret::OnlyIfHasSuggestion,
   1458                    SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   1459                    SuggestCaret::AndIgnoreTrivialError});
   1460        if (NS_FAILED(rv)) {
   1461          NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
   1462          return Err(rv);
   1463        }
   1464        NS_WARNING_ASSERTION(
   1465            rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   1466            "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
   1467        nextOffset = inclusiveNextLinefeedOffset + 1;
   1468        pointToInsert = insertLineBreakResult.AfterLineBreak<EditorDOMPoint>();
   1469        currentPoint.SetAfter(&insertLineBreakResult.LineBreakContentRef());
   1470        if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc()) ||
   1471            NS_WARN_IF(!currentPoint.IsSetAndValidInComposedDoc())) {
   1472          return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1473        }
   1474      }
   1475    }
   1476 
   1477    // After this block, pointToInsert is updated by AutoTrackDOMPoint.
   1478  }
   1479 
   1480  if (currentPoint.IsSet()) {
   1481    // If we appended a collapsible white-space to the end of the text node,
   1482    // its following content may be removed by the web app.  Then, we need to
   1483    // keep it visible even if it becomes immediately before a block boundary.
   1484    // For referring the node from our mutation observer, we need to store the
   1485    // text node temporarily.
   1486    if (currentPoint.IsInTextNode() &&
   1487        MOZ_LIKELY(!currentPoint.IsStartOfContainer()) &&
   1488        currentPoint.IsEndOfContainer() &&
   1489        currentPoint.IsPreviousCharCollapsibleASCIISpace()) {
   1490      mLastCollapsibleWhiteSpaceAppendedTextNode =
   1491          currentPoint.ContainerAs<Text>();
   1492    }
   1493    nsresult rv = EnsureNoFollowingUnnecessaryLineBreak(currentPoint);
   1494    if (NS_FAILED(rv)) {
   1495      NS_WARNING("HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
   1496      return Err(rv);
   1497    }
   1498    currentPoint.SetInterlinePosition(InterlinePosition::EndOfLine);
   1499    rv = CollapseSelectionTo(currentPoint);
   1500    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   1501      return Err(NS_ERROR_EDITOR_DESTROYED);
   1502    }
   1503    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   1504                         "Selection::Collapse() failed, but ignored");
   1505 
   1506    // manually update the doc changed range so that AfterEdit will clean up
   1507    // the correct portion of the document.
   1508    rv = TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
   1509        pointToInsert.ToRawRangeBoundary(), currentPoint.ToRawRangeBoundary());
   1510    if (NS_FAILED(rv)) {
   1511      NS_WARNING("nsRange::SetStartAndEnd() failed");
   1512      return Err(rv);
   1513    }
   1514    return EditActionResult::HandledResult();
   1515  }
   1516 
   1517  DebugOnly<nsresult> rvIgnored =
   1518      SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine);
   1519  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   1520                       "Selection::SetInterlinePosition(InterlinePosition::"
   1521                       "EndOfLine) failed, but ignored");
   1522  rv = TopLevelEditSubActionDataRef().mChangedRange->CollapseTo(pointToInsert);
   1523  if (NS_FAILED(rv)) {
   1524    NS_WARNING("nsRange::CollapseTo() failed");
   1525    return Err(rv);
   1526  }
   1527  return EditActionResult::HandledResult();
   1528 }
   1529 
   1530 HTMLEditor::CharPointData
   1531 HTMLEditor::GetPreviousCharPointDataForNormalizingWhiteSpaces(
   1532    const EditorDOMPointInText& aPoint) const {
   1533  MOZ_ASSERT(aPoint.IsSetAndValid());
   1534 
   1535  if (!aPoint.IsStartOfContainer()) {
   1536    return CharPointData::InSameTextNode(
   1537        HTMLEditor::GetPreviousCharPointType(aPoint));
   1538  }
   1539  const auto previousCharPoint =
   1540      WSRunScanner::GetPreviousCharPoint<EditorRawDOMPointInText>(
   1541          {WSRunScanner::Option::OnlyEditableNodes}, aPoint);
   1542  if (!previousCharPoint.IsSet()) {
   1543    return CharPointData::InDifferentTextNode(CharPointType::TextEnd);
   1544  }
   1545  return CharPointData::InDifferentTextNode(
   1546      HTMLEditor::GetCharPointType(previousCharPoint));
   1547 }
   1548 
   1549 HTMLEditor::CharPointData
   1550 HTMLEditor::GetInclusiveNextCharPointDataForNormalizingWhiteSpaces(
   1551    const EditorDOMPointInText& aPoint) const {
   1552  MOZ_ASSERT(aPoint.IsSetAndValid());
   1553 
   1554  if (!aPoint.IsEndOfContainer()) {
   1555    return CharPointData::InSameTextNode(HTMLEditor::GetCharPointType(aPoint));
   1556  }
   1557  const auto nextCharPoint =
   1558      WSRunScanner::GetInclusiveNextCharPoint<EditorRawDOMPointInText>(
   1559          {WSRunScanner::Option::OnlyEditableNodes}, aPoint);
   1560  if (!nextCharPoint.IsSet()) {
   1561    return CharPointData::InDifferentTextNode(CharPointType::TextEnd);
   1562  }
   1563  return CharPointData::InDifferentTextNode(
   1564      HTMLEditor::GetCharPointType(nextCharPoint));
   1565 }
   1566 
   1567 // static
   1568 void HTMLEditor::NormalizeAllWhiteSpaceSequences(
   1569    nsString& aResult, const CharPointData& aPreviousCharPointData,
   1570    const CharPointData& aNextCharPointData, Linefeed aLinefeed) {
   1571  MOZ_ASSERT(!aResult.IsEmpty());
   1572 
   1573  const auto IsCollapsibleChar = [&](char16_t aChar) {
   1574    if (aChar == HTMLEditUtils::kNewLine) {
   1575      return aLinefeed == Linefeed::Preformatted;
   1576    }
   1577    return nsCRT::IsAsciiSpace(aChar);
   1578  };
   1579  const auto IsCollapsibleCharOrNBSP = [&](char16_t aChar) {
   1580    return aChar == HTMLEditUtils::kNBSP || IsCollapsibleChar(aChar);
   1581  };
   1582 
   1583  const uint32_t length = aResult.Length();
   1584  for (uint32_t offset = 0; offset < length; offset++) {
   1585    const char16_t ch = aResult[offset];
   1586    if (!IsCollapsibleCharOrNBSP(ch)) {
   1587      continue;
   1588    }
   1589    const CharPointData previousCharData = [&]() {
   1590      if (offset) {
   1591        const char16_t prevChar = aResult[offset - 1u];
   1592        return CharPointData::InSameTextNode(
   1593            prevChar == HTMLEditUtils::kNewLine
   1594                ? CharPointType::PreformattedLineBreak
   1595                : CharPointType::VisibleChar);
   1596      }
   1597      return aPreviousCharPointData;
   1598    }();
   1599    const uint32_t endOffset = [&]() {
   1600      for (const uint32_t i : IntegerRange(offset, length)) {
   1601        if (IsCollapsibleCharOrNBSP(aResult[i])) {
   1602          continue;
   1603        }
   1604        return i;
   1605      }
   1606      return length;
   1607    }();
   1608    const CharPointData nextCharData = [&]() {
   1609      if (endOffset < length) {
   1610        const char16_t nextChar = aResult[endOffset];
   1611        return CharPointData::InSameTextNode(
   1612            nextChar == HTMLEditUtils::kNewLine
   1613                ? CharPointType::PreformattedLineBreak
   1614                : CharPointType::VisibleChar);
   1615      }
   1616      return aNextCharPointData;
   1617    }();
   1618    HTMLEditor::ReplaceStringWithNormalizedWhiteSpaceSequence(
   1619        aResult, offset, endOffset - offset, previousCharData, nextCharData);
   1620    offset = endOffset;
   1621  }
   1622 }
   1623 
   1624 // static
   1625 void HTMLEditor::GenerateWhiteSpaceSequence(
   1626    nsString& aResult, uint32_t aLength,
   1627    const CharPointData& aPreviousCharPointData,
   1628    const CharPointData& aNextCharPointData) {
   1629  MOZ_ASSERT(aResult.IsEmpty());
   1630  MOZ_ASSERT(aLength);
   1631 
   1632  aResult.SetLength(aLength);
   1633  HTMLEditor::ReplaceStringWithNormalizedWhiteSpaceSequence(
   1634      aResult, 0u, aLength, aPreviousCharPointData, aNextCharPointData);
   1635 }
   1636 
   1637 // static
   1638 void HTMLEditor::ReplaceStringWithNormalizedWhiteSpaceSequence(
   1639    nsString& aResult, uint32_t aOffset, uint32_t aLength,
   1640    const CharPointData& aPreviousCharPointData,
   1641    const CharPointData& aNextCharPointData) {
   1642  MOZ_ASSERT(!aResult.IsEmpty());
   1643  MOZ_ASSERT(aLength);
   1644  MOZ_ASSERT(aOffset < aResult.Length());
   1645  MOZ_ASSERT(aOffset + aLength <= aResult.Length());
   1646 
   1647  // For now, this method does not assume that result will be append to
   1648  // white-space sequence in the text node.
   1649  MOZ_ASSERT(aPreviousCharPointData.AcrossTextNodeBoundary() ||
   1650             !aPreviousCharPointData.IsCollapsibleWhiteSpace());
   1651  // For now, this method does not assume that the result will be inserted
   1652  // into white-space sequence nor start of white-space sequence.
   1653  MOZ_ASSERT(aNextCharPointData.AcrossTextNodeBoundary() ||
   1654             !aNextCharPointData.IsCollapsibleWhiteSpace());
   1655 
   1656  if (aLength == 1) {
   1657    // Even if previous/next char is in different text node, we should put
   1658    // an ASCII white-space between visible characters.
   1659    // XXX This means that this does not allow to put an NBSP in HTML editor
   1660    //     without preformatted style.  However, Chrome has same issue too.
   1661    if (aPreviousCharPointData.Type() == CharPointType::VisibleChar &&
   1662        aNextCharPointData.Type() == CharPointType::VisibleChar) {
   1663      aResult.SetCharAt(HTMLEditUtils::kSpace, aOffset);
   1664      return;
   1665    }
   1666    // If it's start or end of text, put an NBSP.
   1667    if (aPreviousCharPointData.Type() == CharPointType::TextEnd ||
   1668        aNextCharPointData.Type() == CharPointType::TextEnd) {
   1669      aResult.SetCharAt(HTMLEditUtils::kNBSP, aOffset);
   1670      return;
   1671    }
   1672    // If the character is next to a preformatted linefeed, we need to put
   1673    // an NBSP for avoiding collapsed into the linefeed.
   1674    if (aPreviousCharPointData.Type() == CharPointType::PreformattedLineBreak ||
   1675        aNextCharPointData.Type() == CharPointType::PreformattedLineBreak) {
   1676      aResult.SetCharAt(HTMLEditUtils::kNBSP, aOffset);
   1677      return;
   1678    }
   1679    // Now, the white-space will be inserted to a white-space sequence, but not
   1680    // end of text.  We can put an ASCII white-space only when both sides are
   1681    // not ASCII white-spaces.
   1682    aResult.SetCharAt(
   1683        aPreviousCharPointData.Type() == CharPointType::ASCIIWhiteSpace ||
   1684                aNextCharPointData.Type() == CharPointType::ASCIIWhiteSpace
   1685            ? HTMLEditUtils::kNBSP
   1686            : HTMLEditUtils::kSpace,
   1687        aOffset);
   1688    return;
   1689  }
   1690 
   1691  // Generate pairs of NBSP and ASCII white-space.
   1692  bool appendNBSP = true;  // Basically, starts with an NBSP.
   1693  char16_t* const lastChar = aResult.BeginWriting() + aOffset + aLength - 1;
   1694  for (char16_t* iter = aResult.BeginWriting() + aOffset; iter != lastChar;
   1695       iter++) {
   1696    *iter = appendNBSP ? HTMLEditUtils::kNBSP : HTMLEditUtils::kSpace;
   1697    appendNBSP = !appendNBSP;
   1698  }
   1699 
   1700  // If the final one is expected to an NBSP, we can put an NBSP simply.
   1701  if (appendNBSP) {
   1702    *lastChar = HTMLEditUtils::kNBSP;
   1703    return;
   1704  }
   1705 
   1706  // If next char point is end of text node, an ASCII white-space or
   1707  // preformatted linefeed, we need to put an NBSP.
   1708  *lastChar =
   1709      aNextCharPointData.AcrossTextNodeBoundary() ||
   1710              aNextCharPointData.Type() == CharPointType::ASCIIWhiteSpace ||
   1711              aNextCharPointData.Type() == CharPointType::PreformattedLineBreak
   1712          ? HTMLEditUtils::kNBSP
   1713          : HTMLEditUtils::kSpace;
   1714 }
   1715 
   1716 HTMLEditor::NormalizedStringToInsertText
   1717 HTMLEditor::NormalizeWhiteSpacesToInsertText(
   1718    const EditorDOMPoint& aPointToInsert, const nsAString& aStringToInsert,
   1719    NormalizeSurroundingWhiteSpaces aNormalizeSurroundingWhiteSpaces) const {
   1720  MOZ_ASSERT(aPointToInsert.IsSet());
   1721 
   1722  // If white-spaces are preformatted, we don't need to normalize white-spaces.
   1723  if (EditorUtils::IsWhiteSpacePreformatted(
   1724          *aPointToInsert.ContainerAs<nsIContent>())) {
   1725    return NormalizedStringToInsertText(aStringToInsert, aPointToInsert);
   1726  }
   1727 
   1728  Text* const textNode = aPointToInsert.GetContainerAs<Text>();
   1729  const CharacterDataBuffer* const characterDataBuffer =
   1730      textNode ? &textNode->DataBuffer() : nullptr;
   1731  const bool isNewLineCollapsible = !EditorUtils::IsNewLinePreformatted(
   1732      *aPointToInsert.ContainerAs<nsIContent>());
   1733 
   1734  // We don't want to make invisible things visible with this normalization.
   1735  // Therefore, we need to know whether there are invisible leading and/or
   1736  // trailing white-spaces in the `Text`.
   1737 
   1738  // Then, compute visible white-space length before/after the insertion point.
   1739  // Note that these lengths may contain invisible white-spaces.
   1740  const uint32_t precedingWhiteSpaceLength = [&]() {
   1741    if (!textNode || !aNormalizeSurroundingWhiteSpaces ||
   1742        aPointToInsert.IsStartOfContainer()) {
   1743      return 0u;
   1744    }
   1745    const auto nonWhiteSpaceOffset =
   1746        HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(
   1747            *textNode, aPointToInsert.Offset(),
   1748            {HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible});
   1749    const uint32_t firstWhiteSpaceOffset =
   1750        nonWhiteSpaceOffset ? *nonWhiteSpaceOffset + 1u : 0u;
   1751    return aPointToInsert.Offset() - firstWhiteSpaceOffset;
   1752  }();
   1753  const uint32_t followingWhiteSpaceLength = [&]() {
   1754    if (!textNode || !aNormalizeSurroundingWhiteSpaces ||
   1755        aPointToInsert.IsEndOfContainer()) {
   1756      return 0u;
   1757    }
   1758    MOZ_ASSERT(characterDataBuffer);
   1759    const auto nonWhiteSpaceOffset =
   1760        HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset(
   1761            *textNode, aPointToInsert.Offset(),
   1762            {HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible});
   1763    MOZ_ASSERT(nonWhiteSpaceOffset.valueOr(characterDataBuffer->GetLength()) >=
   1764               aPointToInsert.Offset());
   1765    return nonWhiteSpaceOffset.valueOr(characterDataBuffer->GetLength()) -
   1766           aPointToInsert.Offset();
   1767  }();
   1768 
   1769  // Now, we can know invisible white-space length in precedingWhiteSpaceLength
   1770  // and followingWhiteSpaceLength.
   1771  const uint32_t precedingInvisibleWhiteSpaceCount =
   1772      textNode
   1773          ? HTMLEditUtils::GetInvisibleWhiteSpaceCount(
   1774                *textNode, aPointToInsert.Offset() - precedingWhiteSpaceLength,
   1775                precedingWhiteSpaceLength)
   1776          : 0u;
   1777  MOZ_ASSERT(precedingWhiteSpaceLength >= precedingInvisibleWhiteSpaceCount);
   1778  const uint32_t newPrecedingWhiteSpaceLength =
   1779      precedingWhiteSpaceLength - precedingInvisibleWhiteSpaceCount;
   1780  const uint32_t followingInvisibleSpaceCount =
   1781      textNode
   1782          ? HTMLEditUtils::GetInvisibleWhiteSpaceCount(
   1783                *textNode, aPointToInsert.Offset(), followingWhiteSpaceLength)
   1784          : 0u;
   1785  MOZ_ASSERT(followingWhiteSpaceLength >= followingInvisibleSpaceCount);
   1786  const uint32_t newFollowingWhiteSpaceLength =
   1787      followingWhiteSpaceLength - followingInvisibleSpaceCount;
   1788 
   1789  const nsAutoString stringToInsertWithSurroundingSpaces =
   1790      [&]() -> nsAutoString {
   1791    if (!newPrecedingWhiteSpaceLength && !newFollowingWhiteSpaceLength) {
   1792      return nsAutoString(aStringToInsert);
   1793    }
   1794    nsAutoString str;
   1795    str.SetCapacity(aStringToInsert.Length() + newPrecedingWhiteSpaceLength +
   1796                    newFollowingWhiteSpaceLength);
   1797    for ([[maybe_unused]] auto unused :
   1798         IntegerRange(newPrecedingWhiteSpaceLength)) {
   1799      str.Append(' ');
   1800    }
   1801    str.Append(aStringToInsert);
   1802    for ([[maybe_unused]] auto unused :
   1803         IntegerRange(newFollowingWhiteSpaceLength)) {
   1804      str.Append(' ');
   1805    }
   1806    return str;
   1807  }();
   1808 
   1809  const uint32_t insertionOffsetInTextNode =
   1810      aPointToInsert.IsInTextNode() ? aPointToInsert.Offset() : 0u;
   1811  NormalizedStringToInsertText result(
   1812      stringToInsertWithSurroundingSpaces, insertionOffsetInTextNode,
   1813      insertionOffsetInTextNode - precedingWhiteSpaceLength,  // replace start
   1814      precedingWhiteSpaceLength + followingWhiteSpaceLength,  // replace length
   1815      newPrecedingWhiteSpaceLength, newFollowingWhiteSpaceLength);
   1816 
   1817  // Now, normalize the inserting string.
   1818  // Note that if the caller does not want to normalize the following
   1819  // white-spaces, we always need to guarantee that neither the first character
   1820  // nor the last character of the insertion string is not collapsible, i.e., if
   1821  // each one is a collapsible white-space, we need to replace them an NBSP to
   1822  // keep the visibility of the collapsible white-spaces.  Therefore, if
   1823  // aNormalizeSurroundingWhiteSpaces is "No", we need to treat the insertion
   1824  // string is the only characters in the `Text`.
   1825  HTMLEditor::NormalizeAllWhiteSpaceSequences(
   1826      result.mNormalizedString,
   1827      CharPointData::InSameTextNode(
   1828          !characterDataBuffer || !result.mReplaceStartOffset ||
   1829                  !aNormalizeSurroundingWhiteSpaces
   1830              ? CharPointType::TextEnd
   1831              : (characterDataBuffer->CharAt(result.mReplaceStartOffset - 1u) ==
   1832                         HTMLEditUtils::kNewLine
   1833                     ? CharPointType::PreformattedLineBreak
   1834                     : CharPointType::VisibleChar)),
   1835      CharPointData::InSameTextNode(
   1836          !characterDataBuffer ||
   1837                  result.mReplaceEndOffset >=
   1838                      characterDataBuffer->GetLength() ||
   1839                  !aNormalizeSurroundingWhiteSpaces
   1840              ? CharPointType::TextEnd
   1841              : (characterDataBuffer->CharAt(result.mReplaceEndOffset) ==
   1842                         HTMLEditUtils::kNewLine
   1843                     ? CharPointType::PreformattedLineBreak
   1844                     : CharPointType::VisibleChar)),
   1845      isNewLineCollapsible ? Linefeed::Collapsible : Linefeed::Preformatted);
   1846  return result;
   1847 }
   1848 
   1849 HTMLEditor::ReplaceWhiteSpacesData HTMLEditor::GetNormalizedStringAt(
   1850    const EditorDOMPointInText& aPoint) const {
   1851  MOZ_ASSERT(aPoint.IsSet());
   1852 
   1853  // If white-spaces are preformatted, we don't need to normalize white-spaces.
   1854  if (EditorUtils::IsWhiteSpacePreformatted(*aPoint.ContainerAs<Text>())) {
   1855    return ReplaceWhiteSpacesData();
   1856  }
   1857 
   1858  const Text& textNode = *aPoint.ContainerAs<Text>();
   1859  const CharacterDataBuffer& characterDataBuffer = textNode.DataBuffer();
   1860 
   1861  // We don't want to make invisible things visible with this normalization.
   1862  // Therefore, we need to know whether there are invisible leading and/or
   1863  // trailing white-spaces in the `Text`.
   1864 
   1865  // Then, compute visible white-space length before/after the point.
   1866  // Note that these lengths may contain invisible white-spaces.
   1867  const uint32_t precedingWhiteSpaceLength = [&]() {
   1868    if (aPoint.IsStartOfContainer()) {
   1869      return 0u;
   1870    }
   1871    const auto nonWhiteSpaceOffset =
   1872        HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(
   1873            textNode, aPoint.Offset(),
   1874            {HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible});
   1875    const uint32_t firstWhiteSpaceOffset =
   1876        nonWhiteSpaceOffset ? *nonWhiteSpaceOffset + 1u : 0u;
   1877    return aPoint.Offset() - firstWhiteSpaceOffset;
   1878  }();
   1879  const uint32_t followingWhiteSpaceLength = [&]() {
   1880    if (aPoint.IsEndOfContainer()) {
   1881      return 0u;
   1882    }
   1883    const auto nonWhiteSpaceOffset =
   1884        HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset(
   1885            textNode, aPoint.Offset(),
   1886            {HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible});
   1887    MOZ_ASSERT(nonWhiteSpaceOffset.valueOr(characterDataBuffer.GetLength()) >=
   1888               aPoint.Offset());
   1889    return nonWhiteSpaceOffset.valueOr(characterDataBuffer.GetLength()) -
   1890           aPoint.Offset();
   1891  }();
   1892  if (!precedingWhiteSpaceLength && !followingWhiteSpaceLength) {
   1893    return ReplaceWhiteSpacesData();
   1894  }
   1895 
   1896  // Now, we can know invisible white-space length in precedingWhiteSpaceLength
   1897  // and followingWhiteSpaceLength.
   1898  const uint32_t precedingInvisibleWhiteSpaceCount =
   1899      HTMLEditUtils::GetInvisibleWhiteSpaceCount(
   1900          textNode, aPoint.Offset() - precedingWhiteSpaceLength,
   1901          precedingWhiteSpaceLength);
   1902  MOZ_ASSERT(precedingWhiteSpaceLength >= precedingInvisibleWhiteSpaceCount);
   1903  const uint32_t newPrecedingWhiteSpaceLength =
   1904      precedingWhiteSpaceLength - precedingInvisibleWhiteSpaceCount;
   1905  const uint32_t followingInvisibleSpaceCount =
   1906      HTMLEditUtils::GetInvisibleWhiteSpaceCount(textNode, aPoint.Offset(),
   1907                                                 followingWhiteSpaceLength);
   1908  MOZ_ASSERT(followingWhiteSpaceLength >= followingInvisibleSpaceCount);
   1909  const uint32_t newFollowingWhiteSpaceLength =
   1910      followingWhiteSpaceLength - followingInvisibleSpaceCount;
   1911 
   1912  nsAutoString stringToInsertWithSurroundingSpaces;
   1913  if (newPrecedingWhiteSpaceLength || newFollowingWhiteSpaceLength) {
   1914    stringToInsertWithSurroundingSpaces.SetLength(newPrecedingWhiteSpaceLength +
   1915                                                  newFollowingWhiteSpaceLength);
   1916    for (auto index : IntegerRange(newPrecedingWhiteSpaceLength +
   1917                                   newFollowingWhiteSpaceLength)) {
   1918      stringToInsertWithSurroundingSpaces.SetCharAt(' ', index);
   1919    }
   1920  }
   1921 
   1922  ReplaceWhiteSpacesData result(
   1923      std::move(stringToInsertWithSurroundingSpaces),
   1924      aPoint.Offset() - precedingWhiteSpaceLength,            // replace start
   1925      precedingWhiteSpaceLength + followingWhiteSpaceLength,  // replace length
   1926      // aPoint.Offset() after replacing the white-spaces
   1927      aPoint.Offset() - precedingWhiteSpaceLength +
   1928          newPrecedingWhiteSpaceLength);
   1929  if (!result.mNormalizedString.IsEmpty()) {
   1930    HTMLEditor::NormalizeAllWhiteSpaceSequences(
   1931        result.mNormalizedString,
   1932        CharPointData::InSameTextNode(
   1933            !result.mReplaceStartOffset
   1934                ? CharPointType::TextEnd
   1935                : (characterDataBuffer.CharAt(result.mReplaceStartOffset -
   1936                                              1u) == HTMLEditUtils::kNewLine
   1937                       ? CharPointType::PreformattedLineBreak
   1938                       : CharPointType::VisibleChar)),
   1939        CharPointData::InSameTextNode(
   1940            result.mReplaceEndOffset >= characterDataBuffer.GetLength()
   1941                ? CharPointType::TextEnd
   1942                : (characterDataBuffer.CharAt(result.mReplaceEndOffset) ==
   1943                           HTMLEditUtils::kNewLine
   1944                       ? CharPointType::PreformattedLineBreak
   1945                       : CharPointType::VisibleChar)),
   1946        EditorUtils::IsNewLinePreformatted(textNode) ? Linefeed::Collapsible
   1947                                                     : Linefeed::Preformatted);
   1948  }
   1949  return result;
   1950 }
   1951 
   1952 HTMLEditor::ReplaceWhiteSpacesData
   1953 HTMLEditor::GetFollowingNormalizedStringToSplitAt(
   1954    const EditorDOMPointInText& aPointToSplit) const {
   1955  MOZ_ASSERT(aPointToSplit.IsSet());
   1956 
   1957  if (EditorUtils::IsWhiteSpacePreformatted(
   1958          *aPointToSplit.ContainerAs<Text>()) ||
   1959      aPointToSplit.IsEndOfContainer()) {
   1960    return ReplaceWhiteSpacesData();
   1961  }
   1962  const bool isNewLineCollapsible =
   1963      !EditorUtils::IsNewLinePreformatted(*aPointToSplit.ContainerAs<Text>());
   1964  const auto IsPreformattedLineBreak = [&](char16_t aChar) {
   1965    return !isNewLineCollapsible && aChar == HTMLEditUtils::kNewLine;
   1966  };
   1967  const auto IsCollapsibleChar = [&](char16_t aChar) {
   1968    return !IsPreformattedLineBreak(aChar) && nsCRT::IsAsciiSpace(aChar);
   1969  };
   1970  const auto IsCollapsibleCharOrNBSP = [&](char16_t aChar) {
   1971    return aChar == HTMLEditUtils::kNBSP || IsCollapsibleChar(aChar);
   1972  };
   1973  const char16_t followingChar = aPointToSplit.Char();
   1974  if (!IsCollapsibleCharOrNBSP(followingChar)) {
   1975    return ReplaceWhiteSpacesData();
   1976  }
   1977  const uint32_t followingWhiteSpaceLength = [&]() {
   1978    const auto nonWhiteSpaceOffset =
   1979        HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset(
   1980            *aPointToSplit.ContainerAs<Text>(), aPointToSplit.Offset(),
   1981            {HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible});
   1982    MOZ_ASSERT(nonWhiteSpaceOffset.valueOr(
   1983                   aPointToSplit.ContainerAs<Text>()->TextDataLength()) >=
   1984               aPointToSplit.Offset());
   1985    return nonWhiteSpaceOffset.valueOr(
   1986               aPointToSplit.ContainerAs<Text>()->TextDataLength()) -
   1987           aPointToSplit.Offset();
   1988  }();
   1989  MOZ_ASSERT(followingWhiteSpaceLength);
   1990  if (NS_WARN_IF(!followingWhiteSpaceLength) ||
   1991      (followingWhiteSpaceLength == 1u &&
   1992       followingChar == HTMLEditUtils::kNBSP)) {
   1993    return ReplaceWhiteSpacesData();
   1994  }
   1995 
   1996  const uint32_t followingInvisibleSpaceCount =
   1997      HTMLEditUtils::GetInvisibleWhiteSpaceCount(
   1998          *aPointToSplit.ContainerAs<Text>(), aPointToSplit.Offset(),
   1999          followingWhiteSpaceLength);
   2000  MOZ_ASSERT(followingWhiteSpaceLength >= followingInvisibleSpaceCount);
   2001  const uint32_t newFollowingWhiteSpaceLength =
   2002      followingWhiteSpaceLength - followingInvisibleSpaceCount;
   2003  nsAutoString followingWhiteSpaces;
   2004  if (newFollowingWhiteSpaceLength) {
   2005    followingWhiteSpaces.SetLength(newFollowingWhiteSpaceLength);
   2006    for (const auto offset : IntegerRange(newFollowingWhiteSpaceLength)) {
   2007      followingWhiteSpaces.SetCharAt(' ', offset);
   2008    }
   2009  }
   2010  ReplaceWhiteSpacesData result(std::move(followingWhiteSpaces),
   2011                                aPointToSplit.Offset(),
   2012                                followingWhiteSpaceLength);
   2013  if (!result.mNormalizedString.IsEmpty()) {
   2014    const CharacterDataBuffer& characterDataBuffer =
   2015        aPointToSplit.ContainerAs<Text>()->DataBuffer();
   2016    HTMLEditor::NormalizeAllWhiteSpaceSequences(
   2017        result.mNormalizedString,
   2018        CharPointData::InSameTextNode(CharPointType::TextEnd),
   2019        CharPointData::InSameTextNode(
   2020            result.mReplaceEndOffset >= characterDataBuffer.GetLength()
   2021                ? CharPointType::TextEnd
   2022                : (characterDataBuffer.CharAt(result.mReplaceEndOffset) ==
   2023                           HTMLEditUtils::kNewLine
   2024                       ? CharPointType::PreformattedLineBreak
   2025                       : CharPointType::VisibleChar)),
   2026        isNewLineCollapsible ? Linefeed::Collapsible : Linefeed::Preformatted);
   2027  }
   2028  return result;
   2029 }
   2030 
   2031 HTMLEditor::ReplaceWhiteSpacesData
   2032 HTMLEditor::GetPrecedingNormalizedStringToSplitAt(
   2033    const EditorDOMPointInText& aPointToSplit) const {
   2034  MOZ_ASSERT(aPointToSplit.IsSet());
   2035 
   2036  if (EditorUtils::IsWhiteSpacePreformatted(
   2037          *aPointToSplit.ContainerAs<Text>()) ||
   2038      aPointToSplit.IsStartOfContainer()) {
   2039    return ReplaceWhiteSpacesData();
   2040  }
   2041  const bool isNewLineCollapsible =
   2042      !EditorUtils::IsNewLinePreformatted(*aPointToSplit.ContainerAs<Text>());
   2043  const auto IsPreformattedLineBreak = [&](char16_t aChar) {
   2044    return !isNewLineCollapsible && aChar == HTMLEditUtils::kNewLine;
   2045  };
   2046  const auto IsCollapsibleChar = [&](char16_t aChar) {
   2047    return !IsPreformattedLineBreak(aChar) && nsCRT::IsAsciiSpace(aChar);
   2048  };
   2049  const auto IsCollapsibleCharOrNBSP = [&](char16_t aChar) {
   2050    return aChar == HTMLEditUtils::kNBSP || IsCollapsibleChar(aChar);
   2051  };
   2052  const char16_t precedingChar = aPointToSplit.PreviousChar();
   2053  if (!IsCollapsibleCharOrNBSP(precedingChar)) {
   2054    return ReplaceWhiteSpacesData();
   2055  }
   2056  const uint32_t precedingWhiteSpaceLength = [&]() {
   2057    const auto nonWhiteSpaceOffset =
   2058        HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(
   2059            *aPointToSplit.ContainerAs<Text>(), aPointToSplit.Offset(),
   2060            {HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible});
   2061    const uint32_t firstWhiteSpaceOffset =
   2062        nonWhiteSpaceOffset ? *nonWhiteSpaceOffset + 1u : 0u;
   2063    return aPointToSplit.Offset() - firstWhiteSpaceOffset;
   2064  }();
   2065  MOZ_ASSERT(precedingWhiteSpaceLength);
   2066  if (NS_WARN_IF(!precedingWhiteSpaceLength) ||
   2067      (precedingWhiteSpaceLength == 1u &&
   2068       precedingChar == HTMLEditUtils::kNBSP)) {
   2069    return ReplaceWhiteSpacesData();
   2070  }
   2071 
   2072  const uint32_t precedingInvisibleWhiteSpaceCount =
   2073      HTMLEditUtils::GetInvisibleWhiteSpaceCount(
   2074          *aPointToSplit.ContainerAs<Text>(),
   2075          aPointToSplit.Offset() - precedingWhiteSpaceLength,
   2076          precedingWhiteSpaceLength);
   2077  MOZ_ASSERT(precedingWhiteSpaceLength >= precedingInvisibleWhiteSpaceCount);
   2078  const uint32_t newPrecedingWhiteSpaceLength =
   2079      precedingWhiteSpaceLength - precedingInvisibleWhiteSpaceCount;
   2080  nsAutoString precedingWhiteSpaces;
   2081  if (newPrecedingWhiteSpaceLength) {
   2082    precedingWhiteSpaces.SetLength(newPrecedingWhiteSpaceLength);
   2083    for (const auto offset : IntegerRange(newPrecedingWhiteSpaceLength)) {
   2084      precedingWhiteSpaces.SetCharAt(' ', offset);
   2085    }
   2086  }
   2087  ReplaceWhiteSpacesData result(
   2088      std::move(precedingWhiteSpaces),
   2089      aPointToSplit.Offset() - precedingWhiteSpaceLength,
   2090      precedingWhiteSpaceLength);
   2091  if (!result.mNormalizedString.IsEmpty()) {
   2092    const CharacterDataBuffer& characterDataBuffer =
   2093        aPointToSplit.ContainerAs<Text>()->DataBuffer();
   2094    HTMLEditor::NormalizeAllWhiteSpaceSequences(
   2095        result.mNormalizedString,
   2096        CharPointData::InSameTextNode(
   2097            !result.mReplaceStartOffset
   2098                ? CharPointType::TextEnd
   2099                : (characterDataBuffer.CharAt(result.mReplaceStartOffset -
   2100                                              1u) == HTMLEditUtils::kNewLine
   2101                       ? CharPointType::PreformattedLineBreak
   2102                       : CharPointType::VisibleChar)),
   2103        CharPointData::InSameTextNode(CharPointType::TextEnd),
   2104        isNewLineCollapsible ? Linefeed::Collapsible : Linefeed::Preformatted);
   2105  }
   2106  return result;
   2107 }
   2108 
   2109 HTMLEditor::ReplaceWhiteSpacesData
   2110 HTMLEditor::GetSurroundingNormalizedStringToDelete(const Text& aTextNode,
   2111                                                   uint32_t aOffset,
   2112                                                   uint32_t aLength) const {
   2113  MOZ_ASSERT(aOffset <= aTextNode.TextDataLength());
   2114  MOZ_ASSERT(aOffset + aLength <= aTextNode.TextDataLength());
   2115 
   2116  if (EditorUtils::IsWhiteSpacePreformatted(aTextNode) || !aLength ||
   2117      (!aOffset && aLength >= aTextNode.TextDataLength())) {
   2118    return ReplaceWhiteSpacesData();
   2119  }
   2120  const bool isNewLineCollapsible =
   2121      !EditorUtils::IsNewLinePreformatted(aTextNode);
   2122  const auto IsPreformattedLineBreak = [&](char16_t aChar) {
   2123    return !isNewLineCollapsible && aChar == HTMLEditUtils::kNewLine;
   2124  };
   2125  const auto IsCollapsibleChar = [&](char16_t aChar) {
   2126    return !IsPreformattedLineBreak(aChar) && nsCRT::IsAsciiSpace(aChar);
   2127  };
   2128  const auto IsCollapsibleCharOrNBSP = [&](char16_t aChar) {
   2129    return aChar == HTMLEditUtils::kNBSP || IsCollapsibleChar(aChar);
   2130  };
   2131  const CharacterDataBuffer& characterDataBuffer = aTextNode.DataBuffer();
   2132  const char16_t precedingChar = aOffset
   2133                                     ? characterDataBuffer.CharAt(aOffset - 1u)
   2134                                     : static_cast<char16_t>(0);
   2135  const char16_t followingChar =
   2136      aOffset + aLength < characterDataBuffer.GetLength()
   2137          ? characterDataBuffer.CharAt(aOffset + aLength)
   2138          : static_cast<char16_t>(0);
   2139  // If there is no surrounding white-spaces, we need to do nothing here.
   2140  if (!IsCollapsibleCharOrNBSP(precedingChar) &&
   2141      !IsCollapsibleCharOrNBSP(followingChar)) {
   2142    return ReplaceWhiteSpacesData();
   2143  }
   2144  const uint32_t precedingWhiteSpaceLength = [&]() {
   2145    if (!IsCollapsibleCharOrNBSP(precedingChar)) {
   2146      return 0u;
   2147    }
   2148    const auto nonWhiteSpaceOffset =
   2149        HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(
   2150            aTextNode, aOffset,
   2151            {HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible});
   2152    const uint32_t firstWhiteSpaceOffset =
   2153        nonWhiteSpaceOffset ? *nonWhiteSpaceOffset + 1u : 0u;
   2154    return aOffset - firstWhiteSpaceOffset;
   2155  }();
   2156  const uint32_t followingWhiteSpaceLength = [&]() {
   2157    if (!IsCollapsibleCharOrNBSP(followingChar)) {
   2158      return 0u;
   2159    }
   2160    const auto nonWhiteSpaceOffset =
   2161        HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset(
   2162            aTextNode, aOffset + aLength,
   2163            {HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible});
   2164    MOZ_ASSERT(nonWhiteSpaceOffset.valueOr(characterDataBuffer.GetLength()) >=
   2165               aOffset + aLength);
   2166    return nonWhiteSpaceOffset.valueOr(characterDataBuffer.GetLength()) -
   2167           (aOffset + aLength);
   2168  }();
   2169  if (NS_WARN_IF(!precedingWhiteSpaceLength && !followingWhiteSpaceLength)) {
   2170    return ReplaceWhiteSpacesData();
   2171  }
   2172  const uint32_t precedingInvisibleWhiteSpaceCount =
   2173      HTMLEditUtils::GetInvisibleWhiteSpaceCount(
   2174          aTextNode, aOffset - precedingWhiteSpaceLength,
   2175          precedingWhiteSpaceLength);
   2176  MOZ_ASSERT(precedingWhiteSpaceLength >= precedingInvisibleWhiteSpaceCount);
   2177  const uint32_t followingInvisibleSpaceCount =
   2178      HTMLEditUtils::GetInvisibleWhiteSpaceCount(aTextNode, aOffset + aLength,
   2179                                                 followingWhiteSpaceLength);
   2180  MOZ_ASSERT(followingWhiteSpaceLength >= followingInvisibleSpaceCount);
   2181 
   2182  // Let's try to return early if there is only one white-space around the
   2183  // deleting range to avoid to run the expensive path.
   2184  if (precedingWhiteSpaceLength == 1u && !precedingInvisibleWhiteSpaceCount &&
   2185      !followingWhiteSpaceLength) {
   2186    // If there is only one ASCII space and it'll be followed by a
   2187    // non-collapsible character except preformatted linebreak after deletion,
   2188    // we don't need to normalize the preceding white-space.
   2189    if (precedingChar == HTMLEditUtils::kSpace && followingChar &&
   2190        !IsPreformattedLineBreak(followingChar)) {
   2191      return ReplaceWhiteSpacesData();
   2192    }
   2193    // If there is only one NBSP and it'll be the last character or will be
   2194    // followed by a collapsible white-space, we don't need to normalize the
   2195    // preceding white-space.
   2196    if (precedingChar == HTMLEditUtils::kNBSP &&
   2197        (!followingChar || IsPreformattedLineBreak(followingChar))) {
   2198      return ReplaceWhiteSpacesData();
   2199    }
   2200  }
   2201  if (followingWhiteSpaceLength == 1u && !followingInvisibleSpaceCount &&
   2202      !precedingWhiteSpaceLength) {
   2203    // If there is only one ASCII space and it'll follow by a non-collapsible
   2204    // character after deletion, we don't need to normalize the following
   2205    // white-space.
   2206    if (followingChar == HTMLEditUtils::kSpace && precedingChar &&
   2207        !IsPreformattedLineBreak(precedingChar)) {
   2208      return ReplaceWhiteSpacesData();
   2209    }
   2210    // If there is only one NBSP and it'll be the first character or will
   2211    // follow a preformatted line break, we don't need to normalize the
   2212    // following white-space.
   2213    if (followingChar == HTMLEditUtils::kNBSP &&
   2214        (!precedingChar || IsPreformattedLineBreak(precedingChar))) {
   2215      return ReplaceWhiteSpacesData();
   2216    }
   2217  }
   2218 
   2219  const uint32_t newPrecedingWhiteSpaceLength =
   2220      precedingWhiteSpaceLength - precedingInvisibleWhiteSpaceCount;
   2221  const uint32_t newFollowingWhiteSpaceLength =
   2222      followingWhiteSpaceLength - followingInvisibleSpaceCount;
   2223  nsAutoString surroundingWhiteSpaces;
   2224  if (newPrecedingWhiteSpaceLength || newFollowingWhiteSpaceLength) {
   2225    surroundingWhiteSpaces.SetLength(newPrecedingWhiteSpaceLength +
   2226                                     newFollowingWhiteSpaceLength);
   2227    for (const auto offset : IntegerRange(newPrecedingWhiteSpaceLength +
   2228                                          newFollowingWhiteSpaceLength)) {
   2229      surroundingWhiteSpaces.SetCharAt(' ', offset);
   2230    }
   2231  }
   2232  ReplaceWhiteSpacesData result(
   2233      std::move(surroundingWhiteSpaces), aOffset - precedingWhiteSpaceLength,
   2234      precedingWhiteSpaceLength + aLength + followingWhiteSpaceLength,
   2235      aOffset - precedingInvisibleWhiteSpaceCount);
   2236  if (!result.mNormalizedString.IsEmpty()) {
   2237    HTMLEditor::NormalizeAllWhiteSpaceSequences(
   2238        result.mNormalizedString,
   2239        CharPointData::InSameTextNode(
   2240            !result.mReplaceStartOffset
   2241                ? CharPointType::TextEnd
   2242                : (characterDataBuffer.CharAt(result.mReplaceStartOffset -
   2243                                              1u) == HTMLEditUtils::kNewLine
   2244                       ? CharPointType::PreformattedLineBreak
   2245                       : CharPointType::VisibleChar)),
   2246        CharPointData::InSameTextNode(
   2247            result.mReplaceEndOffset >= characterDataBuffer.GetLength()
   2248                ? CharPointType::TextEnd
   2249                : (characterDataBuffer.CharAt(result.mReplaceEndOffset) ==
   2250                           HTMLEditUtils::kNewLine
   2251                       ? CharPointType::PreformattedLineBreak
   2252                       : CharPointType::VisibleChar)),
   2253        isNewLineCollapsible ? Linefeed::Collapsible : Linefeed::Preformatted);
   2254  }
   2255  return result;
   2256 }
   2257 
   2258 void HTMLEditor::ExtendRangeToDeleteWithNormalizingWhiteSpaces(
   2259    EditorDOMPointInText& aStartToDelete, EditorDOMPointInText& aEndToDelete,
   2260    nsString& aNormalizedWhiteSpacesInStartNode,
   2261    nsString& aNormalizedWhiteSpacesInEndNode) const {
   2262  MOZ_ASSERT(aStartToDelete.IsSetAndValid());
   2263  MOZ_ASSERT(aEndToDelete.IsSetAndValid());
   2264  MOZ_ASSERT(aStartToDelete.EqualsOrIsBefore(aEndToDelete));
   2265  MOZ_ASSERT(aNormalizedWhiteSpacesInStartNode.IsEmpty());
   2266  MOZ_ASSERT(aNormalizedWhiteSpacesInEndNode.IsEmpty());
   2267 
   2268  // First, check whether there is surrounding white-spaces or not, and if there
   2269  // are, check whether they are collapsible or not.  Note that we shouldn't
   2270  // touch white-spaces in different text nodes for performance, but we need
   2271  // adjacent text node's first or last character information in some cases.
   2272  const auto precedingCharPoint =
   2273      WSRunScanner::GetPreviousCharPoint<EditorDOMPointInText>(
   2274          {WSRunScanner::Option::OnlyEditableNodes}, aStartToDelete);
   2275  const auto followingCharPoint =
   2276      WSRunScanner::GetInclusiveNextCharPoint<EditorDOMPointInText>(
   2277          {WSRunScanner::Option::OnlyEditableNodes}, aEndToDelete);
   2278  // Blink-compat: Normalize white-spaces in first node only when not removing
   2279  //               its last character or no text nodes follow the first node.
   2280  //               If removing last character of first node and there are
   2281  //               following text nodes, white-spaces in following text node are
   2282  //               normalized instead.
   2283  const bool removingLastCharOfStartNode =
   2284      aStartToDelete.ContainerAs<Text>() != aEndToDelete.ContainerAs<Text>() ||
   2285      (aEndToDelete.IsEndOfContainer() && followingCharPoint.IsSet());
   2286  const bool maybeNormalizePrecedingWhiteSpaces =
   2287      !removingLastCharOfStartNode && precedingCharPoint.IsSet() &&
   2288      !precedingCharPoint.IsEndOfContainer() &&
   2289      precedingCharPoint.ContainerAs<Text>() ==
   2290          aStartToDelete.ContainerAs<Text>() &&
   2291      precedingCharPoint.IsCharCollapsibleASCIISpaceOrNBSP();
   2292  const bool maybeNormalizeFollowingWhiteSpaces =
   2293      followingCharPoint.IsSet() && !followingCharPoint.IsEndOfContainer() &&
   2294      (followingCharPoint.ContainerAs<Text>() ==
   2295           aEndToDelete.ContainerAs<Text>() ||
   2296       removingLastCharOfStartNode) &&
   2297      followingCharPoint.IsCharCollapsibleASCIISpaceOrNBSP();
   2298 
   2299  if (!maybeNormalizePrecedingWhiteSpaces &&
   2300      !maybeNormalizeFollowingWhiteSpaces) {
   2301    return;  // There are no white-spaces.
   2302  }
   2303 
   2304  // Next, consider the range to normalize.
   2305  EditorDOMPointInText startToNormalize, endToNormalize;
   2306  if (maybeNormalizePrecedingWhiteSpaces) {
   2307    Maybe<uint32_t> previousCharOffsetOfWhiteSpaces =
   2308        HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(
   2309            precedingCharPoint, {WalkTextOption::TreatNBSPsCollapsible});
   2310    startToNormalize.Set(precedingCharPoint.ContainerAs<Text>(),
   2311                         previousCharOffsetOfWhiteSpaces.isSome()
   2312                             ? previousCharOffsetOfWhiteSpaces.value() + 1
   2313                             : 0);
   2314    MOZ_ASSERT(!startToNormalize.IsEndOfContainer());
   2315  }
   2316  if (maybeNormalizeFollowingWhiteSpaces) {
   2317    Maybe<uint32_t> nextCharOffsetOfWhiteSpaces =
   2318        HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset(
   2319            followingCharPoint, {WalkTextOption::TreatNBSPsCollapsible});
   2320    if (nextCharOffsetOfWhiteSpaces.isSome()) {
   2321      endToNormalize.Set(followingCharPoint.ContainerAs<Text>(),
   2322                         nextCharOffsetOfWhiteSpaces.value());
   2323    } else {
   2324      endToNormalize.SetToEndOf(followingCharPoint.ContainerAs<Text>());
   2325    }
   2326    MOZ_ASSERT(!endToNormalize.IsStartOfContainer());
   2327  }
   2328 
   2329  // Next, retrieve surrounding information of white-space sequence.
   2330  // If we're removing first text node's last character, we need to
   2331  // normalize white-spaces starts from another text node.  In this case,
   2332  // we need to lie for avoiding assertion in GenerateWhiteSpaceSequence().
   2333  CharPointData previousCharPointData =
   2334      removingLastCharOfStartNode
   2335          ? CharPointData::InDifferentTextNode(CharPointType::TextEnd)
   2336          : GetPreviousCharPointDataForNormalizingWhiteSpaces(
   2337                startToNormalize.IsSet() ? startToNormalize : aStartToDelete);
   2338  CharPointData nextCharPointData =
   2339      GetInclusiveNextCharPointDataForNormalizingWhiteSpaces(
   2340          endToNormalize.IsSet() ? endToNormalize : aEndToDelete);
   2341 
   2342  // Next, compute number of white-spaces in start/end node.
   2343  uint32_t lengthInStartNode = 0, lengthInEndNode = 0;
   2344  if (startToNormalize.IsSet()) {
   2345    MOZ_ASSERT(startToNormalize.ContainerAs<Text>() ==
   2346               aStartToDelete.ContainerAs<Text>());
   2347    lengthInStartNode = aStartToDelete.Offset() - startToNormalize.Offset();
   2348    MOZ_ASSERT(lengthInStartNode);
   2349  }
   2350  if (endToNormalize.IsSet()) {
   2351    lengthInEndNode =
   2352        endToNormalize.ContainerAs<Text>() == aEndToDelete.ContainerAs<Text>()
   2353            ? endToNormalize.Offset() - aEndToDelete.Offset()
   2354            : endToNormalize.Offset();
   2355    MOZ_ASSERT(lengthInEndNode);
   2356    // If we normalize white-spaces in a text node, we can replace all of them
   2357    // with one ReplaceTextTransaction.
   2358    if (endToNormalize.ContainerAs<Text>() ==
   2359        aStartToDelete.ContainerAs<Text>()) {
   2360      lengthInStartNode += lengthInEndNode;
   2361      lengthInEndNode = 0;
   2362    }
   2363  }
   2364 
   2365  MOZ_ASSERT(lengthInStartNode + lengthInEndNode);
   2366 
   2367  // Next, generate normalized white-spaces.
   2368  if (!lengthInEndNode) {
   2369    HTMLEditor::GenerateWhiteSpaceSequence(
   2370        aNormalizedWhiteSpacesInStartNode, lengthInStartNode,
   2371        previousCharPointData, nextCharPointData);
   2372  } else if (!lengthInStartNode) {
   2373    HTMLEditor::GenerateWhiteSpaceSequence(
   2374        aNormalizedWhiteSpacesInEndNode, lengthInEndNode, previousCharPointData,
   2375        nextCharPointData);
   2376  } else {
   2377    // For making `GenerateWhiteSpaceSequence()` simpler, we should create
   2378    // whole white-space sequence first, then, copy to the out params.
   2379    nsAutoString whiteSpaces;
   2380    HTMLEditor::GenerateWhiteSpaceSequence(
   2381        whiteSpaces, lengthInStartNode + lengthInEndNode, previousCharPointData,
   2382        nextCharPointData);
   2383    aNormalizedWhiteSpacesInStartNode =
   2384        Substring(whiteSpaces, 0, lengthInStartNode);
   2385    aNormalizedWhiteSpacesInEndNode = Substring(whiteSpaces, lengthInStartNode);
   2386    MOZ_ASSERT(aNormalizedWhiteSpacesInEndNode.Length() == lengthInEndNode);
   2387  }
   2388 
   2389  // TODO: Shrink the replacing range and string as far as possible because
   2390  //       this may run a lot, i.e., HTMLEditor creates ReplaceTextTransaction
   2391  //       a lot for normalizing white-spaces.  Then, each transaction shouldn't
   2392  //       have all white-spaces every time because once it's normalized, we
   2393  //       don't need to normalize all of the sequence again, but currently
   2394  //       we do.
   2395 
   2396  // Finally, extend the range.
   2397  if (startToNormalize.IsSet()) {
   2398    aStartToDelete = startToNormalize;
   2399  }
   2400  if (endToNormalize.IsSet()) {
   2401    aEndToDelete = endToNormalize;
   2402  }
   2403 }
   2404 
   2405 Result<CaretPoint, nsresult>
   2406 HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces(
   2407    const EditorDOMPointInText& aStartToDelete,
   2408    const EditorDOMPointInText& aEndToDelete,
   2409    TreatEmptyTextNodes aTreatEmptyTextNodes, DeleteDirection aDeleteDirection,
   2410    const Element& aEditingHost) {
   2411  MOZ_ASSERT(aStartToDelete.IsSetAndValid());
   2412  MOZ_ASSERT(aEndToDelete.IsSetAndValid());
   2413  MOZ_ASSERT(aStartToDelete.EqualsOrIsBefore(aEndToDelete));
   2414 
   2415  // Use nsString for these replacing string because we should avoid to copy
   2416  // the buffer from auto storange to ReplaceTextTransaction.
   2417  nsString normalizedWhiteSpacesInFirstNode, normalizedWhiteSpacesInLastNode;
   2418 
   2419  // First, check whether we need to normalize white-spaces after deleting
   2420  // the given range.
   2421  EditorDOMPointInText startToDelete(aStartToDelete);
   2422  EditorDOMPointInText endToDelete(aEndToDelete);
   2423  ExtendRangeToDeleteWithNormalizingWhiteSpaces(
   2424      startToDelete, endToDelete, normalizedWhiteSpacesInFirstNode,
   2425      normalizedWhiteSpacesInLastNode);
   2426 
   2427  // If extended range is still collapsed, i.e., the caller just wants to
   2428  // normalize white-space sequence, but there is no white-spaces which need to
   2429  // be replaced, we need to do nothing here.
   2430  if (startToDelete == endToDelete) {
   2431    return CaretPoint(aStartToDelete.To<EditorDOMPoint>());
   2432  }
   2433 
   2434  // Note that the container text node of startToDelete may be removed from
   2435  // the tree if it becomes empty.  Therefore, we need to track the point.
   2436  EditorDOMPoint newCaretPosition;
   2437  if (aStartToDelete.ContainerAs<Text>() == aEndToDelete.ContainerAs<Text>()) {
   2438    newCaretPosition = aEndToDelete.To<EditorDOMPoint>();
   2439  } else if (aDeleteDirection == DeleteDirection::Forward) {
   2440    newCaretPosition.SetToEndOf(aStartToDelete.ContainerAs<Text>());
   2441  } else {
   2442    newCaretPosition.Set(aEndToDelete.ContainerAs<Text>(), 0u);
   2443  }
   2444 
   2445  // Then, modify the text nodes in the range.
   2446  while (true) {
   2447    AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(),
   2448                                               &newCaretPosition);
   2449    // Use ReplaceTextTransaction if we need to normalize white-spaces in
   2450    // the first text node.
   2451    if (!normalizedWhiteSpacesInFirstNode.IsEmpty()) {
   2452      EditorDOMPoint trackingEndToDelete(endToDelete.ContainerAs<Text>(),
   2453                                         endToDelete.Offset());
   2454      {
   2455        AutoTrackDOMPoint trackEndToDelete(RangeUpdaterRef(),
   2456                                           &trackingEndToDelete);
   2457        uint32_t lengthToReplaceInFirstTextNode =
   2458            startToDelete.ContainerAs<Text>() ==
   2459                    trackingEndToDelete.ContainerAs<Text>()
   2460                ? trackingEndToDelete.Offset() - startToDelete.Offset()
   2461                : startToDelete.ContainerAs<Text>()->TextLength() -
   2462                      startToDelete.Offset();
   2463        Result<InsertTextResult, nsresult> replaceTextResult =
   2464            ReplaceTextWithTransaction(
   2465                MOZ_KnownLive(*startToDelete.ContainerAs<Text>()),
   2466                startToDelete.Offset(), lengthToReplaceInFirstTextNode,
   2467                normalizedWhiteSpacesInFirstNode);
   2468        if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
   2469          NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
   2470          return replaceTextResult.propagateErr();
   2471        }
   2472        // We'll return computed caret point, newCaretPosition, below.
   2473        replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
   2474        if (startToDelete.ContainerAs<Text>() ==
   2475            trackingEndToDelete.ContainerAs<Text>()) {
   2476          MOZ_ASSERT(normalizedWhiteSpacesInLastNode.IsEmpty());
   2477          break;  // There is no more text which we need to delete.
   2478        }
   2479      }
   2480      MOZ_ASSERT(trackingEndToDelete.IsInTextNode());
   2481      endToDelete.Set(trackingEndToDelete.ContainerAs<Text>(),
   2482                      trackingEndToDelete.Offset());
   2483      // If the remaining range was modified by mutation event listener,
   2484      // we should stop handling the deletion.
   2485      startToDelete =
   2486          EditorDOMPointInText::AtEndOf(*startToDelete.ContainerAs<Text>());
   2487    }
   2488    // Delete ASCII whiteSpaces in the range simpley if there are some text
   2489    // nodes which we don't need to replace their text.
   2490    if (normalizedWhiteSpacesInLastNode.IsEmpty() ||
   2491        startToDelete.ContainerAs<Text>() != endToDelete.ContainerAs<Text>()) {
   2492      // If we need to replace text in the last text node, we should
   2493      // delete text before its previous text node.
   2494      EditorDOMPointInText endToDeleteExceptReplaceRange =
   2495          normalizedWhiteSpacesInLastNode.IsEmpty()
   2496              ? endToDelete
   2497              : EditorDOMPointInText(endToDelete.ContainerAs<Text>(), 0);
   2498      if (startToDelete != endToDeleteExceptReplaceRange) {
   2499        Result<CaretPoint, nsresult> caretPointOrError =
   2500            DeleteTextAndTextNodesWithTransaction(startToDelete,
   2501                                                  endToDeleteExceptReplaceRange,
   2502                                                  aTreatEmptyTextNodes);
   2503        if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   2504          NS_WARNING(
   2505              "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
   2506          return caretPointOrError.propagateErr();
   2507        }
   2508        nsresult rv = caretPointOrError.unwrap().SuggestCaretPointTo(
   2509            *this, {SuggestCaret::OnlyIfHasSuggestion,
   2510                    SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   2511                    SuggestCaret::AndIgnoreTrivialError});
   2512        if (NS_FAILED(rv)) {
   2513          NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
   2514          return Err(rv);
   2515        }
   2516        NS_WARNING_ASSERTION(
   2517            rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   2518            "CaretPoint::SuggestCaretPointTo() failed, but ignored");
   2519        if (normalizedWhiteSpacesInLastNode.IsEmpty()) {
   2520          break;  // There is no more text which we need to delete.
   2521        }
   2522        if (MaybeNodeRemovalsObservedByDevTools() &&
   2523            (NS_WARN_IF(!endToDeleteExceptReplaceRange.IsSetAndValid()) ||
   2524             NS_WARN_IF(!endToDelete.IsSetAndValid()) ||
   2525             NS_WARN_IF(endToDelete.IsStartOfContainer()))) {
   2526          return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2527        }
   2528        // Then, replace the text in the last text node.
   2529        startToDelete = endToDeleteExceptReplaceRange;
   2530      }
   2531    }
   2532 
   2533    // Replace ASCII whiteSpaces in the range and following character in the
   2534    // last text node.
   2535    MOZ_ASSERT(!normalizedWhiteSpacesInLastNode.IsEmpty());
   2536    MOZ_ASSERT(startToDelete.ContainerAs<Text>() ==
   2537               endToDelete.ContainerAs<Text>());
   2538    Result<InsertTextResult, nsresult> replaceTextResult =
   2539        ReplaceTextWithTransaction(
   2540            MOZ_KnownLive(*startToDelete.ContainerAs<Text>()),
   2541            startToDelete.Offset(),
   2542            endToDelete.Offset() - startToDelete.Offset(),
   2543            normalizedWhiteSpacesInLastNode);
   2544    if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
   2545      NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
   2546      return replaceTextResult.propagateErr();
   2547    }
   2548    // We'll return computed caret point, newCaretPosition, below.
   2549    replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
   2550    break;
   2551  }
   2552 
   2553  if (NS_WARN_IF(!newCaretPosition.IsSetAndValid()) ||
   2554      NS_WARN_IF(!newCaretPosition.GetContainer()->IsInComposedDoc())) {
   2555    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2556  }
   2557 
   2558  // Look for leaf node to put caret if we remove some empty inline ancestors
   2559  // at new caret position.
   2560  if (!newCaretPosition.IsInTextNode()) {
   2561    if (const Element* editableBlockElementOrInlineEditingHost =
   2562            HTMLEditUtils::GetInclusiveAncestorElement(
   2563                *newCaretPosition.ContainerAs<nsIContent>(),
   2564                HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost,
   2565                BlockInlineCheck::UseComputedDisplayStyle)) {
   2566      // Try to put caret next to immediately after previous editable leaf.
   2567      nsIContent* previousContent =
   2568          HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   2569              newCaretPosition, {LeafNodeType::LeafNodeOrNonEditableNode},
   2570              BlockInlineCheck::UseComputedDisplayStyle,
   2571              editableBlockElementOrInlineEditingHost);
   2572      if (previousContent &&
   2573          HTMLEditUtils::IsSimplyEditableNode(*previousContent) &&
   2574          !HTMLEditUtils::IsBlockElement(
   2575              *previousContent, BlockInlineCheck::UseComputedDisplayStyle)) {
   2576        newCaretPosition =
   2577            previousContent->IsText() ||
   2578                    HTMLEditUtils::IsContainerNode(*previousContent)
   2579                ? EditorDOMPoint::AtEndOf(*previousContent)
   2580                : EditorDOMPoint::After(*previousContent);
   2581      }
   2582      // But if the point is very first of a block element or immediately after
   2583      // a child block, look for next editable leaf instead.
   2584      else if (nsIContent* nextContent =
   2585                   HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
   2586                       newCaretPosition,
   2587                       {LeafNodeType::LeafNodeOrNonEditableNode},
   2588                       BlockInlineCheck::UseComputedDisplayStyle,
   2589                       editableBlockElementOrInlineEditingHost)) {
   2590        if (HTMLEditUtils::IsSimplyEditableNode(*nextContent) &&
   2591            !HTMLEditUtils::IsBlockElement(
   2592                *nextContent, BlockInlineCheck::UseComputedDisplayStyle)) {
   2593          newCaretPosition =
   2594              nextContent->IsText() ||
   2595                      HTMLEditUtils::IsContainerNode(*nextContent)
   2596                  ? EditorDOMPoint(nextContent, 0)
   2597                  : EditorDOMPoint(nextContent);
   2598        }
   2599      }
   2600    }
   2601  }
   2602 
   2603  // For compatibility with Blink, we should move caret to end of previous
   2604  // text node if it's direct previous sibling of the first text node in the
   2605  // range.
   2606  if (newCaretPosition.IsStartOfContainer() &&
   2607      newCaretPosition.IsInTextNode() &&
   2608      newCaretPosition.GetContainer()->GetPreviousSibling() &&
   2609      newCaretPosition.GetContainer()->GetPreviousSibling()->IsEditable() &&
   2610      newCaretPosition.GetContainer()->GetPreviousSibling()->IsText()) {
   2611    newCaretPosition.SetToEndOf(
   2612        newCaretPosition.GetContainer()->GetPreviousSibling()->AsText());
   2613  }
   2614  MOZ_ASSERT(HTMLEditUtils::IsSimplyEditableNode(
   2615      *newCaretPosition.ContainerAs<nsIContent>()));
   2616 
   2617  {
   2618    AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(),
   2619                                           &newCaretPosition);
   2620    nsresult rv = EnsureNoFollowingUnnecessaryLineBreak(newCaretPosition);
   2621    if (NS_FAILED(rv)) {
   2622      NS_WARNING("HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed");
   2623      return Err(rv);
   2624    }
   2625    if (NS_WARN_IF(!newCaretPosition.IsSet())) {
   2626      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2627    }
   2628  }
   2629 
   2630  if (GetTopLevelEditSubAction() == EditSubAction::eDeleteSelectedContent) {
   2631    AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(),
   2632                                               &newCaretPosition);
   2633    Result<CreateLineBreakResult, nsresult> insertPaddingBRElementOrError =
   2634        InsertPaddingBRElementIfNeeded(
   2635            newCaretPosition,
   2636            aEditingHost.IsContentEditablePlainTextOnly() ? nsIEditor::eNoStrip
   2637                                                          : nsIEditor::eStrip,
   2638            aEditingHost);
   2639    if (MOZ_UNLIKELY(insertPaddingBRElementOrError.isErr())) {
   2640      NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed");
   2641      return insertPaddingBRElementOrError.propagateErr();
   2642    }
   2643    trackingNewCaretPosition.FlushAndStopTracking();
   2644    if (!newCaretPosition.IsInTextNode()) {
   2645      insertPaddingBRElementOrError.unwrap().MoveCaretPointTo(
   2646          newCaretPosition, {SuggestCaret::OnlyIfHasSuggestion});
   2647    } else {
   2648      insertPaddingBRElementOrError.unwrap().IgnoreCaretPointSuggestion();
   2649    }
   2650    if (!newCaretPosition.IsSetAndValid()) {
   2651      NS_WARNING("Inserting <br> element caused unexpected DOM tree");
   2652      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2653    }
   2654  }
   2655  return CaretPoint(std::move(newCaretPosition));
   2656 }
   2657 
   2658 Result<JoinNodesResult, nsresult>
   2659 HTMLEditor::JoinTextNodesWithNormalizeWhiteSpaces(Text& aLeftText,
   2660                                                  Text& aRightText) {
   2661  if (EditorUtils::IsWhiteSpacePreformatted(aLeftText)) {
   2662    Result<JoinNodesResult, nsresult> joinResultOrError =
   2663        JoinNodesWithTransaction(aLeftText, aRightText);
   2664    NS_WARNING_ASSERTION(joinResultOrError.isOk(),
   2665                         "HTMLEditor::JoinNodesWithTransaction() failed");
   2666    return joinResultOrError;
   2667  }
   2668  const bool isNewLinePreformatted =
   2669      EditorUtils::IsNewLinePreformatted(aLeftText);
   2670  const auto IsCollapsibleChar = [&](char16_t aChar) {
   2671    return (aChar == HTMLEditUtils::kNewLine && !isNewLinePreformatted) ||
   2672           nsCRT::IsAsciiSpace(aChar);
   2673  };
   2674  const auto IsCollapsibleCharOrNBSP = [&](char16_t aChar) {
   2675    return aChar == HTMLEditUtils::kNBSP || IsCollapsibleChar(aChar);
   2676  };
   2677  const char16_t lastLeftChar = aLeftText.DataBuffer().SafeLastChar();
   2678  char16_t firstRightChar = aRightText.DataBuffer().SafeFirstChar();
   2679  const char16_t secondRightChar = aRightText.DataBuffer().GetLength() >= 2
   2680                                       ? aRightText.DataBuffer().CharAt(1u)
   2681                                       : static_cast<char16_t>(0);
   2682  if (IsCollapsibleCharOrNBSP(firstRightChar)) {
   2683    // If the right Text starts only with a collapsible white-space and it'll
   2684    // follow a non-collapsible char, we should make it an ASCII white-space.
   2685    if (secondRightChar && !IsCollapsibleCharOrNBSP(secondRightChar) &&
   2686        lastLeftChar && !IsCollapsibleChar(lastLeftChar)) {
   2687      if (firstRightChar != HTMLEditUtils::kSpace) {
   2688        Result<InsertTextResult, nsresult> replaceWhiteSpaceResultOrError =
   2689            ReplaceTextWithTransaction(aRightText, 0u, 1u, u" "_ns);
   2690        if (MOZ_UNLIKELY(replaceWhiteSpaceResultOrError.isErr())) {
   2691          NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
   2692          return replaceWhiteSpaceResultOrError.propagateErr();
   2693        }
   2694        replaceWhiteSpaceResultOrError.unwrap().IgnoreCaretPointSuggestion();
   2695        if (NS_WARN_IF(aLeftText.GetNextSibling() != &aRightText)) {
   2696          return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2697        }
   2698        firstRightChar = HTMLEditUtils::kSpace;
   2699      }
   2700    }
   2701    // Otherwise, normalize the white-spaces before join, i.e., it will start
   2702    // with an NBSP.
   2703    else {
   2704      Result<EditorDOMPoint, nsresult> atFirstVisibleThingOrError =
   2705          WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter(
   2706              *this, EditorDOMPoint(&aRightText, 0u), {});
   2707      if (MOZ_UNLIKELY(atFirstVisibleThingOrError.isErr())) {
   2708        NS_WARNING(
   2709            "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() failed");
   2710        return atFirstVisibleThingOrError.propagateErr();
   2711      }
   2712      if (!aRightText.GetParentNode()) {
   2713        return JoinNodesResult(EditorDOMPoint::AtEndOf(aLeftText), aRightText);
   2714      }
   2715    }
   2716  } else if (IsCollapsibleCharOrNBSP(lastLeftChar) &&
   2717             lastLeftChar != HTMLEditUtils::kSpace &&
   2718             aLeftText.DataBuffer().GetLength() >= 2u) {
   2719    // If the last char of the left `Text` is a single white-space but not an
   2720    // ASCII space, let's replace it with an ASCII space.
   2721    const char16_t secondLastChar =
   2722        aLeftText.DataBuffer().CharAt(aLeftText.DataBuffer().GetLength() - 2u);
   2723    if (!IsCollapsibleCharOrNBSP(secondLastChar) &&
   2724        !IsCollapsibleCharOrNBSP(firstRightChar)) {
   2725      Result<InsertTextResult, nsresult> replaceWhiteSpaceResultOrError =
   2726          ReplaceTextWithTransaction(
   2727              aLeftText, aLeftText.DataBuffer().GetLength() - 1u, 1u, u" "_ns);
   2728      if (MOZ_UNLIKELY(replaceWhiteSpaceResultOrError.isErr())) {
   2729        NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
   2730        return replaceWhiteSpaceResultOrError.propagateErr();
   2731      }
   2732      replaceWhiteSpaceResultOrError.unwrap().IgnoreCaretPointSuggestion();
   2733      if (NS_WARN_IF(aLeftText.GetNextSibling() != &aRightText)) {
   2734        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2735      }
   2736    }
   2737  }
   2738  Result<JoinNodesResult, nsresult> joinResultOrError =
   2739      JoinNodesWithTransaction(aLeftText, aRightText);
   2740  if (MOZ_UNLIKELY(joinResultOrError.isErr())) {
   2741    NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
   2742    return joinResultOrError;
   2743  }
   2744  JoinNodesResult joinResult = joinResultOrError.unwrap();
   2745  const EditorDOMPointInText startOfRightTextData =
   2746      joinResult.AtJoinedPoint<EditorRawDOMPoint>().GetAsInText();
   2747  if (NS_WARN_IF(!startOfRightTextData.IsSet()) ||
   2748      (firstRightChar &&
   2749       (NS_WARN_IF(startOfRightTextData.IsEndOfContainer()) ||
   2750        NS_WARN_IF(firstRightChar != startOfRightTextData.Char())))) {
   2751    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2752  }
   2753  return std::move(joinResult);
   2754 }
   2755 
   2756 // static
   2757 bool HTMLEditor::CanInsertLineBreak(LineBreakType aLineBreakType,
   2758                                    const nsIContent& aContent) {
   2759  if (MOZ_UNLIKELY(!HTMLEditUtils::IsSimplyEditableNode(aContent))) {
   2760    return false;
   2761  }
   2762  if (aLineBreakType == LineBreakType::BRElement) {
   2763    return HTMLEditUtils::CanNodeContain(aContent, *nsGkAtoms::br);
   2764  }
   2765  MOZ_ASSERT(aLineBreakType == LineBreakType::Linefeed);
   2766  const Element* const container = aContent.GetAsElementOrParentElement();
   2767  return container &&
   2768         HTMLEditUtils::CanNodeContain(*container, *nsGkAtoms::textTagName) &&
   2769         EditorUtils::IsNewLinePreformatted(*container);
   2770 }
   2771 
   2772 Result<CreateLineBreakResult, nsresult>
   2773 HTMLEditor::InsertPaddingBRElementToMakeEmptyLineVisibleIfNeeded(
   2774    const EditorDOMPoint& aPointToInsert) {
   2775  MOZ_ASSERT(IsEditActionDataAvailable());
   2776  MOZ_ASSERT(aPointToInsert.IsSet());
   2777 
   2778  if (MOZ_UNLIKELY(!aPointToInsert.IsInContentNode())) {
   2779    return CreateLineBreakResult::NotHandled();
   2780  }
   2781 
   2782  // If we cannot insert a line break here, do nothing.
   2783  if (!HTMLEditor::CanInsertLineBreak(
   2784          LineBreakType::BRElement,
   2785          *aPointToInsert.ContainerAs<nsIContent>())) {
   2786    return CreateLineBreakResult::NotHandled();
   2787  }
   2788 
   2789  // FYI: We don't need to put <br> if it reaches an inline editing host because
   2790  // editing host has at least one line height by default even if it's empty and
   2791  // it's tested by WPT to no <br> element is inserted in the cases.
   2792 
   2793  // If the point is not start of a line, we don't need to put a line break
   2794  // here.
   2795  const WSScanResult previousThing =
   2796      WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   2797          {WSRunScanner::Option::OnlyEditableNodes}, aPointToInsert);
   2798  if (!previousThing.ReachedLineBoundary()) {
   2799    return CreateLineBreakResult::NotHandled();
   2800  }
   2801 
   2802  // If the point is not followed by a block boundary, we don't need to put a
   2803  // line break here.
   2804  const WSScanResult nextThing =
   2805      WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   2806          {WSRunScanner::Option::OnlyEditableNodes}, aPointToInsert);
   2807  if (!nextThing.ReachedBlockBoundary()) {
   2808    return CreateLineBreakResult::NotHandled();
   2809  }
   2810 
   2811  Result<CreateLineBreakResult, nsresult> insertLineBreakResultOrError =
   2812      InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement,
   2813                      aPointToInsert, nsIEditor::ePrevious);
   2814  NS_WARNING_ASSERTION(insertLineBreakResultOrError.isOk(),
   2815                       "HTMLEditor::InsertLineBreak(WithTransaction::Yes, "
   2816                       "LineBreakType::BRElement, ePrevious) failed");
   2817  return insertLineBreakResultOrError;
   2818 }
   2819 
   2820 Result<EditActionResult, nsresult>
   2821 HTMLEditor::MakeOrChangeListAndListItemAsSubAction(
   2822    const nsStaticAtom& aListElementOrListItemElementTagName,
   2823    const nsAString& aBulletType,
   2824    SelectAllOfCurrentList aSelectAllOfCurrentList,
   2825    const Element& aEditingHost) {
   2826  MOZ_ASSERT(IsEditActionDataAvailable());
   2827  MOZ_ASSERT(&aListElementOrListItemElementTagName == nsGkAtoms::ul ||
   2828             &aListElementOrListItemElementTagName == nsGkAtoms::ol ||
   2829             &aListElementOrListItemElementTagName == nsGkAtoms::dl ||
   2830             &aListElementOrListItemElementTagName == nsGkAtoms::dd ||
   2831             &aListElementOrListItemElementTagName == nsGkAtoms::dt);
   2832 
   2833  if (NS_WARN_IF(!mInitSucceeded)) {
   2834    return Err(NS_ERROR_NOT_INITIALIZED);
   2835  }
   2836 
   2837  {
   2838    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
   2839    if (MOZ_UNLIKELY(result.isErr())) {
   2840      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
   2841      return result;
   2842    }
   2843    if (result.inspect().Canceled()) {
   2844      return result;
   2845    }
   2846  }
   2847 
   2848  if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
   2849    NS_WARNING("Some selection containers are not content node, but ignored");
   2850    return EditActionResult::IgnoredResult();
   2851  }
   2852 
   2853  AutoPlaceholderBatch treatAsOneTransaction(
   2854      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   2855 
   2856  // XXX EditSubAction::eCreateOrChangeDefinitionListItem and
   2857  //     EditSubAction::eCreateOrChangeList are treated differently in
   2858  //     HTMLEditor::MaybeSplitElementsAtEveryBRElement().  Only when
   2859  //     EditSubAction::eCreateOrChangeList, it splits inline nodes.
   2860  //     Currently, it shouldn't be done when we called for formatting
   2861  //     `<dd>` or `<dt>` by
   2862  //     HTMLEditor::MakeDefinitionListItemWithTransaction().  But this
   2863  //     difference may be a bug.  We should investigate this later.
   2864  IgnoredErrorResult error;
   2865  AutoEditSubActionNotifier startToHandleEditSubAction(
   2866      *this,
   2867      &aListElementOrListItemElementTagName == nsGkAtoms::dd ||
   2868              &aListElementOrListItemElementTagName == nsGkAtoms::dt
   2869          ? EditSubAction::eCreateOrChangeDefinitionListItem
   2870          : EditSubAction::eCreateOrChangeList,
   2871      nsIEditor::eNext, error);
   2872  if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   2873    return Err(error.StealNSResult());
   2874  }
   2875  NS_WARNING_ASSERTION(
   2876      !error.Failed(),
   2877      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   2878 
   2879  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
   2880  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   2881    return Err(NS_ERROR_EDITOR_DESTROYED);
   2882  }
   2883  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2884                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
   2885                       "failed, but ignored");
   2886 
   2887  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
   2888    nsresult rv = EnsureCaretNotAfterInvisibleBRElement(aEditingHost);
   2889    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   2890      return Err(NS_ERROR_EDITOR_DESTROYED);
   2891    }
   2892    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   2893                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
   2894                         "failed, but ignored");
   2895    if (NS_SUCCEEDED(rv)) {
   2896      nsresult rv = PrepareInlineStylesForCaret();
   2897      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   2898        return Err(NS_ERROR_EDITOR_DESTROYED);
   2899      }
   2900      NS_WARNING_ASSERTION(
   2901          NS_SUCCEEDED(rv),
   2902          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
   2903    }
   2904  }
   2905 
   2906  const nsStaticAtom* listTagName = nullptr;
   2907  const nsStaticAtom* listItemTagName = nullptr;
   2908  if (&aListElementOrListItemElementTagName == nsGkAtoms::ul ||
   2909      &aListElementOrListItemElementTagName == nsGkAtoms::ol) {
   2910    listTagName = &aListElementOrListItemElementTagName;
   2911    listItemTagName = nsGkAtoms::li;
   2912  } else if (&aListElementOrListItemElementTagName == nsGkAtoms::dl) {
   2913    listTagName = &aListElementOrListItemElementTagName;
   2914    listItemTagName = nsGkAtoms::dd;
   2915  } else if (&aListElementOrListItemElementTagName == nsGkAtoms::dd ||
   2916             &aListElementOrListItemElementTagName == nsGkAtoms::dt) {
   2917    listTagName = nsGkAtoms::dl;
   2918    listItemTagName = &aListElementOrListItemElementTagName;
   2919  } else {
   2920    NS_WARNING(
   2921        "aListElementOrListItemElementTagName was neither list element name "
   2922        "nor "
   2923        "definition listitem element name");
   2924    return Err(NS_ERROR_INVALID_ARG);
   2925  }
   2926 
   2927  // Expands selection range to include the immediate block parent, and then
   2928  // further expands to include any ancestors whose children are all in the
   2929  // range.
   2930  // XXX Why do we do this only when there is only one selection range?
   2931  if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) {
   2932    Result<EditorRawDOMRange, nsresult> extendedRange =
   2933        GetRangeExtendedToHardLineEdgesForBlockEditAction(
   2934            SelectionRef().GetRangeAt(0u), aEditingHost);
   2935    if (MOZ_UNLIKELY(extendedRange.isErr())) {
   2936      NS_WARNING(
   2937          "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
   2938          "failed");
   2939      return extendedRange.propagateErr();
   2940    }
   2941    // Note that end point may be prior to start point.  So, we
   2942    // cannot use Selection::SetStartAndEndInLimit() here.
   2943    error.SuppressException();
   2944    SelectionRef().SetBaseAndExtentInLimiter(
   2945        extendedRange.inspect().StartRef().ToRawRangeBoundary(),
   2946        extendedRange.inspect().EndRef().ToRawRangeBoundary(), error);
   2947    if (NS_WARN_IF(Destroyed())) {
   2948      return Err(NS_ERROR_EDITOR_DESTROYED);
   2949    }
   2950    if (MOZ_UNLIKELY(error.Failed())) {
   2951      NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
   2952      return Err(error.StealNSResult());
   2953    }
   2954  }
   2955 
   2956  AutoListElementCreator listCreator(*listTagName, *listItemTagName,
   2957                                     aBulletType);
   2958  AutoClonedSelectionRangeArray selectionRanges(SelectionRef());
   2959  Result<EditActionResult, nsresult> result = listCreator.Run(
   2960      *this, selectionRanges, aSelectAllOfCurrentList, aEditingHost);
   2961  if (MOZ_UNLIKELY(result.isErr())) {
   2962    NS_WARNING("HTMLEditor::ConvertContentAroundRangesToList() failed");
   2963    // XXX Should we try to restore selection ranges in this case?
   2964    return result;
   2965  }
   2966 
   2967  rv = selectionRanges.ApplyTo(SelectionRef());
   2968  if (NS_WARN_IF(Destroyed())) {
   2969    return Err(NS_ERROR_EDITOR_DESTROYED);
   2970  }
   2971  if (NS_FAILED(rv)) {
   2972    NS_WARNING("AutoClonedSelectionRangeArray::ApplyTo() failed");
   2973    return Err(rv);
   2974  }
   2975  return result.inspect().Ignored() ? EditActionResult::CanceledResult()
   2976                                    : EditActionResult::HandledResult();
   2977 }
   2978 
   2979 Result<EditActionResult, nsresult> HTMLEditor::AutoListElementCreator::Run(
   2980    HTMLEditor& aHTMLEditor, AutoClonedSelectionRangeArray& aRanges,
   2981    SelectAllOfCurrentList aSelectAllOfCurrentList,
   2982    const Element& aEditingHost) const {
   2983  MOZ_ASSERT(aHTMLEditor.IsTopLevelEditSubActionDataAvailable());
   2984  MOZ_ASSERT(!aHTMLEditor.IsSelectionRangeContainerNotContent());
   2985 
   2986  if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(aHTMLEditor))) {
   2987    return Err(NS_ERROR_FAILURE);
   2988  }
   2989 
   2990  AutoContentNodeArray arrayOfContents;
   2991  nsresult rv = SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList(
   2992      aHTMLEditor, aRanges, aSelectAllOfCurrentList, aEditingHost,
   2993      arrayOfContents);
   2994  if (NS_FAILED(rv)) {
   2995    NS_WARNING(
   2996        "AutoListElementCreator::"
   2997        "SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList() failed");
   2998    return Err(rv);
   2999  }
   3000 
   3001  // check if all our nodes are <br>s, or empty inlines
   3002  // if no nodes, we make empty list.  Ditto if the user tried to make a list
   3003  // of some # of breaks.
   3004  if (AutoListElementCreator::
   3005          IsEmptyOrContainsOnlyBRElementsOrEmptyInlineElements(
   3006              arrayOfContents)) {
   3007    Result<RefPtr<Element>, nsresult> newListItemElementOrError =
   3008        ReplaceContentNodesWithEmptyNewList(aHTMLEditor, aRanges,
   3009                                            arrayOfContents, aEditingHost);
   3010    if (MOZ_UNLIKELY(newListItemElementOrError.isErr())) {
   3011      NS_WARNING(
   3012          "AutoListElementCreator::ReplaceContentNodesWithEmptyNewList() "
   3013          "failed");
   3014      return newListItemElementOrError.propagateErr();
   3015    }
   3016    if (MOZ_UNLIKELY(!newListItemElementOrError.inspect())) {
   3017      aRanges.RestoreFromSavedRanges();
   3018      return EditActionResult::CanceledResult();
   3019    }
   3020    aRanges.ClearSavedRanges();
   3021    nsresult rv = aRanges.Collapse(
   3022        EditorRawDOMPoint(newListItemElementOrError.inspect(), 0u));
   3023    if (NS_FAILED(rv)) {
   3024      NS_WARNING("AutoClonedRangeArray::Collapse() failed");
   3025      return Err(rv);
   3026    }
   3027    return EditActionResult::IgnoredResult();
   3028  }
   3029 
   3030  Result<RefPtr<Element>, nsresult> listItemOrListToPutCaretOrError =
   3031      WrapContentNodesIntoNewListElements(aHTMLEditor, aRanges, arrayOfContents,
   3032                                          aEditingHost);
   3033  if (MOZ_UNLIKELY(listItemOrListToPutCaretOrError.isErr())) {
   3034    NS_WARNING(
   3035        "AutoListElementCreator::WrapContentNodesIntoNewListElements() failed");
   3036    return listItemOrListToPutCaretOrError.propagateErr();
   3037  }
   3038 
   3039  MOZ_ASSERT(aRanges.HasSavedRanges());
   3040  aRanges.RestoreFromSavedRanges();
   3041 
   3042  // If selection will be collapsed but not in listItemOrListToPutCaret, we need
   3043  // to adjust the caret position into it.
   3044  if (listItemOrListToPutCaretOrError.inspect()) {
   3045    DebugOnly<nsresult> rvIgnored =
   3046        EnsureCollapsedRangeIsInListItemOrListElement(
   3047            *listItemOrListToPutCaretOrError.inspect(), aRanges);
   3048    NS_WARNING_ASSERTION(
   3049        NS_SUCCEEDED(rvIgnored),
   3050        "AutoListElementCreator::"
   3051        "EnsureCollapsedRangeIsInListItemOrListElement() failed, but ignored");
   3052  }
   3053 
   3054  return EditActionResult::HandledResult();
   3055 }
   3056 
   3057 nsresult HTMLEditor::AutoListElementCreator::
   3058    SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList(
   3059        HTMLEditor& aHTMLEditor, AutoClonedRangeArray& aRanges,
   3060        SelectAllOfCurrentList aSelectAllOfCurrentList,
   3061        const Element& aEditingHost,
   3062        ContentNodeArray& aOutArrayOfContents) const {
   3063  MOZ_ASSERT(aOutArrayOfContents.IsEmpty());
   3064 
   3065  if (aSelectAllOfCurrentList == SelectAllOfCurrentList::Yes) {
   3066    if (Element* parentListElementOfRanges =
   3067            aRanges.GetClosestAncestorAnyListElementOfRange()) {
   3068      aOutArrayOfContents.AppendElement(
   3069          OwningNonNull<nsIContent>(*parentListElementOfRanges));
   3070      return NS_OK;
   3071    }
   3072  }
   3073 
   3074  AutoClonedRangeArray extendedRanges(aRanges);
   3075 
   3076  // TODO: We don't need AutoTransactionsConserveSelection here in the
   3077  //       normal cases, but removing this may cause the behavior with the
   3078  //       legacy mutation event listeners.  We should try to delete this in
   3079  //       a bug.
   3080  AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
   3081 
   3082  extendedRanges.ExtendRangesToWrapLines(EditSubAction::eCreateOrChangeList,
   3083                                         BlockInlineCheck::UseHTMLDefaultStyle,
   3084                                         aEditingHost);
   3085  Result<EditorDOMPoint, nsresult> splitResult =
   3086      extendedRanges.SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
   3087          aHTMLEditor, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
   3088  if (MOZ_UNLIKELY(splitResult.isErr())) {
   3089    NS_WARNING(
   3090        "AutoClonedRangeArray::"
   3091        "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() failed");
   3092    return splitResult.unwrapErr();
   3093  }
   3094  nsresult rv = extendedRanges.CollectEditTargetNodes(
   3095      aHTMLEditor, aOutArrayOfContents, EditSubAction::eCreateOrChangeList,
   3096      AutoClonedRangeArray::CollectNonEditableNodes::No);
   3097  if (NS_FAILED(rv)) {
   3098    NS_WARNING(
   3099        "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::"
   3100        "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
   3101    return rv;
   3102  }
   3103 
   3104  Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
   3105      aHTMLEditor.MaybeSplitElementsAtEveryBRElement(
   3106          aOutArrayOfContents, EditSubAction::eCreateOrChangeList);
   3107  if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
   3108    NS_WARNING(
   3109        "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
   3110        "eCreateOrChangeList) failed");
   3111    return splitAtBRElementsResult.unwrapErr();
   3112  }
   3113  return NS_OK;
   3114 }
   3115 
   3116 // static
   3117 bool HTMLEditor::AutoListElementCreator::
   3118    IsEmptyOrContainsOnlyBRElementsOrEmptyInlineElements(
   3119        const ContentNodeArray& aArrayOfContents) {
   3120  for (const OwningNonNull<nsIContent>& content : aArrayOfContents) {
   3121    // if content is not a <br> or empty inline, we're done
   3122    // XXX Should we handle line breaks in preformatted text node?
   3123    if (!content->IsHTMLElement(nsGkAtoms::br) &&
   3124        !HTMLEditUtils::IsEmptyInlineContainer(
   3125            content,
   3126            {EmptyCheckOption::TreatSingleBRElementAsVisible,
   3127             EmptyCheckOption::TreatNonEditableContentAsInvisible},
   3128            BlockInlineCheck::UseComputedDisplayStyle)) {
   3129      return false;
   3130    }
   3131  }
   3132  return true;
   3133 }
   3134 
   3135 Result<RefPtr<Element>, nsresult>
   3136 HTMLEditor::AutoListElementCreator::ReplaceContentNodesWithEmptyNewList(
   3137    HTMLEditor& aHTMLEditor, const AutoClonedRangeArray& aRanges,
   3138    const AutoContentNodeArray& aArrayOfContents,
   3139    const Element& aEditingHost) const {
   3140  // if only breaks, delete them
   3141  for (const OwningNonNull<nsIContent>& content : aArrayOfContents) {
   3142    // MOZ_KnownLive because of bug 1620312
   3143    nsresult rv =
   3144        aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(*content));
   3145    if (NS_FAILED(rv)) {
   3146      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   3147      return Err(rv);
   3148    }
   3149  }
   3150 
   3151  const auto firstRangeStartPoint =
   3152      aRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
   3153  if (NS_WARN_IF(!firstRangeStartPoint.IsSet())) {
   3154    return Err(NS_ERROR_FAILURE);
   3155  }
   3156 
   3157  // Make sure we can put a list here.
   3158  if (!HTMLEditUtils::CanNodeContain(*firstRangeStartPoint.GetContainer(),
   3159                                     mListTagName)) {
   3160    return RefPtr<Element>();
   3161  }
   3162 
   3163  RefPtr<Element> newListItemElement;
   3164  Result<CreateElementResult, nsresult> createNewListElementResult =
   3165      aHTMLEditor.InsertElementWithSplittingAncestorsWithTransaction(
   3166          mListTagName, firstRangeStartPoint, BRElementNextToSplitPoint::Keep,
   3167          aEditingHost,
   3168          // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
   3169          [&](HTMLEditor& aHTMLEditor, Element& aListElement,
   3170              const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
   3171            AutoHandlingState dummyState;
   3172            Result<CreateElementResult, nsresult> createListItemElementResult =
   3173                AppendListItemElement(aHTMLEditor, aListElement, dummyState);
   3174            if (MOZ_UNLIKELY(createListItemElementResult.isErr())) {
   3175              NS_WARNING(
   3176                  "AutoListElementCreator::AppendListItemElement() failed");
   3177              return createListItemElementResult.unwrapErr();
   3178            }
   3179            CreateElementResult unwrappedResult =
   3180                createListItemElementResult.unwrap();
   3181            // There is AutoSelectionRestorer in this method so that it'll
   3182            // be restored or updated with making it abort.  Therefore,
   3183            // we don't need to update selection here.
   3184            // XXX I'd like to check aRanges.HasSavedRanges() here, but it
   3185            //     requires ifdefs to avoid bustage of opt builds caused
   3186            //     by unused warning...
   3187            unwrappedResult.IgnoreCaretPointSuggestion();
   3188            newListItemElement = unwrappedResult.UnwrapNewNode();
   3189            MOZ_ASSERT(newListItemElement);
   3190            return NS_OK;
   3191          });
   3192  if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
   3193    NS_WARNING(
   3194        nsPrintfCString(
   3195            "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
   3196            "%s) failed",
   3197            nsAtomCString(&mListTagName).get())
   3198            .get());
   3199    return createNewListElementResult.propagateErr();
   3200  }
   3201  MOZ_ASSERT(createNewListElementResult.inspect().GetNewNode());
   3202 
   3203  // Put selection in new list item and don't restore the Selection.
   3204  createNewListElementResult.inspect().IgnoreCaretPointSuggestion();
   3205  return newListItemElement;
   3206 }
   3207 
   3208 Result<RefPtr<Element>, nsresult>
   3209 HTMLEditor::AutoListElementCreator::WrapContentNodesIntoNewListElements(
   3210    HTMLEditor& aHTMLEditor, AutoClonedRangeArray& aRanges,
   3211    AutoContentNodeArray& aArrayOfContents, const Element& aEditingHost) const {
   3212  // if there is only one node in the array, and it is a list, div, or
   3213  // blockquote, then look inside of it until we find inner list or content.
   3214  if (aArrayOfContents.Length() == 1) {
   3215    if (Element* deepestDivBlockquoteOrListElement =
   3216            HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild(
   3217                aArrayOfContents[0], {WalkTreeOption::IgnoreNonEditableNode},
   3218                BlockInlineCheck::UseHTMLDefaultStyle, nsGkAtoms::div,
   3219                nsGkAtoms::blockquote, nsGkAtoms::ul, nsGkAtoms::ol,
   3220                nsGkAtoms::dl)) {
   3221      if (deepestDivBlockquoteOrListElement->IsAnyOfHTMLElements(
   3222              nsGkAtoms::div, nsGkAtoms::blockquote)) {
   3223        aArrayOfContents.Clear();
   3224        HTMLEditUtils::CollectChildren(*deepestDivBlockquoteOrListElement,
   3225                                       aArrayOfContents, 0, {});
   3226      } else {
   3227        aArrayOfContents.ReplaceElementAt(
   3228            0, OwningNonNull<nsIContent>(*deepestDivBlockquoteOrListElement));
   3229      }
   3230    }
   3231  }
   3232 
   3233  // Ok, now go through all the nodes and put then in the list,
   3234  // or whatever is appropriate.  Wohoo!
   3235  AutoHandlingState handlingState;
   3236  for (const OwningNonNull<nsIContent>& content : aArrayOfContents) {
   3237    // MOZ_KnownLive because of bug 1620312
   3238    nsresult rv = HandleChildContent(aHTMLEditor, MOZ_KnownLive(content),
   3239                                     handlingState, aEditingHost);
   3240    if (NS_FAILED(rv)) {
   3241      NS_WARNING("AutoListElementCreator::HandleChildContent() failed");
   3242      return Err(rv);
   3243    }
   3244  }
   3245 
   3246  return std::move(handlingState.mListOrListItemElementToPutCaret);
   3247 }
   3248 
   3249 nsresult HTMLEditor::AutoListElementCreator::HandleChildContent(
   3250    HTMLEditor& aHTMLEditor, nsIContent& aHandlingContent,
   3251    AutoHandlingState& aState, const Element& aEditingHost) const {
   3252  // make sure we don't assemble content that is in different table cells
   3253  // into the same list.  respect table cell boundaries when listifying.
   3254  if (aState.mCurrentListElement &&
   3255      HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
   3256          *aState.mCurrentListElement) !=
   3257          HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
   3258              aHandlingContent)) {
   3259    aState.mCurrentListElement = nullptr;
   3260  }
   3261 
   3262  // If current node is a `<br>` element, delete it and forget previous
   3263  // list item element.
   3264  // If current node is an empty inline node, just delete it.
   3265  if (EditorUtils::IsEditableContent(aHandlingContent, EditorType::HTML) &&
   3266      (aHandlingContent.IsHTMLElement(nsGkAtoms::br) ||
   3267       HTMLEditUtils::IsEmptyInlineContainer(
   3268           aHandlingContent,
   3269           {EmptyCheckOption::TreatSingleBRElementAsVisible,
   3270            EmptyCheckOption::TreatNonEditableContentAsInvisible},
   3271           BlockInlineCheck::UseHTMLDefaultStyle))) {
   3272    nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aHandlingContent);
   3273    if (NS_FAILED(rv)) {
   3274      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   3275      return rv;
   3276    }
   3277    if (aHandlingContent.IsHTMLElement(nsGkAtoms::br)) {
   3278      aState.mPreviousListItemElement = nullptr;
   3279    }
   3280    return NS_OK;
   3281  }
   3282 
   3283  // If we meet a list, we can reuse it or convert it to the expected type list.
   3284  if (HTMLEditUtils::IsListElement(aHandlingContent)) {
   3285    nsresult rv = HandleChildListElement(
   3286        aHTMLEditor, MOZ_KnownLive(*aHandlingContent.AsElement()), aState);
   3287    NS_WARNING_ASSERTION(
   3288        NS_SUCCEEDED(rv),
   3289        "AutoListElementCreator::HandleChildListElement() failed");
   3290    return rv;
   3291  }
   3292 
   3293  // We cannot handle nodes if not in element node.
   3294  if (NS_WARN_IF(!aHandlingContent.GetParentElement())) {
   3295    return NS_ERROR_FAILURE;
   3296  }
   3297 
   3298  // If we meet a list item, we can just move it to current list element or new
   3299  // list element.
   3300  if (HTMLEditUtils::IsListItemElement(aHandlingContent)) {
   3301    nsresult rv = HandleChildListItemElement(
   3302        aHTMLEditor, MOZ_KnownLive(*aHandlingContent.AsElement()), aState);
   3303    NS_WARNING_ASSERTION(
   3304        NS_SUCCEEDED(rv),
   3305        "AutoListElementCreator::HandleChildListItemElement() failed");
   3306    return rv;
   3307  }
   3308 
   3309  // If we meet a <div> or a <p>, we want only its children to wrapping into
   3310  // list element.  Therefore, this call will call this recursively.
   3311  if (aHandlingContent.IsAnyOfHTMLElements(nsGkAtoms::div, nsGkAtoms::p)) {
   3312    nsresult rv = HandleChildDivOrParagraphElement(
   3313        aHTMLEditor, MOZ_KnownLive(*aHandlingContent.AsElement()), aState,
   3314        aEditingHost);
   3315    NS_WARNING_ASSERTION(
   3316        NS_SUCCEEDED(rv),
   3317        "AutoListElementCreator::HandleChildDivOrParagraphElement() failed");
   3318    return rv;
   3319  }
   3320 
   3321  // If we've not met a list element, create a list element and make it
   3322  // current list element.
   3323  if (!aState.mCurrentListElement) {
   3324    nsresult rv = CreateAndUpdateCurrentListElement(
   3325        aHTMLEditor, EditorDOMPoint(&aHandlingContent),
   3326        EmptyListItem::NotCreate, aState, aEditingHost);
   3327    if (NS_FAILED(rv)) {
   3328      NS_WARNING("AutoListElementCreator::HandleChildInlineElement() failed");
   3329      return rv;
   3330    }
   3331  }
   3332 
   3333  // If we meet an inline content, we want to move it to previously used list
   3334  // item element or new list item element.
   3335  if (HTMLEditUtils::IsInlineContent(aHandlingContent,
   3336                                     BlockInlineCheck::UseHTMLDefaultStyle)) {
   3337    nsresult rv =
   3338        HandleChildInlineContent(aHTMLEditor, aHandlingContent, aState);
   3339    NS_WARNING_ASSERTION(
   3340        NS_SUCCEEDED(rv),
   3341        "AutoListElementCreator::HandleChildInlineElement() failed");
   3342    return rv;
   3343  }
   3344 
   3345  // Otherwise, we should wrap it into new list item element.
   3346  nsresult rv =
   3347      WrapContentIntoNewListItemElement(aHTMLEditor, aHandlingContent, aState);
   3348  NS_WARNING_ASSERTION(
   3349      NS_SUCCEEDED(rv),
   3350      "AutoListElementCreator::WrapContentIntoNewListItemElement() failed");
   3351  return rv;
   3352 }
   3353 
   3354 nsresult HTMLEditor::AutoListElementCreator::HandleChildListElement(
   3355    HTMLEditor& aHTMLEditor, Element& aHandlingListElement,
   3356    AutoHandlingState& aState) const {
   3357  MOZ_ASSERT(HTMLEditUtils::IsListElement(aHandlingListElement));
   3358 
   3359  // If we met a list element and current list element is not a descendant
   3360  // of the list, append current node to end of the current list element.
   3361  // Then, wrap it with list item element and delete the old container.
   3362  if (aState.mCurrentListElement &&
   3363      !EditorUtils::IsDescendantOf(aHandlingListElement,
   3364                                   *aState.mCurrentListElement)) {
   3365    Result<MoveNodeResult, nsresult> moveNodeResult =
   3366        aHTMLEditor.MoveNodeToEndWithTransaction(
   3367            aHandlingListElement, MOZ_KnownLive(*aState.mCurrentListElement));
   3368    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
   3369      NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
   3370      return moveNodeResult.propagateErr();
   3371    }
   3372    moveNodeResult.inspect().IgnoreCaretPointSuggestion();
   3373 
   3374    Result<CreateElementResult, nsresult> convertListTypeResult =
   3375        aHTMLEditor.ChangeListElementType(aHandlingListElement, mListTagName,
   3376                                          mListItemTagName);
   3377    if (MOZ_UNLIKELY(convertListTypeResult.isErr())) {
   3378      NS_WARNING("HTMLEditor::ChangeListElementType() failed");
   3379      return convertListTypeResult.propagateErr();
   3380    }
   3381    convertListTypeResult.inspect().IgnoreCaretPointSuggestion();
   3382 
   3383    Result<EditorDOMPoint, nsresult> unwrapNewListElementResult =
   3384        aHTMLEditor.RemoveBlockContainerWithTransaction(
   3385            MOZ_KnownLive(*convertListTypeResult.inspect().GetNewNode()));
   3386    if (MOZ_UNLIKELY(unwrapNewListElementResult.isErr())) {
   3387      NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
   3388      return unwrapNewListElementResult.propagateErr();
   3389    }
   3390    aState.mPreviousListItemElement = nullptr;
   3391    return NS_OK;
   3392  }
   3393 
   3394  // If current list element is in found list element or we've not met a
   3395  // list element, convert current list element to proper type.
   3396  Result<CreateElementResult, nsresult> convertListTypeResult =
   3397      aHTMLEditor.ChangeListElementType(aHandlingListElement, mListTagName,
   3398                                        mListItemTagName);
   3399  if (MOZ_UNLIKELY(convertListTypeResult.isErr())) {
   3400    NS_WARNING("HTMLEditor::ChangeListElementType() failed");
   3401    return convertListTypeResult.propagateErr();
   3402  }
   3403  CreateElementResult unwrappedConvertListTypeResult =
   3404      convertListTypeResult.unwrap();
   3405  unwrappedConvertListTypeResult.IgnoreCaretPointSuggestion();
   3406  MOZ_ASSERT(unwrappedConvertListTypeResult.GetNewNode());
   3407  aState.mCurrentListElement = unwrappedConvertListTypeResult.UnwrapNewNode();
   3408  aState.mPreviousListItemElement = nullptr;
   3409  return NS_OK;
   3410 }
   3411 
   3412 nsresult
   3413 HTMLEditor::AutoListElementCreator::HandleChildListItemInDifferentTypeList(
   3414    HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement,
   3415    AutoHandlingState& aState) const {
   3416  MOZ_ASSERT(HTMLEditUtils::IsListItemElement(aHandlingListItemElement));
   3417  MOZ_ASSERT(
   3418      !aHandlingListItemElement.GetParent()->IsHTMLElement(&mListTagName));
   3419 
   3420  // If we've not met a list element or current node is not in current list
   3421  // element, insert a list element at current node and set current list element
   3422  // to the new one.
   3423  if (!aState.mCurrentListElement ||
   3424      aHandlingListItemElement.IsInclusiveDescendantOf(
   3425          aState.mCurrentListElement)) {
   3426    EditorDOMPoint atListItem(&aHandlingListItemElement);
   3427    MOZ_ASSERT(atListItem.IsInContentNode());
   3428 
   3429    Result<SplitNodeResult, nsresult> splitListItemParentResult =
   3430        aHTMLEditor.SplitNodeWithTransaction(atListItem);
   3431    if (MOZ_UNLIKELY(splitListItemParentResult.isErr())) {
   3432      NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
   3433      return splitListItemParentResult.propagateErr();
   3434    }
   3435    SplitNodeResult unwrappedSplitListItemParentResult =
   3436        splitListItemParentResult.unwrap();
   3437    MOZ_ASSERT(unwrappedSplitListItemParentResult.DidSplit());
   3438    unwrappedSplitListItemParentResult.IgnoreCaretPointSuggestion();
   3439 
   3440    Result<CreateElementResult, nsresult> createNewListElementResult =
   3441        aHTMLEditor.CreateAndInsertElement(
   3442            WithTransaction::Yes, mListTagName,
   3443            unwrappedSplitListItemParentResult.AtNextContent<EditorDOMPoint>());
   3444    if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
   3445      NS_WARNING(
   3446          "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
   3447          "failed");
   3448      return createNewListElementResult.propagateErr();
   3449    }
   3450    CreateElementResult unwrapCreateNewListElementResult =
   3451        createNewListElementResult.unwrap();
   3452    unwrapCreateNewListElementResult.IgnoreCaretPointSuggestion();
   3453    MOZ_ASSERT(unwrapCreateNewListElementResult.GetNewNode());
   3454    aState.mCurrentListElement =
   3455        unwrapCreateNewListElementResult.UnwrapNewNode();
   3456  }
   3457 
   3458  // Then, move current node into current list element.
   3459  Result<MoveNodeResult, nsresult> moveNodeResult =
   3460      aHTMLEditor.MoveNodeToEndWithTransaction(
   3461          aHandlingListItemElement, MOZ_KnownLive(*aState.mCurrentListElement));
   3462  if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
   3463    NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
   3464    return moveNodeResult.propagateErr();
   3465  }
   3466  moveNodeResult.inspect().IgnoreCaretPointSuggestion();
   3467 
   3468  // Convert list item type if current node is different list item type.
   3469  if (aHandlingListItemElement.IsHTMLElement(&mListItemTagName)) {
   3470    return NS_OK;
   3471  }
   3472  Result<CreateElementResult, nsresult> newListItemElementOrError =
   3473      aHTMLEditor.ReplaceContainerAndCloneAttributesWithTransaction(
   3474          aHandlingListItemElement, mListItemTagName);
   3475  if (MOZ_UNLIKELY(newListItemElementOrError.isErr())) {
   3476    NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
   3477    return newListItemElementOrError.propagateErr();
   3478  }
   3479  newListItemElementOrError.inspect().IgnoreCaretPointSuggestion();
   3480  return NS_OK;
   3481 }
   3482 
   3483 nsresult HTMLEditor::AutoListElementCreator::HandleChildListItemElement(
   3484    HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement,
   3485    AutoHandlingState& aState) const {
   3486  MOZ_ASSERT(aHandlingListItemElement.GetParentNode());
   3487  MOZ_ASSERT(HTMLEditUtils::IsListItemElement(aHandlingListItemElement));
   3488 
   3489  // If current list item element is not in proper list element, we need
   3490  // to convert the list element.
   3491  // XXX This check is not enough,
   3492  if (!aHandlingListItemElement.GetParentNode()->IsHTMLElement(&mListTagName)) {
   3493    nsresult rv = HandleChildListItemInDifferentTypeList(
   3494        aHTMLEditor, aHandlingListItemElement, aState);
   3495    if (NS_FAILED(rv)) {
   3496      NS_WARNING(
   3497          "AutoListElementCreator::HandleChildListItemInDifferentTypeList() "
   3498          "failed");
   3499      return rv;
   3500    }
   3501  } else {
   3502    nsresult rv = HandleChildListItemInSameTypeList(
   3503        aHTMLEditor, aHandlingListItemElement, aState);
   3504    if (NS_FAILED(rv)) {
   3505      NS_WARNING(
   3506          "AutoListElementCreator::HandleChildListItemInSameTypeList() failed");
   3507      return rv;
   3508    }
   3509  }
   3510 
   3511  // If bullet type is specified, set list type attribute.
   3512  // XXX Cannot we set type attribute before inserting the list item
   3513  //     element into the DOM tree?
   3514  if (!mBulletType.IsEmpty()) {
   3515    nsresult rv = aHTMLEditor.SetAttributeWithTransaction(
   3516        aHandlingListItemElement, *nsGkAtoms::type, mBulletType);
   3517    if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
   3518      return NS_ERROR_EDITOR_DESTROYED;
   3519    }
   3520    NS_WARNING_ASSERTION(
   3521        NS_SUCCEEDED(rv),
   3522        "EditorBase::SetAttributeWithTransaction(nsGkAtoms::type) failed");
   3523    return rv;
   3524  }
   3525 
   3526  // Otherwise, remove list type attribute if there is.
   3527  if (!aHandlingListItemElement.HasAttr(nsGkAtoms::type)) {
   3528    return NS_OK;
   3529  }
   3530  nsresult rv = aHTMLEditor.RemoveAttributeWithTransaction(
   3531      aHandlingListItemElement, *nsGkAtoms::type);
   3532  NS_WARNING_ASSERTION(
   3533      NS_SUCCEEDED(rv),
   3534      "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::type) failed");
   3535  return rv;
   3536 }
   3537 
   3538 nsresult HTMLEditor::AutoListElementCreator::HandleChildListItemInSameTypeList(
   3539    HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement,
   3540    AutoHandlingState& aState) const {
   3541  MOZ_ASSERT(HTMLEditUtils::IsListItemElement(aHandlingListItemElement));
   3542  MOZ_ASSERT(
   3543      aHandlingListItemElement.GetParent()->IsHTMLElement(&mListTagName));
   3544 
   3545  EditorDOMPoint atListItem(&aHandlingListItemElement);
   3546  MOZ_ASSERT(atListItem.IsInContentNode());
   3547 
   3548  // If we've not met a list element, set current list element to the
   3549  // parent of current list item element.
   3550  if (!aState.mCurrentListElement) {
   3551    aState.mCurrentListElement = atListItem.GetContainerAs<Element>();
   3552    NS_WARNING_ASSERTION(
   3553        HTMLEditUtils::IsListElement(*aState.mCurrentListElement),
   3554        "Current list item parent is not a list element");
   3555  }
   3556  // If current list item element is not a child of current list element,
   3557  // move it into current list item.
   3558  else if (atListItem.GetContainer() != aState.mCurrentListElement) {
   3559    Result<MoveNodeResult, nsresult> moveNodeResult =
   3560        aHTMLEditor.MoveNodeToEndWithTransaction(
   3561            aHandlingListItemElement,
   3562            MOZ_KnownLive(*aState.mCurrentListElement));
   3563    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
   3564      NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
   3565      return moveNodeResult.propagateErr();
   3566    }
   3567    moveNodeResult.inspect().IgnoreCaretPointSuggestion();
   3568  }
   3569 
   3570  // Then, if current list item element is not proper type for current
   3571  // list element, convert list item element to proper element.
   3572  if (aHandlingListItemElement.IsHTMLElement(&mListItemTagName)) {
   3573    return NS_OK;
   3574  }
   3575  // FIXME: Manage attribute cloning
   3576  Result<CreateElementResult, nsresult> newListItemElementOrError =
   3577      aHTMLEditor.ReplaceContainerAndCloneAttributesWithTransaction(
   3578          aHandlingListItemElement, mListItemTagName);
   3579  if (MOZ_UNLIKELY(newListItemElementOrError.isErr())) {
   3580    NS_WARNING(
   3581        "HTMLEditor::ReplaceContainerAndCloneAttributesWithTransaction() "
   3582        "failed");
   3583    return newListItemElementOrError.propagateErr();
   3584  }
   3585  newListItemElementOrError.inspect().IgnoreCaretPointSuggestion();
   3586  return NS_OK;
   3587 }
   3588 
   3589 nsresult HTMLEditor::AutoListElementCreator::HandleChildDivOrParagraphElement(
   3590    HTMLEditor& aHTMLEditor, Element& aHandlingDivOrParagraphElement,
   3591    AutoHandlingState& aState, const Element& aEditingHost) const {
   3592  MOZ_ASSERT(aHandlingDivOrParagraphElement.IsAnyOfHTMLElements(nsGkAtoms::div,
   3593                                                                nsGkAtoms::p));
   3594 
   3595  AutoRestore<RefPtr<Element>> previouslyReplacingBlockElement(
   3596      aState.mReplacingBlockElement);
   3597  aState.mReplacingBlockElement = &aHandlingDivOrParagraphElement;
   3598  AutoRestore<bool> previouslyReplacingBlockElementIdCopied(
   3599      aState.mMaybeCopiedReplacingBlockElementId);
   3600  aState.mMaybeCopiedReplacingBlockElementId = false;
   3601 
   3602  // If the <div> or <p> is empty, we should replace it with a list element
   3603  // and/or a list item element.
   3604  if (HTMLEditUtils::IsEmptyNode(aHandlingDivOrParagraphElement,
   3605                                 {EmptyCheckOption::TreatListItemAsVisible,
   3606                                  EmptyCheckOption::TreatTableCellAsVisible})) {
   3607    if (!aState.mCurrentListElement) {
   3608      nsresult rv = CreateAndUpdateCurrentListElement(
   3609          aHTMLEditor, EditorDOMPoint(&aHandlingDivOrParagraphElement),
   3610          EmptyListItem::Create, aState, aEditingHost);
   3611      if (NS_FAILED(rv)) {
   3612        NS_WARNING(
   3613            "AutoListElementCreator::CreateAndUpdateCurrentListElement("
   3614            "EmptyListItem::Create) failed");
   3615        return rv;
   3616      }
   3617    } else {
   3618      Result<CreateElementResult, nsresult> createListItemElementResult =
   3619          AppendListItemElement(
   3620              aHTMLEditor, MOZ_KnownLive(*aState.mCurrentListElement), aState);
   3621      if (MOZ_UNLIKELY(createListItemElementResult.isErr())) {
   3622        NS_WARNING("AutoListElementCreator::AppendListItemElement() failed");
   3623        return createListItemElementResult.unwrapErr();
   3624      }
   3625      CreateElementResult unwrappedResult =
   3626          createListItemElementResult.unwrap();
   3627      unwrappedResult.IgnoreCaretPointSuggestion();
   3628      aState.mListOrListItemElementToPutCaret = unwrappedResult.UnwrapNewNode();
   3629    }
   3630    nsresult rv =
   3631        aHTMLEditor.DeleteNodeWithTransaction(aHandlingDivOrParagraphElement);
   3632    if (NS_FAILED(rv)) {
   3633      NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
   3634      return rv;
   3635    }
   3636 
   3637    // We don't want new inline contents inserted into the new list item element
   3638    // because we want to keep the line break at end of
   3639    // aHandlingDivOrParagraphElement.
   3640    aState.mPreviousListItemElement = nullptr;
   3641 
   3642    return NS_OK;
   3643  }
   3644 
   3645  // If current node is a <div> element, replace it with its children and handle
   3646  // them as same as topmost children in the range.
   3647  AutoContentNodeArray arrayOfContentsInDiv;
   3648  HTMLEditUtils::CollectChildren(aHandlingDivOrParagraphElement,
   3649                                 arrayOfContentsInDiv, 0,
   3650                                 {CollectChildrenOption::CollectListChildren,
   3651                                  CollectChildrenOption::CollectTableChildren});
   3652 
   3653  Result<EditorDOMPoint, nsresult> unwrapDivElementResult =
   3654      aHTMLEditor.RemoveContainerWithTransaction(
   3655          aHandlingDivOrParagraphElement);
   3656  if (MOZ_UNLIKELY(unwrapDivElementResult.isErr())) {
   3657    NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
   3658    return unwrapDivElementResult.unwrapErr();
   3659  }
   3660 
   3661  for (const OwningNonNull<nsIContent>& content : arrayOfContentsInDiv) {
   3662    // MOZ_KnownLive because of bug 1620312
   3663    nsresult rv = HandleChildContent(aHTMLEditor, MOZ_KnownLive(content),
   3664                                     aState, aEditingHost);
   3665    if (NS_FAILED(rv)) {
   3666      NS_WARNING("AutoListElementCreator::HandleChildContent() failed");
   3667      return rv;
   3668    }
   3669  }
   3670 
   3671  // We don't want new inline contents inserted into the new list item element
   3672  // because we want to keep the line break at end of
   3673  // aHandlingDivOrParagraphElement.
   3674  aState.mPreviousListItemElement = nullptr;
   3675 
   3676  return NS_OK;
   3677 }
   3678 
   3679 nsresult HTMLEditor::AutoListElementCreator::CreateAndUpdateCurrentListElement(
   3680    HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToInsert,
   3681    EmptyListItem aEmptyListItem, AutoHandlingState& aState,
   3682    const Element& aEditingHost) const {
   3683  MOZ_ASSERT(aPointToInsert.IsSetAndValid());
   3684 
   3685  aState.mPreviousListItemElement = nullptr;
   3686  RefPtr<Element> newListItemElement;
   3687  auto initializer =
   3688      // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
   3689      [&](HTMLEditor&, Element& aListElement, const EditorDOMPoint&)
   3690          MOZ_CAN_RUN_SCRIPT_BOUNDARY {
   3691            // If the replacing element has `dir` attribute, the new list
   3692            // element should take it to correct its list marker position.
   3693            if (aState.mReplacingBlockElement) {
   3694              nsString dirValue;
   3695              if (aState.mReplacingBlockElement->GetAttr(nsGkAtoms::dir,
   3696                                                         dirValue) &&
   3697                  !dirValue.IsEmpty()) {
   3698                // We don't need to use transaction to set `dir` attribute here
   3699                // because the element will be stored with the `dir` attribute
   3700                // in InsertNodeTransaction.  Therefore, undo should work.
   3701                IgnoredErrorResult ignoredError;
   3702                aListElement.SetAttr(nsGkAtoms::dir, dirValue, ignoredError);
   3703                NS_WARNING_ASSERTION(
   3704                    !ignoredError.Failed(),
   3705                    "Element::SetAttr(nsGkAtoms::dir) failed, but ignored");
   3706              }
   3707            }
   3708            if (aEmptyListItem == EmptyListItem::Create) {
   3709              Result<CreateElementResult, nsresult> createNewListItemResult =
   3710                  AppendListItemElement(aHTMLEditor, aListElement, aState);
   3711              if (MOZ_UNLIKELY(createNewListItemResult.isErr())) {
   3712                NS_WARNING(
   3713                    "HTMLEditor::AppendNewElementToInsertingElement()"
   3714                    " failed");
   3715                return createNewListItemResult.unwrapErr();
   3716              }
   3717              CreateElementResult unwrappedResult =
   3718                  createNewListItemResult.unwrap();
   3719              unwrappedResult.IgnoreCaretPointSuggestion();
   3720              newListItemElement = unwrappedResult.UnwrapNewNode();
   3721            }
   3722            return NS_OK;
   3723          };
   3724  Result<CreateElementResult, nsresult> createNewListElementResult =
   3725      aHTMLEditor.InsertElementWithSplittingAncestorsWithTransaction(
   3726          mListTagName, aPointToInsert, BRElementNextToSplitPoint::Keep,
   3727          aEditingHost, initializer);
   3728  if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
   3729    NS_WARNING(
   3730        nsPrintfCString(
   3731            "HTMLEditor::"
   3732            "InsertElementWithSplittingAncestorsWithTransaction(%s) failed",
   3733            nsAtomCString(&mListTagName).get())
   3734            .get());
   3735    return createNewListElementResult.propagateErr();
   3736  }
   3737  CreateElementResult unwrappedCreateNewListElementResult =
   3738      createNewListElementResult.unwrap();
   3739  unwrappedCreateNewListElementResult.IgnoreCaretPointSuggestion();
   3740 
   3741  MOZ_ASSERT(unwrappedCreateNewListElementResult.GetNewNode());
   3742  aState.mListOrListItemElementToPutCaret =
   3743      newListItemElement ? newListItemElement.get()
   3744                         : unwrappedCreateNewListElementResult.GetNewNode();
   3745  aState.mCurrentListElement =
   3746      unwrappedCreateNewListElementResult.UnwrapNewNode();
   3747  aState.mPreviousListItemElement = std::move(newListItemElement);
   3748  return NS_OK;
   3749 }
   3750 
   3751 // static
   3752 nsresult HTMLEditor::AutoListElementCreator::MaybeCloneAttributesToNewListItem(
   3753    HTMLEditor& aHTMLEditor, Element& aListItemElement,
   3754    AutoHandlingState& aState) {
   3755  if (!aState.mReplacingBlockElement) {
   3756    return NS_OK;
   3757  }
   3758  // If we're replacing a block element, the list items should have attributes
   3759  // of the replacing element. However, we don't want to copy `dir` attribute
   3760  // because it does not affect content in list item element and setting
   3761  // opposite direction from the parent list causes the marker invisible.
   3762  // Therefore, we don't want to take it. Finally, we don't need to use
   3763  // transaction to copy the attributes here because the element will be stored
   3764  // with the attributes in InsertNodeTransaction.  Therefore, undo should work.
   3765  nsresult rv = aHTMLEditor.CopyAttributes(
   3766      WithTransaction::No, aListItemElement,
   3767      MOZ_KnownLive(*aState.mReplacingBlockElement),
   3768      aState.mMaybeCopiedReplacingBlockElementId
   3769          ? HTMLEditor::CopyAllAttributesExceptIdAndDir
   3770          : HTMLEditor::CopyAllAttributesExceptDir);
   3771  aState.mMaybeCopiedReplacingBlockElementId = true;
   3772  if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
   3773    return NS_ERROR_EDITOR_DESTROYED;
   3774  }
   3775  NS_WARNING_ASSERTION(
   3776      NS_SUCCEEDED(rv),
   3777      "HTMLEditor::CopyAttributes(WithTransaction::No) failed");
   3778  return rv;
   3779 }
   3780 
   3781 Result<CreateElementResult, nsresult>
   3782 HTMLEditor::AutoListElementCreator::AppendListItemElement(
   3783    HTMLEditor& aHTMLEditor, const Element& aListElement,
   3784    AutoHandlingState& aState) const {
   3785  const WithTransaction withTransaction = aListElement.IsInComposedDoc()
   3786                                              ? WithTransaction::Yes
   3787                                              : WithTransaction::No;
   3788  Result<CreateElementResult, nsresult> createNewListItemResult =
   3789      aHTMLEditor.CreateAndInsertElement(
   3790          withTransaction, mListItemTagName,
   3791          EditorDOMPoint::AtEndOf(aListElement),
   3792          !aState.mReplacingBlockElement
   3793              ? HTMLEditor::DoNothingForNewElement
   3794              // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
   3795              : [&aState](HTMLEditor& aHTMLEditor, Element& aListItemElement,
   3796                          const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
   3797                  nsresult rv =
   3798                      AutoListElementCreator::MaybeCloneAttributesToNewListItem(
   3799                          aHTMLEditor, aListItemElement, aState);
   3800                  NS_WARNING_ASSERTION(
   3801                      NS_SUCCEEDED(rv),
   3802                      "AutoListElementCreator::"
   3803                      "MaybeCloneAttributesToNewListItem() failed");
   3804                  return rv;
   3805                });
   3806  NS_WARNING_ASSERTION(createNewListItemResult.isOk(),
   3807                       "HTMLEditor::CreateAndInsertElement() failed");
   3808  return createNewListItemResult;
   3809 }
   3810 
   3811 nsresult HTMLEditor::AutoListElementCreator::HandleChildInlineContent(
   3812    HTMLEditor& aHTMLEditor, nsIContent& aHandlingInlineContent,
   3813    AutoHandlingState& aState) const {
   3814  MOZ_ASSERT(HTMLEditUtils::IsInlineContent(
   3815      aHandlingInlineContent, BlockInlineCheck::UseHTMLDefaultStyle));
   3816 
   3817  // If we're currently handling contents of a list item and current node
   3818  // is not a block element, move current node into the list item.
   3819  if (!aState.mPreviousListItemElement) {
   3820    nsresult rv = WrapContentIntoNewListItemElement(
   3821        aHTMLEditor, aHandlingInlineContent, aState);
   3822    NS_WARNING_ASSERTION(
   3823        NS_SUCCEEDED(rv),
   3824        "AutoListElementCreator::WrapContentIntoNewListItemElement() failed");
   3825    return rv;
   3826  }
   3827 
   3828  Result<MoveNodeResult, nsresult> moveInlineElementResult =
   3829      aHTMLEditor.MoveNodeToEndWithTransaction(
   3830          aHandlingInlineContent,
   3831          MOZ_KnownLive(*aState.mPreviousListItemElement));
   3832  if (MOZ_UNLIKELY(moveInlineElementResult.isErr())) {
   3833    NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
   3834    return moveInlineElementResult.propagateErr();
   3835  }
   3836  moveInlineElementResult.inspect().IgnoreCaretPointSuggestion();
   3837  return NS_OK;
   3838 }
   3839 
   3840 nsresult HTMLEditor::AutoListElementCreator::WrapContentIntoNewListItemElement(
   3841    HTMLEditor& aHTMLEditor, nsIContent& aHandlingContent,
   3842    AutoHandlingState& aState) const {
   3843  // If current node is not a paragraph, wrap current node with new list
   3844  // item element and move it into current list element.
   3845  Result<CreateElementResult, nsresult> wrapContentInListItemElementResult =
   3846      aHTMLEditor.InsertContainerWithTransaction(
   3847          aHandlingContent, mListItemTagName,
   3848          !aState.mReplacingBlockElement
   3849              ? HTMLEditor::DoNothingForNewElement
   3850              // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
   3851              : [&aState](HTMLEditor& aHTMLEditor, Element& aListItemElement,
   3852                          const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
   3853                  nsresult rv =
   3854                      AutoListElementCreator::MaybeCloneAttributesToNewListItem(
   3855                          aHTMLEditor, aListItemElement, aState);
   3856                  NS_WARNING_ASSERTION(
   3857                      NS_SUCCEEDED(rv),
   3858                      "AutoListElementCreator::"
   3859                      "MaybeCloneAttributesToNewListItem() failed");
   3860                  return rv;
   3861                });
   3862  if (MOZ_UNLIKELY(wrapContentInListItemElementResult.isErr())) {
   3863    NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed");
   3864    return wrapContentInListItemElementResult.unwrapErr();
   3865  }
   3866  CreateElementResult unwrappedWrapContentInListItemElementResult =
   3867      wrapContentInListItemElementResult.unwrap();
   3868  unwrappedWrapContentInListItemElementResult.IgnoreCaretPointSuggestion();
   3869  MOZ_ASSERT(unwrappedWrapContentInListItemElementResult.GetNewNode());
   3870 
   3871  // MOZ_KnownLive(unwrappedWrapContentInListItemElementResult.GetNewNode()):
   3872  // The result is grabbed by unwrappedWrapContentInListItemElementResult.
   3873  Result<MoveNodeResult, nsresult> moveListItemElementResult =
   3874      aHTMLEditor.MoveNodeToEndWithTransaction(
   3875          MOZ_KnownLive(
   3876              *unwrappedWrapContentInListItemElementResult.GetNewNode()),
   3877          MOZ_KnownLive(*aState.mCurrentListElement));
   3878  if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
   3879    NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
   3880    return moveListItemElementResult.unwrapErr();
   3881  }
   3882  moveListItemElementResult.inspect().IgnoreCaretPointSuggestion();
   3883 
   3884  // If current node is not a block element, new list item should have
   3885  // following inline nodes too.
   3886  if (HTMLEditUtils::IsInlineContent(aHandlingContent,
   3887                                     BlockInlineCheck::UseHTMLDefaultStyle)) {
   3888    aState.mPreviousListItemElement =
   3889        unwrappedWrapContentInListItemElementResult.UnwrapNewNode();
   3890  } else {
   3891    aState.mPreviousListItemElement = nullptr;
   3892  }
   3893 
   3894  // XXX Why don't we set `type` attribute here??
   3895  return NS_OK;
   3896 }
   3897 
   3898 nsresult HTMLEditor::AutoListElementCreator::
   3899    EnsureCollapsedRangeIsInListItemOrListElement(
   3900        Element& aListItemOrListToPutCaret,
   3901        AutoClonedRangeArray& aRanges) const {
   3902  if (!aRanges.IsCollapsed() || aRanges.Ranges().IsEmpty()) {
   3903    return NS_OK;
   3904  }
   3905 
   3906  const auto firstRangeStartPoint =
   3907      aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
   3908  if (MOZ_UNLIKELY(!firstRangeStartPoint.IsSet())) {
   3909    return NS_OK;
   3910  }
   3911  Result<EditorRawDOMPoint, nsresult> pointToPutCaretOrError =
   3912      HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
   3913          EditorRawDOMPoint>(aListItemOrListToPutCaret, firstRangeStartPoint);
   3914  if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   3915    NS_WARNING("HTMLEditUtils::ComputePointToPutCaretInElementIfOutside()");
   3916    return pointToPutCaretOrError.unwrapErr();
   3917  }
   3918  if (pointToPutCaretOrError.inspect().IsSet()) {
   3919    nsresult rv = aRanges.Collapse(pointToPutCaretOrError.inspect());
   3920    if (NS_FAILED(rv)) {
   3921      NS_WARNING("AutoClonedRangeArray::Collapse() failed");
   3922      return rv;
   3923    }
   3924  }
   3925  return NS_OK;
   3926 }
   3927 
   3928 nsresult HTMLEditor::RemoveListAtSelectionAsSubAction(
   3929    const Element& aEditingHost) {
   3930  MOZ_ASSERT(IsEditActionDataAvailable());
   3931 
   3932  {
   3933    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
   3934    if (MOZ_UNLIKELY(result.isErr())) {
   3935      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
   3936      return result.unwrapErr();
   3937    }
   3938    if (result.inspect().Canceled()) {
   3939      return NS_OK;
   3940    }
   3941  }
   3942 
   3943  AutoPlaceholderBatch treatAsOneTransaction(
   3944      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   3945  IgnoredErrorResult error;
   3946  AutoEditSubActionNotifier startToHandleEditSubAction(
   3947      *this, EditSubAction::eRemoveList, nsIEditor::eNext, error);
   3948  if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   3949    return error.StealNSResult();
   3950  }
   3951  NS_WARNING_ASSERTION(
   3952      !error.Failed(),
   3953      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   3954 
   3955  // XXX Why do we do this only when there is only one selection range?
   3956  if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) {
   3957    Result<EditorRawDOMRange, nsresult> extendedRange =
   3958        GetRangeExtendedToHardLineEdgesForBlockEditAction(
   3959            SelectionRef().GetRangeAt(0u), aEditingHost);
   3960    if (MOZ_UNLIKELY(extendedRange.isErr())) {
   3961      NS_WARNING(
   3962          "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
   3963          "failed");
   3964      return extendedRange.unwrapErr();
   3965    }
   3966    // Note that end point may be prior to start point.  So, we
   3967    // cannot use Selection::SetStartAndEndInLimit() here.
   3968    error.SuppressException();
   3969    SelectionRef().SetBaseAndExtentInLimiter(
   3970        extendedRange.inspect().StartRef().ToRawRangeBoundary(),
   3971        extendedRange.inspect().EndRef().ToRawRangeBoundary(), error);
   3972    if (NS_WARN_IF(Destroyed())) {
   3973      return NS_ERROR_EDITOR_DESTROYED;
   3974    }
   3975    if (error.Failed()) {
   3976      NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
   3977      return error.StealNSResult();
   3978    }
   3979  }
   3980 
   3981  AutoSelectionRestorer restoreSelectionLater(this);
   3982 
   3983  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
   3984  {
   3985    // TODO: We don't need AutoTransactionsConserveSelection here in the normal
   3986    //       cases, but removing this may cause the behavior with the legacy
   3987    //       mutation event listeners.  We should try to delete this in a bug.
   3988    AutoTransactionsConserveSelection dontChangeMySelection(*this);
   3989 
   3990    {
   3991      AutoClonedSelectionRangeArray extendedSelectionRanges(SelectionRef());
   3992      extendedSelectionRanges.ExtendRangesToWrapLines(
   3993          EditSubAction::eCreateOrChangeList,
   3994          BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
   3995      Result<EditorDOMPoint, nsresult> splitResult =
   3996          extendedSelectionRanges
   3997              .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
   3998                  *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
   3999      if (MOZ_UNLIKELY(splitResult.isErr())) {
   4000        NS_WARNING(
   4001            "AutoClonedRangeArray::"
   4002            "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
   4003            "failed");
   4004        return splitResult.unwrapErr();
   4005      }
   4006      nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
   4007          *this, arrayOfContents, EditSubAction::eCreateOrChangeList,
   4008          AutoClonedRangeArray::CollectNonEditableNodes::No);
   4009      if (NS_FAILED(rv)) {
   4010        NS_WARNING(
   4011            "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::"
   4012            "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
   4013        return rv;
   4014      }
   4015    }
   4016 
   4017    const Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
   4018        MaybeSplitElementsAtEveryBRElement(arrayOfContents,
   4019                                           EditSubAction::eCreateOrChangeList);
   4020    if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
   4021      NS_WARNING(
   4022          "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
   4023          "eCreateOrChangeList) failed");
   4024      return splitAtBRElementsResult.inspectErr();
   4025    }
   4026  }
   4027 
   4028  // Remove all non-editable nodes.  Leave them be.
   4029  // XXX CollectEditTargetNodes() should return only editable contents when it's
   4030  //     called with CollectNonEditableNodes::No, but checking it here, looks
   4031  //     like just wasting the runtime cost.
   4032  for (int32_t i = arrayOfContents.Length() - 1; i >= 0; i--) {
   4033    const OwningNonNull<nsIContent>& content = arrayOfContents[i];
   4034    if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
   4035      arrayOfContents.RemoveElementAt(i);
   4036    }
   4037  }
   4038 
   4039  // Only act on lists or list items in the array
   4040  for (const OwningNonNull<nsIContent>& content : arrayOfContents) {
   4041    // here's where we actually figure out what to do
   4042    if (HTMLEditUtils::IsListItemElement(*content)) {
   4043      // unlist this listitem
   4044      nsresult rv = LiftUpListItemElement(MOZ_KnownLive(*content->AsElement()),
   4045                                          LiftUpFromAllParentListElements::Yes);
   4046      if (NS_FAILED(rv)) {
   4047        NS_WARNING(
   4048            "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
   4049            ":Yes) failed");
   4050        return rv;
   4051      }
   4052      continue;
   4053    }
   4054    if (HTMLEditUtils::IsListElement(*content)) {
   4055      // node is a list, move list items out
   4056      nsresult rv =
   4057          DestroyListStructureRecursively(MOZ_KnownLive(*content->AsElement()));
   4058      if (NS_FAILED(rv)) {
   4059        NS_WARNING("HTMLEditor::DestroyListStructureRecursively() failed");
   4060        return rv;
   4061      }
   4062      continue;
   4063    }
   4064  }
   4065  return NS_OK;
   4066 }
   4067 
   4068 Result<RefPtr<Element>, nsresult>
   4069 HTMLEditor::FormatBlockContainerWithTransaction(
   4070    AutoClonedSelectionRangeArray& aSelectionRanges,
   4071    const nsStaticAtom& aNewFormatTagName, FormatBlockMode aFormatBlockMode,
   4072    const Element& aEditingHost) {
   4073  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
   4074 
   4075  // XXX Why do we do this only when there is only one selection range?
   4076  if (!aSelectionRanges.IsCollapsed() &&
   4077      aSelectionRanges.Ranges().Length() == 1u) {
   4078    Result<EditorRawDOMRange, nsresult> extendedRange =
   4079        GetRangeExtendedToHardLineEdgesForBlockEditAction(
   4080            aSelectionRanges.FirstRangeRef(), aEditingHost);
   4081    if (MOZ_UNLIKELY(extendedRange.isErr())) {
   4082      NS_WARNING(
   4083          "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
   4084          "failed");
   4085      return extendedRange.propagateErr();
   4086    }
   4087    // Note that end point may be prior to start point.  So, we
   4088    // cannot use AutoClonedRangeArray::SetStartAndEnd() here.
   4089    if (NS_FAILED(aSelectionRanges.SetBaseAndExtent(
   4090            extendedRange.inspect().StartRef(),
   4091            extendedRange.inspect().EndRef()))) {
   4092      NS_WARNING("AutoClonedRangeArray::SetBaseAndExtent() failed");
   4093      return Err(NS_ERROR_FAILURE);
   4094    }
   4095  }
   4096 
   4097  MOZ_ALWAYS_TRUE(aSelectionRanges.SaveAndTrackRanges(*this));
   4098 
   4099  // TODO: We don't need AutoTransactionsConserveSelection here in the normal
   4100  //       cases, but removing this may cause the behavior with the legacy
   4101  //       mutation event listeners.  We should try to delete this in a bug.
   4102  AutoTransactionsConserveSelection dontChangeMySelection(*this);
   4103 
   4104  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
   4105  aSelectionRanges.ExtendRangesToWrapLines(
   4106      aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand
   4107          ? EditSubAction::eFormatBlockForHTMLCommand
   4108          : EditSubAction::eCreateOrRemoveBlock,
   4109      BlockInlineCheck::UseComputedDisplayOutsideStyle, aEditingHost);
   4110  Result<EditorDOMPoint, nsresult> splitResult =
   4111      aSelectionRanges
   4112          .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
   4113              *this, BlockInlineCheck::UseComputedDisplayOutsideStyle,
   4114              aEditingHost);
   4115  if (MOZ_UNLIKELY(splitResult.isErr())) {
   4116    NS_WARNING(
   4117        "AutoClonedRangeArray::"
   4118        "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() failed");
   4119    return splitResult.propagateErr();
   4120  }
   4121  nsresult rv = aSelectionRanges.CollectEditTargetNodes(
   4122      *this, arrayOfContents,
   4123      aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand
   4124          ? EditSubAction::eFormatBlockForHTMLCommand
   4125          : EditSubAction::eCreateOrRemoveBlock,
   4126      AutoClonedRangeArray::CollectNonEditableNodes::Yes);
   4127  if (NS_FAILED(rv)) {
   4128    NS_WARNING(
   4129        "AutoClonedRangeArray::CollectEditTargetNodes(CollectNonEditableNodes::"
   4130        "No) failed");
   4131    return Err(rv);
   4132  }
   4133 
   4134  Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
   4135      MaybeSplitElementsAtEveryBRElement(
   4136          arrayOfContents,
   4137          aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand
   4138              ? EditSubAction::eFormatBlockForHTMLCommand
   4139              : EditSubAction::eCreateOrRemoveBlock);
   4140  if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
   4141    NS_WARNING("HTMLEditor::MaybeSplitElementsAtEveryBRElement() failed");
   4142    return splitAtBRElementsResult.propagateErr();
   4143  }
   4144 
   4145  // If there is no visible and editable nodes in the edit targets, make an
   4146  // empty block.
   4147  // XXX Isn't this odd if there are only non-editable visible nodes?
   4148  if (HTMLEditUtils::IsEmptyOneHardLine(
   4149          arrayOfContents, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
   4150    if (NS_WARN_IF(aSelectionRanges.Ranges().IsEmpty())) {
   4151      return Err(NS_ERROR_FAILURE);
   4152    }
   4153 
   4154    auto pointToInsertBlock =
   4155        aSelectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
   4156    if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand &&
   4157        (&aNewFormatTagName == nsGkAtoms::normal ||
   4158         &aNewFormatTagName == nsGkAtoms::_empty)) {
   4159      if (!pointToInsertBlock.IsInContentNode()) {
   4160        NS_WARNING(
   4161            "HTMLEditor::FormatBlockContainerWithTransaction() couldn't find "
   4162            "block parent because container of the point is not content");
   4163        return Err(NS_ERROR_FAILURE);
   4164      }
   4165      // We are removing blocks (going to "body text")
   4166      const RefPtr<Element> editableBlockElement =
   4167          HTMLEditUtils::GetInclusiveAncestorElement(
   4168              *pointToInsertBlock.ContainerAs<nsIContent>(),
   4169              HTMLEditUtils::ClosestEditableBlockElement,
   4170              BlockInlineCheck::UseComputedDisplayOutsideStyle);
   4171      if (!editableBlockElement) {
   4172        NS_WARNING(
   4173            "HTMLEditor::FormatBlockContainerWithTransaction() couldn't find "
   4174            "block parent");
   4175        return Err(NS_ERROR_FAILURE);
   4176      }
   4177      if (editableBlockElement->IsAnyOfHTMLElements(
   4178              nsGkAtoms::dd, nsGkAtoms::dl, nsGkAtoms::dt) ||
   4179          !HTMLEditUtils::IsFormatElementForParagraphStateCommand(
   4180              *editableBlockElement)) {
   4181        return RefPtr<Element>();
   4182      }
   4183 
   4184      // If the first editable node after selection is a br, consume it.
   4185      // Otherwise it gets pushed into a following block after the split,
   4186      // which is visually bad.
   4187      if (nsCOMPtr<nsIContent> brContent = HTMLEditUtils::GetNextContent(
   4188              pointToInsertBlock, {WalkTreeOption::IgnoreNonEditableNode},
   4189              BlockInlineCheck::UseComputedDisplayOutsideStyle,
   4190              &aEditingHost)) {
   4191        if (brContent && brContent->IsHTMLElement(nsGkAtoms::br)) {
   4192          AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
   4193          nsresult rv = DeleteNodeWithTransaction(*brContent);
   4194          if (NS_FAILED(rv)) {
   4195            NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   4196            return Err(rv);
   4197          }
   4198        }
   4199      }
   4200      // Do the splits!
   4201      Result<SplitNodeResult, nsresult> splitNodeResult =
   4202          SplitNodeDeepWithTransaction(
   4203              *editableBlockElement, pointToInsertBlock,
   4204              SplitAtEdges::eDoNotCreateEmptyContainer);
   4205      if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
   4206        NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
   4207        return splitNodeResult.propagateErr();
   4208      }
   4209      SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
   4210      unwrappedSplitNodeResult.IgnoreCaretPointSuggestion();
   4211      // Put a <br> element at the split point
   4212      Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError =
   4213          InsertLineBreak(
   4214              WithTransaction::Yes, LineBreakType::BRElement,
   4215              unwrappedSplitNodeResult.AtSplitPoint<EditorDOMPoint>());
   4216      if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) {
   4217        NS_WARNING(
   4218            "HTMLEditor::InsertLineBreak(WithTransaction::Yes "
   4219            "LineBreakType::BRElement) failed");
   4220        return insertBRElementResultOrError.propagateErr();
   4221      }
   4222      CreateLineBreakResult insertBRElementResult =
   4223          insertBRElementResultOrError.unwrap();
   4224      MOZ_ASSERT(insertBRElementResult.Handled());
   4225      aSelectionRanges.ClearSavedRanges();
   4226      nsresult rv =
   4227          aSelectionRanges.Collapse(insertBRElementResult.UnwrapCaretPoint());
   4228      if (NS_FAILED(rv)) {
   4229        NS_WARNING("AutoClonedRangeArray::Collapse() failed");
   4230        return Err(rv);
   4231      }
   4232      return RefPtr<Element>();
   4233    }
   4234 
   4235    // We are making a block.  Consume a br, if needed.
   4236    if (nsCOMPtr<nsIContent> maybeBRContent = HTMLEditUtils::GetNextContent(
   4237            pointToInsertBlock,
   4238            {WalkTreeOption::IgnoreNonEditableNode,
   4239             WalkTreeOption::StopAtBlockBoundary},
   4240            BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)) {
   4241      if (maybeBRContent->IsHTMLElement(nsGkAtoms::br)) {
   4242        AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
   4243        nsresult rv = DeleteNodeWithTransaction(*maybeBRContent);
   4244        if (NS_FAILED(rv)) {
   4245          NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   4246          return Err(rv);
   4247        }
   4248        // We don't need to act on this node any more
   4249        arrayOfContents.RemoveElement(maybeBRContent);
   4250      }
   4251    }
   4252    // Make sure we can put a block here.
   4253    Result<CreateElementResult, nsresult> createNewBlockElementResult =
   4254        InsertElementWithSplittingAncestorsWithTransaction(
   4255            aNewFormatTagName, pointToInsertBlock,
   4256            BRElementNextToSplitPoint::Keep, aEditingHost);
   4257    if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) {
   4258      NS_WARNING(
   4259          nsPrintfCString(
   4260              "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
   4261              "%s) failed",
   4262              nsAtomCString(&aNewFormatTagName).get())
   4263              .get());
   4264      return createNewBlockElementResult.propagateErr();
   4265    }
   4266    CreateElementResult unwrappedCreateNewBlockElementResult =
   4267        createNewBlockElementResult.unwrap();
   4268    unwrappedCreateNewBlockElementResult.IgnoreCaretPointSuggestion();
   4269    MOZ_ASSERT(unwrappedCreateNewBlockElementResult.GetNewNode());
   4270 
   4271    // Delete anything that was in the list of nodes
   4272    while (!arrayOfContents.IsEmpty()) {
   4273      OwningNonNull<nsIContent>& content = arrayOfContents[0];
   4274      // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
   4275      // keep it alive.
   4276      nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
   4277      if (NS_FAILED(rv)) {
   4278        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   4279        return Err(rv);
   4280      }
   4281      arrayOfContents.RemoveElementAt(0);
   4282    }
   4283    // Put selection in new block
   4284    aSelectionRanges.ClearSavedRanges();
   4285    nsresult rv = aSelectionRanges.Collapse(EditorRawDOMPoint(
   4286        unwrappedCreateNewBlockElementResult.GetNewNode(), 0u));
   4287    if (NS_FAILED(rv)) {
   4288      NS_WARNING("AutoClonedRangeArray::Collapse() failed");
   4289      return Err(rv);
   4290    }
   4291    return unwrappedCreateNewBlockElementResult.UnwrapNewNode();
   4292  }
   4293 
   4294  if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand) {
   4295    // Okay, now go through all the nodes and make the right kind of blocks, or
   4296    // whatever is appropriate.
   4297    // Note: blockquote is handled a little differently.
   4298    if (&aNewFormatTagName == nsGkAtoms::blockquote) {
   4299      Result<CreateElementResult, nsresult>
   4300          wrapContentsInBlockquoteElementsResult =
   4301              WrapContentsInBlockquoteElementsWithTransaction(arrayOfContents,
   4302                                                              aEditingHost);
   4303      if (MOZ_UNLIKELY(wrapContentsInBlockquoteElementsResult.isErr())) {
   4304        NS_WARNING(
   4305            "HTMLEditor::WrapContentsInBlockquoteElementsWithTransaction() "
   4306            "failed");
   4307        return wrapContentsInBlockquoteElementsResult.propagateErr();
   4308      }
   4309      wrapContentsInBlockquoteElementsResult.inspect()
   4310          .IgnoreCaretPointSuggestion();
   4311      return wrapContentsInBlockquoteElementsResult.unwrap().UnwrapNewNode();
   4312    }
   4313    if (&aNewFormatTagName == nsGkAtoms::normal ||
   4314        &aNewFormatTagName == nsGkAtoms::_empty) {
   4315      Result<EditorDOMPoint, nsresult> removeBlockContainerElementsResult =
   4316          RemoveBlockContainerElementsWithTransaction(
   4317              arrayOfContents, FormatBlockMode::XULParagraphStateCommand,
   4318              BlockInlineCheck::UseComputedDisplayOutsideStyle);
   4319      if (MOZ_UNLIKELY(removeBlockContainerElementsResult.isErr())) {
   4320        NS_WARNING(
   4321            "HTMLEditor::RemoveBlockContainerElementsWithTransaction() failed");
   4322        return removeBlockContainerElementsResult.propagateErr();
   4323      }
   4324      return RefPtr<Element>();
   4325    }
   4326  }
   4327 
   4328  Result<CreateElementResult, nsresult> wrapContentsInBlockElementResult =
   4329      CreateOrChangeFormatContainerElement(arrayOfContents, aNewFormatTagName,
   4330                                           aFormatBlockMode, aEditingHost);
   4331  if (MOZ_UNLIKELY(wrapContentsInBlockElementResult.isErr())) {
   4332    NS_WARNING("HTMLEditor::CreateOrChangeFormatContainerElement() failed");
   4333    return wrapContentsInBlockElementResult.propagateErr();
   4334  }
   4335  wrapContentsInBlockElementResult.inspect().IgnoreCaretPointSuggestion();
   4336  return wrapContentsInBlockElementResult.unwrap().UnwrapNewNode();
   4337 }
   4338 
   4339 Result<EditActionResult, nsresult> HTMLEditor::IndentAsSubAction(
   4340    const Element& aEditingHost) {
   4341  MOZ_ASSERT(IsEditActionDataAvailable());
   4342 
   4343  AutoPlaceholderBatch treatAsOneTransaction(
   4344      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   4345  IgnoredErrorResult ignoredError;
   4346  AutoEditSubActionNotifier startToHandleEditSubAction(
   4347      *this, EditSubAction::eIndent, nsIEditor::eNext, ignoredError);
   4348  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   4349    return Err(ignoredError.StealNSResult());
   4350  }
   4351  NS_WARNING_ASSERTION(
   4352      !ignoredError.Failed(),
   4353      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   4354 
   4355  {
   4356    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
   4357    if (MOZ_UNLIKELY(result.isErr())) {
   4358      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
   4359      return result;
   4360    }
   4361    if (result.inspect().Canceled()) {
   4362      return result;
   4363    }
   4364  }
   4365 
   4366  if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
   4367    NS_WARNING("Some selection containers are not content node, but ignored");
   4368    return EditActionResult::IgnoredResult();
   4369  }
   4370 
   4371  Result<EditActionResult, nsresult> result =
   4372      HandleIndentAtSelection(aEditingHost);
   4373  if (MOZ_UNLIKELY(result.isErr())) {
   4374    NS_WARNING("HTMLEditor::HandleIndentAtSelection() failed");
   4375    return result;
   4376  }
   4377  if (result.inspect().Canceled()) {
   4378    return result;
   4379  }
   4380 
   4381  if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
   4382    NS_WARNING("Mutation event listener might have changed selection");
   4383    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   4384  }
   4385 
   4386  if (!SelectionRef().IsCollapsed()) {
   4387    return result;
   4388  }
   4389 
   4390  // TODO: Investigate when we need to put a `<br>` element after indenting
   4391  //       ranges.  Then, we could stop calling this here, or maybe we need to
   4392  //       do it while moving content nodes.
   4393  const auto caretPosition =
   4394      EditorBase::GetFirstSelectionStartPoint<EditorDOMPoint>();
   4395  Result<CreateLineBreakResult, nsresult> insertPaddingBRElementResultOrError =
   4396      InsertPaddingBRElementIfInEmptyBlock(caretPosition, eNoStrip);
   4397  if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) {
   4398    NS_WARNING(
   4399        "HTMLEditor::InsertPaddingBRElementIfInEmptyBlock(eNoStrip) failed");
   4400    return insertPaddingBRElementResultOrError.propagateErr();
   4401  }
   4402  nsresult rv =
   4403      insertPaddingBRElementResultOrError.unwrap().SuggestCaretPointTo(
   4404          *this, {SuggestCaret::OnlyIfHasSuggestion,
   4405                  SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   4406                  SuggestCaret::AndIgnoreTrivialError});
   4407  if (NS_FAILED(rv)) {
   4408    NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
   4409    return Err(rv);
   4410  }
   4411  NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   4412                       "CaretPoint::SuggestCaretPointTo() failed, but ignored");
   4413  return result;
   4414 }
   4415 
   4416 Result<EditorDOMPoint, nsresult> HTMLEditor::IndentListChildWithTransaction(
   4417    RefPtr<Element>* aSubListElement, const EditorDOMPoint& aPointInListElement,
   4418    nsIContent& aContentMovingToSubList, const Element& aEditingHost) {
   4419  MOZ_ASSERT(aPointInListElement.IsInContentNode());
   4420  MOZ_ASSERT(HTMLEditUtils::IsListElement(
   4421      *aPointInListElement.ContainerAs<nsIContent>()));
   4422  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
   4423 
   4424  // some logic for putting list items into nested lists...
   4425 
   4426  // If aContentMovingToSubList is followed by a sub-list element whose tag is
   4427  // same as the parent list element's tag, we can move it to start of the
   4428  // sub-list.
   4429  if (nsIContent* const nextEditableSibling = HTMLEditUtils::GetNextSibling(
   4430          aContentMovingToSubList, {WalkTreeOption::IgnoreWhiteSpaceOnlyText,
   4431                                    WalkTreeOption::IgnoreNonEditableNode})) {
   4432    if (HTMLEditUtils::IsListElement(*nextEditableSibling) &&
   4433        aPointInListElement.GetContainer()->NodeInfo()->NameAtom() ==
   4434            nextEditableSibling->NodeInfo()->NameAtom() &&
   4435        aPointInListElement.GetContainer()->NodeInfo()->NamespaceID() ==
   4436            nextEditableSibling->NodeInfo()->NamespaceID()) {
   4437      Result<MoveNodeResult, nsresult> moveListElementResult =
   4438          MoveNodeWithTransaction(aContentMovingToSubList,
   4439                                  EditorDOMPoint(nextEditableSibling, 0u));
   4440      if (MOZ_UNLIKELY(moveListElementResult.isErr())) {
   4441        NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
   4442        return moveListElementResult.propagateErr();
   4443      }
   4444      return moveListElementResult.unwrap().UnwrapCaretPoint();
   4445    }
   4446  }
   4447 
   4448  // If aContentMovingToSubList follows a sub-list element whose tag is same
   4449  // as the parent list element's tag, we can move it to end of the sub-list.
   4450  if (const nsCOMPtr<nsIContent> previousEditableSibling =
   4451          HTMLEditUtils::GetPreviousSibling(
   4452              aContentMovingToSubList,
   4453              {WalkTreeOption::IgnoreWhiteSpaceOnlyText,
   4454               WalkTreeOption::IgnoreNonEditableNode})) {
   4455    if (HTMLEditUtils::IsListElement(*previousEditableSibling) &&
   4456        aPointInListElement.GetContainer()->NodeInfo()->NameAtom() ==
   4457            previousEditableSibling->NodeInfo()->NameAtom() &&
   4458        aPointInListElement.GetContainer()->NodeInfo()->NamespaceID() ==
   4459            previousEditableSibling->NodeInfo()->NamespaceID()) {
   4460      Result<MoveNodeResult, nsresult> moveListElementResult =
   4461          MoveNodeToEndWithTransaction(aContentMovingToSubList,
   4462                                       *previousEditableSibling);
   4463      if (MOZ_UNLIKELY(moveListElementResult.isErr())) {
   4464        NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
   4465        return moveListElementResult.propagateErr();
   4466      }
   4467      return moveListElementResult.unwrap().UnwrapCaretPoint();
   4468    }
   4469  }
   4470 
   4471  // If aContentMovingToSubList does not follow aSubListElement, we need
   4472  // to create new sub-list element.
   4473  EditorDOMPoint pointToPutCaret;
   4474  nsIContent* previousEditableSibling =
   4475      *aSubListElement ? HTMLEditUtils::GetPreviousSibling(
   4476                             aContentMovingToSubList,
   4477                             {WalkTreeOption::IgnoreWhiteSpaceOnlyText,
   4478                              WalkTreeOption::IgnoreNonEditableNode})
   4479                       : nullptr;
   4480  if (!*aSubListElement || (previousEditableSibling &&
   4481                            previousEditableSibling != *aSubListElement)) {
   4482    nsAtom* containerName =
   4483        aPointInListElement.GetContainer()->NodeInfo()->NameAtom();
   4484    // Create a new nested list of correct type.
   4485    Result<CreateElementResult, nsresult> createNewListElementResult =
   4486        InsertElementWithSplittingAncestorsWithTransaction(
   4487            MOZ_KnownLive(*containerName), aPointInListElement,
   4488            BRElementNextToSplitPoint::Keep, aEditingHost);
   4489    if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
   4490      NS_WARNING(
   4491          nsPrintfCString(
   4492              "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
   4493              "%s) failed",
   4494              nsAtomCString(containerName).get())
   4495              .get());
   4496      return createNewListElementResult.propagateErr();
   4497    }
   4498    CreateElementResult unwrappedCreateNewListElementResult =
   4499        createNewListElementResult.unwrap();
   4500    MOZ_ASSERT(unwrappedCreateNewListElementResult.GetNewNode());
   4501    pointToPutCaret = unwrappedCreateNewListElementResult.UnwrapCaretPoint();
   4502    *aSubListElement = unwrappedCreateNewListElementResult.UnwrapNewNode();
   4503  }
   4504 
   4505  // Finally, we should move aContentMovingToSubList into aSubListElement.
   4506  const RefPtr<Element> subListElement = *aSubListElement;
   4507  Result<MoveNodeResult, nsresult> moveNodeResult =
   4508      MoveNodeToEndWithTransaction(aContentMovingToSubList, *subListElement);
   4509  if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
   4510    NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
   4511    return moveNodeResult.propagateErr();
   4512  }
   4513  MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
   4514  if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
   4515    pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
   4516  }
   4517  return pointToPutCaret;
   4518 }
   4519 
   4520 Result<EditActionResult, nsresult> HTMLEditor::HandleIndentAtSelection(
   4521    const Element& aEditingHost) {
   4522  MOZ_ASSERT(IsEditActionDataAvailable());
   4523  MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
   4524 
   4525  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
   4526  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   4527    return Err(NS_ERROR_EDITOR_DESTROYED);
   4528  }
   4529  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   4530                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
   4531                       "failed, but ignored");
   4532 
   4533  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
   4534    nsresult rv = EnsureCaretNotAfterInvisibleBRElement(aEditingHost);
   4535    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   4536      return Err(NS_ERROR_EDITOR_DESTROYED);
   4537    }
   4538    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   4539                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
   4540                         "failed, but ignored");
   4541    if (NS_SUCCEEDED(rv)) {
   4542      nsresult rv = PrepareInlineStylesForCaret();
   4543      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   4544        return Err(NS_ERROR_EDITOR_DESTROYED);
   4545      }
   4546      NS_WARNING_ASSERTION(
   4547          NS_SUCCEEDED(rv),
   4548          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
   4549    }
   4550  }
   4551 
   4552  AutoClonedSelectionRangeArray selectionRanges(SelectionRef());
   4553 
   4554  if (MOZ_UNLIKELY(!selectionRanges.IsInContent())) {
   4555    NS_WARNING("Mutation event listener might have changed the selection");
   4556    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   4557  }
   4558 
   4559  if (IsCSSEnabled()) {
   4560    nsresult rv = HandleCSSIndentAroundRanges(selectionRanges, aEditingHost);
   4561    if (NS_FAILED(rv)) {
   4562      NS_WARNING("HTMLEditor::HandleCSSIndentAroundRanges() failed");
   4563      return Err(rv);
   4564    }
   4565  } else {
   4566    nsresult rv = HandleHTMLIndentAroundRanges(selectionRanges, aEditingHost);
   4567    if (NS_FAILED(rv)) {
   4568      NS_WARNING("HTMLEditor::HandleHTMLIndentAroundRanges() failed");
   4569      return Err(rv);
   4570    }
   4571  }
   4572  rv = selectionRanges.ApplyTo(SelectionRef());
   4573  if (MOZ_UNLIKELY(Destroyed())) {
   4574    NS_WARNING(
   4575        "AutoClonedSelectionRangeArray::ApplyTo() caused destroying the "
   4576        "editor");
   4577    return Err(NS_ERROR_EDITOR_DESTROYED);
   4578  }
   4579  if (NS_FAILED(rv)) {
   4580    NS_WARNING("AutoClonedSelectionRangeArray::ApplyTo() failed");
   4581    return Err(rv);
   4582  }
   4583  return EditActionResult::HandledResult();
   4584 }
   4585 
   4586 nsresult HTMLEditor::HandleCSSIndentAroundRanges(
   4587    AutoClonedSelectionRangeArray& aRanges, const Element& aEditingHost) {
   4588  MOZ_ASSERT(IsEditActionDataAvailable());
   4589  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
   4590  MOZ_ASSERT(!aRanges.Ranges().IsEmpty());
   4591  MOZ_ASSERT(aRanges.IsInContent());
   4592 
   4593  if (aRanges.Ranges().IsEmpty()) {
   4594    NS_WARNING("There is no selection range");
   4595    return NS_ERROR_FAILURE;
   4596  }
   4597 
   4598  // XXX Why do we do this only when there is only one selection range?
   4599  if (!aRanges.IsCollapsed() && aRanges.Ranges().Length() == 1u) {
   4600    Result<EditorRawDOMRange, nsresult> extendedRange =
   4601        GetRangeExtendedToHardLineEdgesForBlockEditAction(
   4602            aRanges.FirstRangeRef(), aEditingHost);
   4603    if (MOZ_UNLIKELY(extendedRange.isErr())) {
   4604      NS_WARNING(
   4605          "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
   4606          "failed");
   4607      return extendedRange.unwrapErr();
   4608    }
   4609    // Note that end point may be prior to start point.  So, we
   4610    // cannot use SetStartAndEnd() here.
   4611    nsresult rv = aRanges.SetBaseAndExtent(extendedRange.inspect().StartRef(),
   4612                                           extendedRange.inspect().EndRef());
   4613    if (NS_FAILED(rv)) {
   4614      NS_WARNING("AutoClonedRangeArray::SetBaseAndExtent() failed");
   4615      return rv;
   4616    }
   4617  }
   4618 
   4619  if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(*this))) {
   4620    return NS_ERROR_FAILURE;
   4621  }
   4622 
   4623  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
   4624 
   4625  // short circuit: detect case of collapsed selection inside an <li>.
   4626  // just sublist that <li>.  This prevents bug 97797.
   4627 
   4628  if (aRanges.IsCollapsed()) {
   4629    const auto atCaret = aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
   4630    if (NS_WARN_IF(!atCaret.IsSet())) {
   4631      return NS_ERROR_FAILURE;
   4632    }
   4633    MOZ_ASSERT(atCaret.IsInContentNode());
   4634    Element* const editableBlockElement =
   4635        HTMLEditUtils::GetInclusiveAncestorElement(
   4636            *atCaret.ContainerAs<nsIContent>(),
   4637            HTMLEditUtils::ClosestEditableBlockElement,
   4638            BlockInlineCheck::UseHTMLDefaultStyle);
   4639    if (editableBlockElement &&
   4640        HTMLEditUtils::IsListItemElement(*editableBlockElement)) {
   4641      arrayOfContents.AppendElement(*editableBlockElement);
   4642    }
   4643  }
   4644 
   4645  EditorDOMPoint pointToPutCaret;
   4646  if (arrayOfContents.IsEmpty()) {
   4647    {
   4648      AutoClonedSelectionRangeArray extendedRanges(aRanges);
   4649      extendedRanges.ExtendRangesToWrapLines(
   4650          EditSubAction::eIndent, BlockInlineCheck::UseHTMLDefaultStyle,
   4651          aEditingHost);
   4652      Result<EditorDOMPoint, nsresult> splitResult =
   4653          extendedRanges
   4654              .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
   4655                  *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
   4656      if (MOZ_UNLIKELY(splitResult.isErr())) {
   4657        NS_WARNING(
   4658            "AutoClonedRangeArray::"
   4659            "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
   4660            "failed");
   4661        return splitResult.unwrapErr();
   4662      }
   4663      if (splitResult.inspect().IsSet()) {
   4664        pointToPutCaret = splitResult.unwrap();
   4665      }
   4666      nsresult rv = extendedRanges.CollectEditTargetNodes(
   4667          *this, arrayOfContents, EditSubAction::eIndent,
   4668          AutoClonedRangeArray::CollectNonEditableNodes::Yes);
   4669      if (NS_FAILED(rv)) {
   4670        NS_WARNING(
   4671            "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::"
   4672            "eIndent, CollectNonEditableNodes::Yes) failed");
   4673        return rv;
   4674      }
   4675    }
   4676    Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
   4677        MaybeSplitElementsAtEveryBRElement(arrayOfContents,
   4678                                           EditSubAction::eIndent);
   4679    if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
   4680      NS_WARNING(
   4681          "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
   4682          "eIndent) failed");
   4683      return splitAtBRElementsResult.inspectErr();
   4684    }
   4685    if (splitAtBRElementsResult.inspect().IsSet()) {
   4686      pointToPutCaret = splitAtBRElementsResult.unwrap();
   4687    }
   4688  }
   4689 
   4690  // If there is no visible and editable nodes in the edit targets, make an
   4691  // empty block.
   4692  // XXX Isn't this odd if there are only non-editable visible nodes?
   4693  if (HTMLEditUtils::IsEmptyOneHardLine(
   4694          arrayOfContents, BlockInlineCheck::UseHTMLDefaultStyle)) {
   4695    const EditorDOMPoint pointToInsertDivElement =
   4696        pointToPutCaret.IsSet()
   4697            ? std::move(pointToPutCaret)
   4698            : aRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
   4699    if (NS_WARN_IF(!pointToInsertDivElement.IsSet())) {
   4700      return NS_ERROR_FAILURE;
   4701    }
   4702 
   4703    // make sure we can put a block here
   4704    Result<CreateElementResult, nsresult> createNewDivElementResult =
   4705        InsertElementWithSplittingAncestorsWithTransaction(
   4706            *nsGkAtoms::div, pointToInsertDivElement,
   4707            BRElementNextToSplitPoint::Keep, aEditingHost);
   4708    if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
   4709      NS_WARNING(
   4710          "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
   4711          "nsGkAtoms::div) failed");
   4712      return createNewDivElementResult.unwrapErr();
   4713    }
   4714    CreateElementResult unwrappedCreateNewDivElementResult =
   4715        createNewDivElementResult.unwrap();
   4716    // We'll collapse ranges below, so we don't need to touch the ranges here.
   4717    unwrappedCreateNewDivElementResult.IgnoreCaretPointSuggestion();
   4718    const RefPtr<Element> newDivElement =
   4719        unwrappedCreateNewDivElementResult.UnwrapNewNode();
   4720    MOZ_ASSERT(newDivElement);
   4721    const Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   4722        ChangeMarginStart(*newDivElement, ChangeMargin::Increase, aEditingHost);
   4723    if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   4724      if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() ==
   4725                     NS_ERROR_EDITOR_DESTROYED)) {
   4726        return NS_ERROR_EDITOR_DESTROYED;
   4727      }
   4728      NS_WARNING(
   4729          "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed, but "
   4730          "ignored");
   4731    }
   4732    // delete anything that was in the list of nodes
   4733    // XXX We don't need to remove the nodes from the array for performance.
   4734    for (const OwningNonNull<nsIContent>& content : arrayOfContents) {
   4735      // MOZ_KnownLive(content) due to bug 1622253
   4736      nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(content));
   4737      if (NS_FAILED(rv)) {
   4738        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   4739        return rv;
   4740      }
   4741    }
   4742    aRanges.ClearSavedRanges();
   4743    nsresult rv = aRanges.Collapse(EditorDOMPoint(newDivElement, 0u));
   4744    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   4745                         "AutoClonedRangeArray::Collapse() failed");
   4746    return rv;
   4747  }
   4748 
   4749  RefPtr<Element> latestNewBlockElement;
   4750  auto RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside =
   4751      [&]() -> nsresult {
   4752    MOZ_ASSERT(aRanges.HasSavedRanges());
   4753    aRanges.RestoreFromSavedRanges();
   4754 
   4755    if (!latestNewBlockElement || !aRanges.IsCollapsed() ||
   4756        aRanges.Ranges().IsEmpty()) {
   4757      return NS_OK;
   4758    }
   4759 
   4760    const auto firstRangeStartRawPoint =
   4761        aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
   4762    if (MOZ_UNLIKELY(!firstRangeStartRawPoint.IsSet())) {
   4763      return NS_OK;
   4764    }
   4765    Result<EditorRawDOMPoint, nsresult> pointInNewBlockElementOrError =
   4766        HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
   4767            EditorRawDOMPoint>(*latestNewBlockElement, firstRangeStartRawPoint);
   4768    if (MOZ_UNLIKELY(pointInNewBlockElementOrError.isErr())) {
   4769      NS_WARNING(
   4770          "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, "
   4771          "but ignored");
   4772      return NS_OK;
   4773    }
   4774    if (!pointInNewBlockElementOrError.inspect().IsSet()) {
   4775      return NS_OK;
   4776    }
   4777    return aRanges.Collapse(pointInNewBlockElementOrError.unwrap());
   4778  };
   4779 
   4780  // Ok, now go through all the nodes and put them into sub-list element
   4781  // elements and new <div> elements which have start margin.
   4782  RefPtr<Element> subListElement, divElement;
   4783  for (size_t i = 0; i < arrayOfContents.Length(); i++) {
   4784    const OwningNonNull<nsIContent>& content = arrayOfContents[i];
   4785 
   4786    // Here's where we actually figure out what to do.
   4787    EditorDOMPoint atContent(content);
   4788    if (NS_WARN_IF(!atContent.IsInContentNode())) {
   4789      continue;
   4790    }
   4791 
   4792    // Ignore all non-editable nodes.  Leave them be.
   4793    // XXX We ignore non-editable nodes here, but not so in the above block.
   4794    if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
   4795      continue;
   4796    }
   4797 
   4798    if (HTMLEditUtils::IsListElement(*atContent.ContainerAs<nsIContent>())) {
   4799      const RefPtr<Element> oldSubListElement = subListElement;
   4800      // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
   4801      // keep it alive.
   4802      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   4803          IndentListChildWithTransaction(&subListElement, atContent,
   4804                                         MOZ_KnownLive(content), aEditingHost);
   4805      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   4806        NS_WARNING("HTMLEditor::IndentListChildWithTransaction() failed");
   4807        return pointToPutCaretOrError.unwrapErr();
   4808      }
   4809      if (subListElement != oldSubListElement) {
   4810        // New list element is created, so we should put caret into the new list
   4811        // element.
   4812        latestNewBlockElement = subListElement;
   4813      }
   4814      if (pointToPutCaretOrError.inspect().IsSet()) {
   4815        pointToPutCaret = pointToPutCaretOrError.unwrap();
   4816      }
   4817      continue;
   4818    }
   4819 
   4820    // Not a list item.
   4821 
   4822    if (HTMLEditUtils::IsBlockElement(content,
   4823                                      BlockInlineCheck::UseHTMLDefaultStyle)) {
   4824      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   4825          ChangeMarginStart(MOZ_KnownLive(*content->AsElement()),
   4826                            ChangeMargin::Increase, aEditingHost);
   4827      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   4828        if (MOZ_UNLIKELY(pointToPutCaretOrError.inspectErr() ==
   4829                         NS_ERROR_EDITOR_DESTROYED)) {
   4830          NS_WARNING(
   4831              "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed");
   4832          return NS_ERROR_EDITOR_DESTROYED;
   4833        }
   4834        NS_WARNING(
   4835            "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed, but "
   4836            "ignored");
   4837      } else if (pointToPutCaretOrError.inspect().IsSet()) {
   4838        pointToPutCaret = pointToPutCaretOrError.unwrap();
   4839      }
   4840      divElement = nullptr;
   4841      continue;
   4842    }
   4843 
   4844    if (!divElement) {
   4845      // First, check that our element can contain a div.
   4846      if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(),
   4847                                         *nsGkAtoms::div)) {
   4848        // XXX This is odd, why do we stop indenting remaining content nodes?
   4849        //     Perhaps, `continue` is better.
   4850        nsresult rv =
   4851            RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside();
   4852        NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   4853                             "RestoreSavedRangesAndCollapseInLatestBlockElement"
   4854                             "IfOutside() failed");
   4855        return rv;
   4856      }
   4857 
   4858      Result<CreateElementResult, nsresult> createNewDivElementResult =
   4859          InsertElementWithSplittingAncestorsWithTransaction(
   4860              *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
   4861              aEditingHost);
   4862      if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
   4863        NS_WARNING(
   4864            "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
   4865            "nsGkAtoms::div) failed");
   4866        return createNewDivElementResult.unwrapErr();
   4867      }
   4868      CreateElementResult unwrappedCreateNewDivElementResult =
   4869          createNewDivElementResult.unwrap();
   4870      pointToPutCaret = unwrappedCreateNewDivElementResult.UnwrapCaretPoint();
   4871 
   4872      MOZ_ASSERT(unwrappedCreateNewDivElementResult.GetNewNode());
   4873      divElement = unwrappedCreateNewDivElementResult.UnwrapNewNode();
   4874      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   4875          ChangeMarginStart(*divElement, ChangeMargin::Increase, aEditingHost);
   4876      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   4877        if (MOZ_UNLIKELY(pointToPutCaretOrError.inspectErr() ==
   4878                         NS_ERROR_EDITOR_DESTROYED)) {
   4879          NS_WARNING(
   4880              "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed");
   4881          return NS_ERROR_EDITOR_DESTROYED;
   4882        }
   4883        NS_WARNING(
   4884            "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed, but "
   4885            "ignored");
   4886      } else if (AllowsTransactionsToChangeSelection() &&
   4887                 pointToPutCaretOrError.inspect().IsSet()) {
   4888        pointToPutCaret = pointToPutCaretOrError.unwrap();
   4889      }
   4890 
   4891      latestNewBlockElement = divElement;
   4892    }
   4893 
   4894    const auto IsMovableContentSibling = [&](const nsIContent& aContent) {
   4895      return HTMLEditUtils::IsSimplyEditableNode(aContent) &&
   4896             !HTMLEditUtils::IsBlockElement(
   4897                 aContent, BlockInlineCheck::UseHTMLDefaultStyle);
   4898    };
   4899    MOZ_ASSERT(IsMovableContentSibling(content));
   4900    const OwningNonNull<nsIContent> lastContent = [&]() {
   4901      nsIContent* lastContent = content;
   4902      for (; i + 1 < arrayOfContents.Length(); i++) {
   4903        nsIContent* const nextContent = arrayOfContents[i + 1];
   4904        if (lastContent->GetNextSibling() != nextContent ||
   4905            !IsMovableContentSibling(*nextContent)) {
   4906          break;
   4907        }
   4908        lastContent = nextContent;
   4909      }
   4910      return OwningNonNull<nsIContent>(*lastContent);
   4911    }();
   4912    // Move the content into the <div> which has start margin.
   4913    // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
   4914    // keep it alive.
   4915    Result<MoveNodeResult, nsresult> moveNodeResult =
   4916        MoveSiblingsToEndWithTransaction(MOZ_KnownLive(content), lastContent,
   4917                                         *divElement);
   4918    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
   4919      NS_WARNING("HTMLEditor::MoveSiblingsToEndWithTransaction() failed");
   4920      return moveNodeResult.unwrapErr();
   4921    }
   4922    MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
   4923    if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
   4924      pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
   4925    }
   4926  }
   4927 
   4928  nsresult rv = RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside();
   4929  NS_WARNING_ASSERTION(
   4930      NS_SUCCEEDED(rv),
   4931      "RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside() failed");
   4932  return rv;
   4933 }
   4934 
   4935 nsresult HTMLEditor::HandleHTMLIndentAroundRanges(
   4936    AutoClonedSelectionRangeArray& aRanges, const Element& aEditingHost) {
   4937  MOZ_ASSERT(IsEditActionDataAvailable());
   4938  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
   4939  MOZ_ASSERT(!aRanges.Ranges().IsEmpty());
   4940  MOZ_ASSERT(aRanges.IsInContent());
   4941 
   4942  // XXX Why do we do this only when there is only one range?
   4943  if (!aRanges.IsCollapsed() && aRanges.Ranges().Length() == 1u) {
   4944    Result<EditorRawDOMRange, nsresult> extendedRange =
   4945        GetRangeExtendedToHardLineEdgesForBlockEditAction(
   4946            aRanges.FirstRangeRef(), aEditingHost);
   4947    if (MOZ_UNLIKELY(extendedRange.isErr())) {
   4948      NS_WARNING(
   4949          "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
   4950          "failed");
   4951      return extendedRange.unwrapErr();
   4952    }
   4953    // Note that end point may be prior to start point.  So, we cannot use
   4954    // SetStartAndEnd() here.
   4955    nsresult rv = aRanges.SetBaseAndExtent(extendedRange.inspect().StartRef(),
   4956                                           extendedRange.inspect().EndRef());
   4957    if (NS_FAILED(rv)) {
   4958      NS_WARNING("AutoClonedRangeArray::SetBaseAndExtent() failed");
   4959      return rv;
   4960    }
   4961  }
   4962 
   4963  if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(*this))) {
   4964    return NS_ERROR_FAILURE;
   4965  }
   4966 
   4967  EditorDOMPoint pointToPutCaret;
   4968 
   4969  // convert the selection ranges into "promoted" selection ranges:
   4970  // this basically just expands the range to include the immediate
   4971  // block parent, and then further expands to include any ancestors
   4972  // whose children are all in the range
   4973 
   4974  // use these ranges to construct a list of nodes to act on.
   4975  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
   4976  {
   4977    AutoClonedSelectionRangeArray extendedRanges(aRanges);
   4978    extendedRanges.ExtendRangesToWrapLines(
   4979        EditSubAction::eIndent, BlockInlineCheck::UseHTMLDefaultStyle,
   4980        aEditingHost);
   4981    Result<EditorDOMPoint, nsresult> splitResult =
   4982        extendedRanges
   4983            .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
   4984                *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
   4985    if (MOZ_UNLIKELY(splitResult.isErr())) {
   4986      NS_WARNING(
   4987          "AutoClonedRangeArray::"
   4988          "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
   4989          "failed");
   4990      return splitResult.unwrapErr();
   4991    }
   4992    if (splitResult.inspect().IsSet()) {
   4993      pointToPutCaret = splitResult.unwrap();
   4994    }
   4995    nsresult rv = extendedRanges.CollectEditTargetNodes(
   4996        *this, arrayOfContents, EditSubAction::eIndent,
   4997        AutoClonedRangeArray::CollectNonEditableNodes::Yes);
   4998    if (NS_FAILED(rv)) {
   4999      NS_WARNING(
   5000          "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::eIndent,"
   5001          " CollectNonEditableNodes::Yes) failed");
   5002      return rv;
   5003    }
   5004  }
   5005 
   5006  // FIXME: Split ancestors when we consider to indent the range.
   5007  Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
   5008      MaybeSplitElementsAtEveryBRElement(arrayOfContents,
   5009                                         EditSubAction::eIndent);
   5010  if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
   5011    NS_WARNING(
   5012        "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::eIndent)"
   5013        " failed");
   5014    return splitAtBRElementsResult.inspectErr();
   5015  }
   5016  if (splitAtBRElementsResult.inspect().IsSet()) {
   5017    pointToPutCaret = splitAtBRElementsResult.unwrap();
   5018  }
   5019 
   5020  // If there is no visible and editable nodes in the edit targets, make an
   5021  // empty block.
   5022  // XXX Isn't this odd if there are only non-editable visible nodes?
   5023  if (HTMLEditUtils::IsEmptyOneHardLine(
   5024          arrayOfContents, BlockInlineCheck::UseHTMLDefaultStyle)) {
   5025    const EditorDOMPoint pointToInsertBlockquoteElement =
   5026        pointToPutCaret.IsSet()
   5027            ? std::move(pointToPutCaret)
   5028            : EditorBase::GetFirstSelectionStartPoint<EditorDOMPoint>();
   5029    if (NS_WARN_IF(!pointToInsertBlockquoteElement.IsSet())) {
   5030      return NS_ERROR_FAILURE;
   5031    }
   5032 
   5033    // If there is no element which can have <blockquote>, abort.
   5034    if (NS_WARN_IF(!HTMLEditUtils::GetInsertionPointInInclusiveAncestor(
   5035                        *nsGkAtoms::blockquote, pointToInsertBlockquoteElement,
   5036                        &aEditingHost)
   5037                        .IsSet())) {
   5038      return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
   5039    }
   5040 
   5041    // Make sure we can put a block here.
   5042    // XXX Unfortunately, this calls
   5043    // MaybeSplitAncestorsForInsertWithTransaction() then,
   5044    // HTMLEditUtils::GetInsertionPointInInclusiveAncestor() is called again.
   5045    Result<CreateElementResult, nsresult> createNewBlockquoteElementResult =
   5046        InsertElementWithSplittingAncestorsWithTransaction(
   5047            *nsGkAtoms::blockquote, pointToInsertBlockquoteElement,
   5048            BRElementNextToSplitPoint::Keep, aEditingHost);
   5049    if (MOZ_UNLIKELY(createNewBlockquoteElementResult.isErr())) {
   5050      NS_WARNING(
   5051          "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
   5052          "nsGkAtoms::blockquote) failed");
   5053      return createNewBlockquoteElementResult.unwrapErr();
   5054    }
   5055    CreateElementResult unwrappedCreateNewBlockquoteElementResult =
   5056        createNewBlockquoteElementResult.unwrap();
   5057    unwrappedCreateNewBlockquoteElementResult.IgnoreCaretPointSuggestion();
   5058    RefPtr<Element> newBlockquoteElement =
   5059        unwrappedCreateNewBlockquoteElementResult.UnwrapNewNode();
   5060    MOZ_ASSERT(newBlockquoteElement);
   5061    // delete anything that was in the list of nodes
   5062    // XXX We don't need to remove the nodes from the array for performance.
   5063    for (const OwningNonNull<nsIContent>& content : arrayOfContents) {
   5064      // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
   5065      // keep it alive.
   5066      nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
   5067      if (NS_FAILED(rv)) {
   5068        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   5069        return rv;
   5070      }
   5071    }
   5072    aRanges.ClearSavedRanges();
   5073    nsresult rv = aRanges.Collapse(EditorRawDOMPoint(newBlockquoteElement, 0u));
   5074    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   5075                         "EditorBase::CollapseSelectionToStartOf() failed");
   5076    return rv;
   5077  }
   5078 
   5079  RefPtr<Element> latestNewBlockElement;
   5080  auto RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside =
   5081      [&]() -> nsresult {
   5082    MOZ_ASSERT(aRanges.HasSavedRanges());
   5083    aRanges.RestoreFromSavedRanges();
   5084 
   5085    if (!latestNewBlockElement || !aRanges.IsCollapsed() ||
   5086        aRanges.Ranges().IsEmpty()) {
   5087      return NS_OK;
   5088    }
   5089 
   5090    const auto firstRangeStartRawPoint =
   5091        aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
   5092    if (MOZ_UNLIKELY(!firstRangeStartRawPoint.IsSet())) {
   5093      return NS_OK;
   5094    }
   5095    Result<EditorRawDOMPoint, nsresult> pointInNewBlockElementOrError =
   5096        HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
   5097            EditorRawDOMPoint>(*latestNewBlockElement, firstRangeStartRawPoint);
   5098    if (MOZ_UNLIKELY(pointInNewBlockElementOrError.isErr())) {
   5099      NS_WARNING(
   5100          "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, "
   5101          "but ignored");
   5102      return NS_OK;
   5103    }
   5104    if (!pointInNewBlockElementOrError.inspect().IsSet()) {
   5105      return NS_OK;
   5106    }
   5107    return aRanges.Collapse(pointInNewBlockElementOrError.unwrap());
   5108  };
   5109 
   5110  // Ok, now go through all the nodes and put them in a blockquote,
   5111  // or whatever is appropriate.  Wohoo!
   5112  RefPtr<Element> subListElement, blockquoteElement, indentedListItemElement;
   5113  for (size_t i = 0; i < arrayOfContents.Length(); i++) {
   5114    const OwningNonNull<nsIContent>& content = arrayOfContents[i];
   5115 
   5116    // Here's where we actually figure out what to do.
   5117    EditorDOMPoint atContent(content);
   5118    if (NS_WARN_IF(!atContent.IsInContentNode())) {
   5119      continue;
   5120    }
   5121 
   5122    const auto IsNotHandlableContent = [](const nsIContent& aContent) {
   5123      // Ignore all non-editable nodes.  Leave them be.
   5124      // XXX We ignore non-editable nodes here, but not so in the above
   5125      // block.
   5126      return !EditorUtils::IsEditableContent(aContent, EditorType::HTML) ||
   5127             !HTMLEditUtils::IsRemovableNode(aContent);
   5128    };
   5129 
   5130    const auto IsMovableContentSibling = [&](const nsIContent& aContent) {
   5131      return !IsNotHandlableContent(aContent) &&
   5132             !HTMLEditUtils::IsListItemElement(aContent);
   5133    };
   5134 
   5135    if (IsNotHandlableContent(content)) {
   5136      continue;
   5137    }
   5138 
   5139    // If the content has been moved to different place, ignore it.
   5140    if (!content->IsInclusiveDescendantOf(&aEditingHost)) {
   5141      continue;
   5142    }
   5143 
   5144    if (HTMLEditUtils::IsListElement(*atContent.ContainerAs<nsIContent>())) {
   5145      const RefPtr<Element> oldSubListElement = subListElement;
   5146      // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
   5147      // keep it alive.
   5148      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   5149          IndentListChildWithTransaction(&subListElement, atContent,
   5150                                         MOZ_KnownLive(content), aEditingHost);
   5151      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   5152        NS_WARNING("HTMLEditor::IndentListChildWithTransaction() failed");
   5153        return pointToPutCaretOrError.unwrapErr();
   5154      }
   5155      if (oldSubListElement != subListElement) {
   5156        // New list element is created, so we should put caret into the new list
   5157        // element.
   5158        latestNewBlockElement = subListElement;
   5159      }
   5160      if (pointToPutCaretOrError.inspect().IsSet()) {
   5161        pointToPutCaret = pointToPutCaretOrError.unwrap();
   5162      }
   5163      blockquoteElement = nullptr;
   5164      continue;
   5165    }
   5166 
   5167    // Not a list item, use blockquote?
   5168 
   5169    // if we are inside a list item, we don't want to blockquote, we want
   5170    // to sublist the list item.  We may have several nodes listed in the
   5171    // array of nodes to act on, that are in the same list item.  Since
   5172    // we only want to indent that li once, we must keep track of the most
   5173    // recent indented list item, and not indent it if we find another node
   5174    // to act on that is still inside the same li.
   5175    if (RefPtr<Element> listItem =
   5176            HTMLEditUtils::GetClosestInclusiveAncestorListItemElement(
   5177                content, &aEditingHost)) {
   5178      if (indentedListItemElement == listItem) {
   5179        // already indented this list item
   5180        continue;
   5181      }
   5182      // check to see if subListElement is still appropriate.  Which it is if
   5183      // content is still right after it in the same list.
   5184      nsIContent* previousEditableSibling =
   5185          subListElement
   5186              ? HTMLEditUtils::GetPreviousSibling(
   5187                    *listItem, {WalkTreeOption::IgnoreNonEditableNode})
   5188              : nullptr;
   5189      if (!subListElement || (previousEditableSibling &&
   5190                              previousEditableSibling != subListElement)) {
   5191        EditorDOMPoint atListItem(listItem);
   5192        if (NS_WARN_IF(!listItem)) {
   5193          return NS_ERROR_FAILURE;
   5194        }
   5195        nsAtom* containerName =
   5196            atListItem.GetContainer()->NodeInfo()->NameAtom();
   5197        // Create a new nested list of correct type.
   5198        Result<CreateElementResult, nsresult> createNewListElementResult =
   5199            InsertElementWithSplittingAncestorsWithTransaction(
   5200                MOZ_KnownLive(*containerName), atListItem,
   5201                BRElementNextToSplitPoint::Keep, aEditingHost);
   5202        if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
   5203          NS_WARNING(nsPrintfCString("HTMLEditor::"
   5204                                     "InsertElementWithSplittingAncestorsWithTr"
   5205                                     "ansaction(%s) failed",
   5206                                     nsAtomCString(containerName).get())
   5207                         .get());
   5208          return createNewListElementResult.unwrapErr();
   5209        }
   5210        CreateElementResult unwrappedCreateNewListElementResult =
   5211            createNewListElementResult.unwrap();
   5212        if (unwrappedCreateNewListElementResult.HasCaretPointSuggestion()) {
   5213          pointToPutCaret =
   5214              unwrappedCreateNewListElementResult.UnwrapCaretPoint();
   5215        }
   5216        MOZ_ASSERT(unwrappedCreateNewListElementResult.GetNewNode());
   5217        subListElement = unwrappedCreateNewListElementResult.UnwrapNewNode();
   5218      }
   5219 
   5220      Result<MoveNodeResult, nsresult> moveListItemElementResult =
   5221          MoveNodeToEndWithTransaction(*listItem, *subListElement);
   5222      if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
   5223        NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
   5224        return moveListItemElementResult.unwrapErr();
   5225      }
   5226      MoveNodeResult unwrappedMoveListItemElementResult =
   5227          moveListItemElementResult.unwrap();
   5228      if (unwrappedMoveListItemElementResult.HasCaretPointSuggestion()) {
   5229        pointToPutCaret = unwrappedMoveListItemElementResult.UnwrapCaretPoint();
   5230      }
   5231 
   5232      // Remember the list item element which we indented now for ignoring its
   5233      // children to avoid using <blockquote> in it.
   5234      indentedListItemElement = std::move(listItem);
   5235 
   5236      continue;
   5237    }
   5238 
   5239    // need to make a blockquote to put things in if we haven't already,
   5240    // or if this node doesn't go in blockquote we used earlier.
   5241    // One reason it might not go in prio blockquote is if we are now
   5242    // in a different table cell.
   5243    if (blockquoteElement &&
   5244        HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
   5245            *blockquoteElement) !=
   5246            HTMLEditUtils::GetInclusiveAncestorAnyTableElement(content)) {
   5247      blockquoteElement = nullptr;
   5248    }
   5249 
   5250    if (!blockquoteElement) {
   5251      // First, check that our element can contain a blockquote.
   5252      if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(),
   5253                                         *nsGkAtoms::blockquote)) {
   5254        // XXX This is odd, why do we stop indenting remaining content nodes?
   5255        //     Perhaps, `continue` is better.
   5256        nsresult rv =
   5257            RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside();
   5258        NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   5259                             "RestoreSavedRangesAndCollapseInLatestBlockElement"
   5260                             "IfOutside() failed");
   5261        return rv;
   5262      }
   5263 
   5264      Result<CreateElementResult, nsresult> createNewBlockquoteElementResult =
   5265          InsertElementWithSplittingAncestorsWithTransaction(
   5266              *nsGkAtoms::blockquote, atContent,
   5267              BRElementNextToSplitPoint::Keep, aEditingHost);
   5268      if (MOZ_UNLIKELY(createNewBlockquoteElementResult.isErr())) {
   5269        NS_WARNING(
   5270            "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
   5271            "nsGkAtoms::blockquote) failed");
   5272        return createNewBlockquoteElementResult.unwrapErr();
   5273      }
   5274      CreateElementResult unwrappedCreateNewBlockquoteElementResult =
   5275          createNewBlockquoteElementResult.unwrap();
   5276      if (unwrappedCreateNewBlockquoteElementResult.HasCaretPointSuggestion()) {
   5277        pointToPutCaret =
   5278            unwrappedCreateNewBlockquoteElementResult.UnwrapCaretPoint();
   5279      }
   5280 
   5281      MOZ_ASSERT(unwrappedCreateNewBlockquoteElementResult.GetNewNode());
   5282      blockquoteElement =
   5283          unwrappedCreateNewBlockquoteElementResult.UnwrapNewNode();
   5284      latestNewBlockElement = blockquoteElement;
   5285    }
   5286 
   5287    MOZ_ASSERT(IsMovableContentSibling(content));
   5288    const OwningNonNull<nsIContent> lastContent = [&]() {
   5289      nsIContent* lastContent = content;
   5290      for (; i + 1 < arrayOfContents.Length(); i++) {
   5291        const OwningNonNull<nsIContent>& nextContent = arrayOfContents[i + 1];
   5292        if (lastContent->GetNextSibling() != nextContent ||
   5293            !IsMovableContentSibling(nextContent)) {
   5294          break;
   5295        }
   5296        lastContent = nextContent;
   5297      }
   5298      return OwningNonNull<nsIContent>(*lastContent);
   5299    }();
   5300    // tuck the node into the end of the active blockquote
   5301    // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
   5302    // keep it alive.
   5303    Result<MoveNodeResult, nsresult> moveNodeResult =
   5304        MoveSiblingsToEndWithTransaction(MOZ_KnownLive(content), lastContent,
   5305                                         *blockquoteElement);
   5306    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
   5307      NS_WARNING("HTMLEditor::MoveSiblingsToEndWithTransaction() failed");
   5308      return moveNodeResult.unwrapErr();
   5309    }
   5310    MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
   5311    if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
   5312      pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
   5313    }
   5314    subListElement = nullptr;
   5315  }
   5316 
   5317  nsresult rv = RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside();
   5318  NS_WARNING_ASSERTION(
   5319      NS_SUCCEEDED(rv),
   5320      "RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside() failed");
   5321  return rv;
   5322 }
   5323 
   5324 Result<EditActionResult, nsresult> HTMLEditor::OutdentAsSubAction(
   5325    const Element& aEditingHost) {
   5326  MOZ_ASSERT(IsEditActionDataAvailable());
   5327 
   5328  AutoPlaceholderBatch treatAsOneTransaction(
   5329      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   5330  IgnoredErrorResult ignoredError;
   5331  AutoEditSubActionNotifier startToHandleEditSubAction(
   5332      *this, EditSubAction::eOutdent, nsIEditor::eNext, ignoredError);
   5333  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   5334    return Err(ignoredError.StealNSResult());
   5335  }
   5336  NS_WARNING_ASSERTION(
   5337      !ignoredError.Failed(),
   5338      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   5339 
   5340  {
   5341    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
   5342    if (MOZ_UNLIKELY(result.isErr())) {
   5343      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
   5344      return result;
   5345    }
   5346    if (result.inspect().Canceled()) {
   5347      return result;
   5348    }
   5349  }
   5350 
   5351  if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
   5352    NS_WARNING("Some selection containers are not content node, but ignored");
   5353    return EditActionResult::IgnoredResult();
   5354  }
   5355 
   5356  Result<EditActionResult, nsresult> result =
   5357      HandleOutdentAtSelection(aEditingHost);
   5358  if (MOZ_UNLIKELY(result.isErr())) {
   5359    NS_WARNING("HTMLEditor::HandleOutdentAtSelection() failed");
   5360    return result;
   5361  }
   5362  if (result.inspect().Canceled()) {
   5363    return result;
   5364  }
   5365 
   5366  if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
   5367    NS_WARNING("Mutation event listener might have changed the selection");
   5368    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   5369  }
   5370 
   5371  if (!SelectionRef().IsCollapsed()) {
   5372    return result;
   5373  }
   5374 
   5375  const auto caretPosition =
   5376      EditorBase::GetFirstSelectionStartPoint<EditorDOMPoint>();
   5377  Result<CreateLineBreakResult, nsresult> insertPaddingBRElementResultOrError =
   5378      InsertPaddingBRElementIfInEmptyBlock(caretPosition, eNoStrip);
   5379  if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) {
   5380    NS_WARNING(
   5381        "HTMLEditor::InsertPaddingBRElementIfInEmptyBlock(eNoStrip) failed");
   5382    return insertPaddingBRElementResultOrError.propagateErr();
   5383  }
   5384  nsresult rv =
   5385      insertPaddingBRElementResultOrError.unwrap().SuggestCaretPointTo(
   5386          *this, {SuggestCaret::OnlyIfHasSuggestion,
   5387                  SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   5388                  SuggestCaret::AndIgnoreTrivialError});
   5389  if (NS_FAILED(rv)) {
   5390    NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
   5391    return Err(rv);
   5392  }
   5393  NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   5394                       "CaretPoint::SuggestCaretPointTo() failed, but ignored");
   5395  return result;
   5396 }
   5397 
   5398 Result<EditActionResult, nsresult> HTMLEditor::HandleOutdentAtSelection(
   5399    const Element& aEditingHost) {
   5400  MOZ_ASSERT(IsEditActionDataAvailable());
   5401  MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
   5402 
   5403  // XXX Why do we do this only when there is only one selection range?
   5404  if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) {
   5405    Result<EditorRawDOMRange, nsresult> extendedRange =
   5406        GetRangeExtendedToHardLineEdgesForBlockEditAction(
   5407            SelectionRef().GetRangeAt(0u), aEditingHost);
   5408    if (MOZ_UNLIKELY(extendedRange.isErr())) {
   5409      NS_WARNING(
   5410          "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
   5411          "failed");
   5412      return extendedRange.propagateErr();
   5413    }
   5414    // Note that end point may be prior to start point.  So, we
   5415    // cannot use Selection::SetStartAndEndInLimit() here.
   5416    IgnoredErrorResult error;
   5417    SelectionRef().SetBaseAndExtentInLimiter(
   5418        extendedRange.inspect().StartRef().ToRawRangeBoundary(),
   5419        extendedRange.inspect().EndRef().ToRawRangeBoundary(), error);
   5420    if (NS_WARN_IF(Destroyed())) {
   5421      return Err(NS_ERROR_EDITOR_DESTROYED);
   5422    }
   5423    if (MOZ_UNLIKELY(error.Failed())) {
   5424      NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
   5425      return Err(error.StealNSResult());
   5426    }
   5427  }
   5428 
   5429  // HandleOutdentAtSelectionInternal() creates AutoSelectionRestorer.
   5430  // Therefore, even if it returns NS_OK, the editor might have been destroyed
   5431  // at restoring Selection.
   5432  Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
   5433      HandleOutdentAtSelectionInternal(aEditingHost);
   5434  MOZ_ASSERT_IF(outdentResult.isOk(),
   5435                !outdentResult.inspect().HasCaretPointSuggestion());
   5436  if (NS_WARN_IF(Destroyed())) {
   5437    return Err(NS_ERROR_EDITOR_DESTROYED);
   5438  }
   5439  if (MOZ_UNLIKELY(outdentResult.isErr())) {
   5440    NS_WARNING("HTMLEditor::HandleOutdentAtSelectionInternal() failed");
   5441    return outdentResult.propagateErr();
   5442  }
   5443  SplitRangeOffFromNodeResult unwrappedOutdentResult = outdentResult.unwrap();
   5444 
   5445  // Make sure selection didn't stick to last piece of content in old bq (only
   5446  // a problem for collapsed selections)
   5447  if (!unwrappedOutdentResult.GetLeftContent() &&
   5448      !unwrappedOutdentResult.GetRightContent()) {
   5449    return EditActionResult::HandledResult();
   5450  }
   5451 
   5452  if (!SelectionRef().IsCollapsed()) {
   5453    return EditActionResult::HandledResult();
   5454  }
   5455 
   5456  // Push selection past end of left element of last split indented element.
   5457  if (unwrappedOutdentResult.GetLeftContent()) {
   5458    const nsRange* firstRange = SelectionRef().GetRangeAt(0);
   5459    if (NS_WARN_IF(!firstRange)) {
   5460      return EditActionResult::HandledResult();
   5461    }
   5462    const RangeBoundary& atStartOfSelection = firstRange->StartRef();
   5463    if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
   5464      return Err(NS_ERROR_FAILURE);
   5465    }
   5466    if (atStartOfSelection.GetContainer() ==
   5467            unwrappedOutdentResult.GetLeftContent() ||
   5468        EditorUtils::IsDescendantOf(*atStartOfSelection.GetContainer(),
   5469                                    *unwrappedOutdentResult.GetLeftContent())) {
   5470      // Selection is inside the left node - push it past it.
   5471      EditorRawDOMPoint afterRememberedLeftBQ(
   5472          EditorRawDOMPoint::After(*unwrappedOutdentResult.GetLeftContent()));
   5473      NS_WARNING_ASSERTION(
   5474          afterRememberedLeftBQ.IsSet(),
   5475          "Failed to set after remembered left blockquote element");
   5476      nsresult rv = CollapseSelectionTo(afterRememberedLeftBQ);
   5477      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   5478        return Err(NS_ERROR_EDITOR_DESTROYED);
   5479      }
   5480      NS_WARNING_ASSERTION(
   5481          NS_SUCCEEDED(rv),
   5482          "EditorBase::CollapseSelectionTo() failed, but ignored");
   5483    }
   5484  }
   5485  // And pull selection before beginning of right element of last split
   5486  // indented element.
   5487  if (unwrappedOutdentResult.GetRightContent()) {
   5488    const nsRange* firstRange = SelectionRef().GetRangeAt(0);
   5489    if (NS_WARN_IF(!firstRange)) {
   5490      return EditActionResult::HandledResult();
   5491    }
   5492    const RangeBoundary& atStartOfSelection = firstRange->StartRef();
   5493    if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
   5494      return Err(NS_ERROR_FAILURE);
   5495    }
   5496    if (atStartOfSelection.GetContainer() ==
   5497            unwrappedOutdentResult.GetRightContent() ||
   5498        EditorUtils::IsDescendantOf(
   5499            *atStartOfSelection.GetContainer(),
   5500            *unwrappedOutdentResult.GetRightContent())) {
   5501      // Selection is inside the right element - push it before it.
   5502      EditorRawDOMPoint atRememberedRightBQ(
   5503          unwrappedOutdentResult.GetRightContent());
   5504      nsresult rv = CollapseSelectionTo(atRememberedRightBQ);
   5505      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   5506        return Err(NS_ERROR_EDITOR_DESTROYED);
   5507      }
   5508      NS_WARNING_ASSERTION(
   5509          NS_SUCCEEDED(rv),
   5510          "EditorBase::CollapseSelectionTo() failed, but ignored");
   5511    }
   5512  }
   5513  return EditActionResult::HandledResult();
   5514 }
   5515 
   5516 Result<SplitRangeOffFromNodeResult, nsresult>
   5517 HTMLEditor::HandleOutdentAtSelectionInternal(const Element& aEditingHost) {
   5518  MOZ_ASSERT(IsEditActionDataAvailable());
   5519 
   5520  AutoSelectionRestorer restoreSelectionLater(this);
   5521 
   5522  bool useCSS = IsCSSEnabled();
   5523 
   5524  // Convert the selection ranges into "promoted" selection ranges: this
   5525  // basically just expands the range to include the immediate block parent,
   5526  // and then further expands to include any ancestors whose children are all
   5527  // in the range
   5528  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
   5529  {
   5530    AutoClonedSelectionRangeArray extendedSelectionRanges(SelectionRef());
   5531    extendedSelectionRanges.ExtendRangesToWrapLines(
   5532        EditSubAction::eOutdent, BlockInlineCheck::UseHTMLDefaultStyle,
   5533        aEditingHost);
   5534    nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
   5535        *this, arrayOfContents, EditSubAction::eOutdent,
   5536        AutoClonedRangeArray::CollectNonEditableNodes::Yes);
   5537    if (NS_FAILED(rv)) {
   5538      NS_WARNING(
   5539          "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::"
   5540          "eOutdent, CollectNonEditableNodes::Yes) failed");
   5541      return Err(rv);
   5542    }
   5543    Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
   5544        MaybeSplitElementsAtEveryBRElement(arrayOfContents,
   5545                                           EditSubAction::eOutdent);
   5546    if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
   5547      NS_WARNING(
   5548          "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
   5549          "eOutdent) failed");
   5550      return splitAtBRElementsResult.propagateErr();
   5551    }
   5552    if (AllowsTransactionsToChangeSelection() &&
   5553        splitAtBRElementsResult.inspect().IsSet()) {
   5554      nsresult rv = CollapseSelectionTo(splitAtBRElementsResult.inspect());
   5555      if (NS_FAILED(rv)) {
   5556        NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   5557        return Err(rv);
   5558      }
   5559    }
   5560  }
   5561 
   5562  nsCOMPtr<nsIContent> leftContentOfLastOutdented;
   5563  nsCOMPtr<nsIContent> middleContentOfLastOutdented;
   5564  nsCOMPtr<nsIContent> rightContentOfLastOutdented;
   5565  RefPtr<Element> indentedParentElement;
   5566  nsCOMPtr<nsIContent> firstContentToBeOutdented, lastContentToBeOutdented;
   5567  BlockIndentedWith indentedParentIndentedWith = BlockIndentedWith::HTML;
   5568  for (const OwningNonNull<nsIContent>& content : arrayOfContents) {
   5569    // Here's where we actually figure out what to do
   5570    EditorDOMPoint atContent(content);
   5571    if (NS_WARN_IF(!atContent.IsInContentNode())) {
   5572      continue;
   5573    }
   5574 
   5575    // If it's a `<blockquote>`, remove it to outdent its children.
   5576    if (content->IsHTMLElement(nsGkAtoms::blockquote)) {
   5577      // If we've already found an ancestor block element indented, we need to
   5578      // split it and remove the block element first.
   5579      if (indentedParentElement) {
   5580        NS_WARNING_ASSERTION(indentedParentElement == content,
   5581                             "Indented parent element is not the <blockquote>");
   5582        Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
   5583            OutdentPartOfBlock(*indentedParentElement,
   5584                               *firstContentToBeOutdented,
   5585                               *lastContentToBeOutdented,
   5586                               indentedParentIndentedWith, aEditingHost);
   5587        if (MOZ_UNLIKELY(outdentResult.isErr())) {
   5588          NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
   5589          return outdentResult;
   5590        }
   5591        SplitRangeOffFromNodeResult unwrappedOutdentResult =
   5592            outdentResult.unwrap();
   5593        unwrappedOutdentResult.IgnoreCaretPointSuggestion();
   5594        leftContentOfLastOutdented = unwrappedOutdentResult.UnwrapLeftContent();
   5595        middleContentOfLastOutdented =
   5596            unwrappedOutdentResult.UnwrapMiddleContent();
   5597        rightContentOfLastOutdented =
   5598            unwrappedOutdentResult.UnwrapRightContent();
   5599        indentedParentElement = nullptr;
   5600        firstContentToBeOutdented = nullptr;
   5601        lastContentToBeOutdented = nullptr;
   5602        indentedParentIndentedWith = BlockIndentedWith::HTML;
   5603      }
   5604      Result<EditorDOMPoint, nsresult> unwrapBlockquoteElementResult =
   5605          RemoveBlockContainerWithTransaction(
   5606              MOZ_KnownLive(*content->AsElement()));
   5607      if (MOZ_UNLIKELY(unwrapBlockquoteElementResult.isErr())) {
   5608        NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
   5609        return unwrapBlockquoteElementResult.propagateErr();
   5610      }
   5611      const EditorDOMPoint& pointToPutCaret =
   5612          unwrapBlockquoteElementResult.inspect();
   5613      if (AllowsTransactionsToChangeSelection() && pointToPutCaret.IsSet()) {
   5614        nsresult rv = CollapseSelectionTo(pointToPutCaret);
   5615        if (NS_FAILED(rv)) {
   5616          NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   5617          return Err(rv);
   5618        }
   5619      }
   5620      continue;
   5621    }
   5622 
   5623    // If we're using CSS and the node is a block element, check its start
   5624    // margin whether it's indented with CSS.
   5625    if (useCSS && HTMLEditUtils::IsBlockElement(
   5626                      content, BlockInlineCheck::UseHTMLDefaultStyle)) {
   5627      nsStaticAtom& marginProperty =
   5628          MarginPropertyAtomForIndent(MOZ_KnownLive(content));
   5629      if (NS_WARN_IF(Destroyed())) {
   5630        return Err(NS_ERROR_EDITOR_DESTROYED);
   5631      }
   5632      nsAutoString value;
   5633      DebugOnly<nsresult> rvIgnored =
   5634          CSSEditUtils::GetSpecifiedProperty(content, marginProperty, value);
   5635      if (NS_WARN_IF(Destroyed())) {
   5636        return Err(NS_ERROR_EDITOR_DESTROYED);
   5637      }
   5638      NS_WARNING_ASSERTION(
   5639          NS_SUCCEEDED(rvIgnored),
   5640          "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
   5641      float startMargin = 0;
   5642      RefPtr<nsAtom> unit;
   5643      CSSEditUtils::ParseLength(value, &startMargin, getter_AddRefs(unit));
   5644      // If indented with CSS, we should decrease the start margin.
   5645      if (startMargin > 0) {
   5646        const Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   5647            ChangeMarginStart(MOZ_KnownLive(*content->AsElement()),
   5648                              ChangeMargin::Decrease, aEditingHost);
   5649        if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   5650          if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() ==
   5651                         NS_ERROR_EDITOR_DESTROYED)) {
   5652            return Err(NS_ERROR_EDITOR_DESTROYED);
   5653          }
   5654          NS_WARNING(
   5655              "HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed, "
   5656              "but ignored");
   5657        } else if (AllowsTransactionsToChangeSelection() &&
   5658                   pointToPutCaretOrError.inspect().IsSet()) {
   5659          nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
   5660          if (NS_FAILED(rv)) {
   5661            NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   5662            return Err(rv);
   5663          }
   5664        }
   5665        continue;
   5666      }
   5667    }
   5668 
   5669    // If it's a list item, we should treat as that it "indents" its children.
   5670    if (HTMLEditUtils::IsListItemElement(*content)) {
   5671      // If it is a list item, that means we are not outdenting whole list.
   5672      // XXX I don't understand this sentence...  We may meet parent list
   5673      //     element, no?
   5674      if (indentedParentElement) {
   5675        Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
   5676            OutdentPartOfBlock(*indentedParentElement,
   5677                               *firstContentToBeOutdented,
   5678                               *lastContentToBeOutdented,
   5679                               indentedParentIndentedWith, aEditingHost);
   5680        if (MOZ_UNLIKELY(outdentResult.isErr())) {
   5681          NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
   5682          return outdentResult;
   5683        }
   5684        SplitRangeOffFromNodeResult unwrappedOutdentResult =
   5685            outdentResult.unwrap();
   5686        unwrappedOutdentResult.IgnoreCaretPointSuggestion();
   5687        leftContentOfLastOutdented = unwrappedOutdentResult.UnwrapLeftContent();
   5688        middleContentOfLastOutdented =
   5689            unwrappedOutdentResult.UnwrapMiddleContent();
   5690        rightContentOfLastOutdented =
   5691            unwrappedOutdentResult.UnwrapRightContent();
   5692        indentedParentElement = nullptr;
   5693        firstContentToBeOutdented = nullptr;
   5694        lastContentToBeOutdented = nullptr;
   5695        indentedParentIndentedWith = BlockIndentedWith::HTML;
   5696      }
   5697      // XXX `content` could become different element since
   5698      //     `OutdentPartOfBlock()` may run mutation event listeners.
   5699      nsresult rv = LiftUpListItemElement(MOZ_KnownLive(*content->AsElement()),
   5700                                          LiftUpFromAllParentListElements::No);
   5701      if (NS_FAILED(rv)) {
   5702        NS_WARNING(
   5703            "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
   5704            ":No) failed");
   5705        return Err(rv);
   5706      }
   5707      continue;
   5708    }
   5709 
   5710    // If we've found an ancestor block element which indents its children
   5711    // and the current node is NOT a descendant of it, we should remove it to
   5712    // outdent its children.  Otherwise, i.e., current node is a descendant of
   5713    // it, we meet new node which should be outdented when the indented parent
   5714    // is removed.
   5715    if (indentedParentElement) {
   5716      if (EditorUtils::IsDescendantOf(*content, *indentedParentElement)) {
   5717        // Extend the range to be outdented at removing the
   5718        // indentedParentElement.
   5719        lastContentToBeOutdented = content;
   5720        continue;
   5721      }
   5722      Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
   5723          OutdentPartOfBlock(*indentedParentElement, *firstContentToBeOutdented,
   5724                             *lastContentToBeOutdented,
   5725                             indentedParentIndentedWith, aEditingHost);
   5726      if (MOZ_UNLIKELY(outdentResult.isErr())) {
   5727        NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
   5728        return outdentResult;
   5729      }
   5730      SplitRangeOffFromNodeResult unwrappedOutdentResult =
   5731          outdentResult.unwrap();
   5732      unwrappedOutdentResult.IgnoreCaretPointSuggestion();
   5733      leftContentOfLastOutdented = unwrappedOutdentResult.UnwrapLeftContent();
   5734      middleContentOfLastOutdented =
   5735          unwrappedOutdentResult.UnwrapMiddleContent();
   5736      rightContentOfLastOutdented = unwrappedOutdentResult.UnwrapRightContent();
   5737      indentedParentElement = nullptr;
   5738      firstContentToBeOutdented = nullptr;
   5739      lastContentToBeOutdented = nullptr;
   5740      // curBlockIndentedWith = HTMLEditor::BlockIndentedWith::HTML;
   5741 
   5742      // Then, we need to look for next indentedParentElement.
   5743    }
   5744 
   5745    indentedParentIndentedWith = BlockIndentedWith::HTML;
   5746    for (nsCOMPtr<nsIContent> parentContent = content->GetParent();
   5747         parentContent && !parentContent->IsHTMLElement(nsGkAtoms::body) &&
   5748         parentContent != &aEditingHost &&
   5749         (parentContent->IsHTMLElement(nsGkAtoms::table) ||
   5750          !HTMLEditUtils::IsAnyTableElementExceptColumnElement(*parentContent));
   5751         parentContent = parentContent->GetParent()) {
   5752      if (MOZ_UNLIKELY(!HTMLEditUtils::IsRemovableNode(*parentContent))) {
   5753        continue;
   5754      }
   5755      // If we reach a `<blockquote>` ancestor, it should be split at next
   5756      // time at least for outdenting current node.
   5757      if (parentContent->IsHTMLElement(nsGkAtoms::blockquote)) {
   5758        indentedParentElement = parentContent->AsElement();
   5759        firstContentToBeOutdented = content;
   5760        lastContentToBeOutdented = content;
   5761        break;
   5762      }
   5763 
   5764      if (!useCSS) {
   5765        continue;
   5766      }
   5767 
   5768      nsCOMPtr<nsINode> grandParentNode = parentContent->GetParentNode();
   5769      nsStaticAtom& marginProperty =
   5770          MarginPropertyAtomForIndent(MOZ_KnownLive(content));
   5771      if (NS_WARN_IF(Destroyed())) {
   5772        return Err(NS_ERROR_EDITOR_DESTROYED);
   5773      }
   5774      if (NS_WARN_IF(grandParentNode != parentContent->GetParentNode())) {
   5775        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   5776      }
   5777      nsAutoString value;
   5778      DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetSpecifiedProperty(
   5779          *parentContent, marginProperty, value);
   5780      if (NS_WARN_IF(Destroyed())) {
   5781        return Err(NS_ERROR_EDITOR_DESTROYED);
   5782      }
   5783      NS_WARNING_ASSERTION(
   5784          NS_SUCCEEDED(rvIgnored),
   5785          "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
   5786      // XXX Now, editing host may become different element.  If so, shouldn't
   5787      //     we stop this handling?
   5788      float startMargin;
   5789      RefPtr<nsAtom> unit;
   5790      CSSEditUtils::ParseLength(value, &startMargin, getter_AddRefs(unit));
   5791      // If we reach a block element which indents its children with start
   5792      // margin, we should remove it at next time.
   5793      if (startMargin > 0 && !(HTMLEditUtils::IsListElement(
   5794                                   *atContent.ContainerAs<nsIContent>()) &&
   5795                               HTMLEditUtils::IsListElement(*content))) {
   5796        indentedParentElement = parentContent->AsElement();
   5797        firstContentToBeOutdented = content;
   5798        lastContentToBeOutdented = content;
   5799        indentedParentIndentedWith = BlockIndentedWith::CSS;
   5800        break;
   5801      }
   5802    }
   5803 
   5804    if (indentedParentElement) {
   5805      continue;
   5806    }
   5807 
   5808    // If we don't have any block elements which indents current node and
   5809    // both current node and its parent are list element, remove current
   5810    // node to move all its children to the parent list.
   5811    // XXX This is buggy.  When both lists' item types are different,
   5812    //     we create invalid tree.  E.g., `<ul>` may have `<dd>` as its
   5813    //     list item element.
   5814    if (HTMLEditUtils::IsListElement(*atContent.ContainerAs<nsIContent>())) {
   5815      if (!HTMLEditUtils::IsListElement(*content)) {
   5816        continue;
   5817      }
   5818      // Just unwrap this sublist
   5819      Result<EditorDOMPoint, nsresult> unwrapSubListElementResult =
   5820          RemoveBlockContainerWithTransaction(
   5821              MOZ_KnownLive(*content->AsElement()));
   5822      if (MOZ_UNLIKELY(unwrapSubListElementResult.isErr())) {
   5823        NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
   5824        return unwrapSubListElementResult.propagateErr();
   5825      }
   5826      const EditorDOMPoint& pointToPutCaret =
   5827          unwrapSubListElementResult.inspect();
   5828      if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret.IsSet()) {
   5829        continue;
   5830      }
   5831      nsresult rv = CollapseSelectionTo(pointToPutCaret);
   5832      if (NS_FAILED(rv)) {
   5833        NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   5834        return Err(rv);
   5835      }
   5836      continue;
   5837    }
   5838 
   5839    // If current content is a list element but its parent is not a list
   5840    // element, move children to where it is and remove it from the tree.
   5841    if (HTMLEditUtils::IsListElement(*content)) {
   5842      // XXX If mutation event listener appends new children forever, this
   5843      //     becomes an infinite loop so that we should set limitation from
   5844      //     first child count.
   5845      for (nsCOMPtr<nsIContent> lastChildContent = content->GetLastChild();
   5846           lastChildContent; lastChildContent = content->GetLastChild()) {
   5847        if (HTMLEditUtils::IsListItemElement(*lastChildContent)) {
   5848          nsresult rv = LiftUpListItemElement(
   5849              MOZ_KnownLive(*lastChildContent->AsElement()),
   5850              LiftUpFromAllParentListElements::No);
   5851          if (NS_FAILED(rv)) {
   5852            NS_WARNING(
   5853                "HTMLEditor::LiftUpListItemElement("
   5854                "LiftUpFromAllParentListElements::No) failed");
   5855            return Err(rv);
   5856          }
   5857          continue;
   5858        }
   5859 
   5860        if (HTMLEditUtils::IsListElement(*lastChildContent)) {
   5861          // We have an embedded list, so move it out from under the parent
   5862          // list. Be sure to put it after the parent list because this
   5863          // loop iterates backwards through the parent's list of children.
   5864          EditorDOMPoint afterCurrentList(EditorDOMPoint::After(atContent));
   5865          NS_WARNING_ASSERTION(
   5866              afterCurrentList.IsSet(),
   5867              "Failed to set it to after current list element");
   5868          Result<MoveNodeResult, nsresult> moveListElementResult =
   5869              MoveNodeWithTransaction(*lastChildContent, afterCurrentList);
   5870          if (MOZ_UNLIKELY(moveListElementResult.isErr())) {
   5871            NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
   5872            return moveListElementResult.propagateErr();
   5873          }
   5874          nsresult rv = moveListElementResult.inspect().SuggestCaretPointTo(
   5875              *this, {SuggestCaret::OnlyIfHasSuggestion,
   5876                      SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   5877                      SuggestCaret::AndIgnoreTrivialError});
   5878          if (NS_FAILED(rv)) {
   5879            NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
   5880            return Err(rv);
   5881          }
   5882          NS_WARNING_ASSERTION(
   5883              rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   5884              "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
   5885          continue;
   5886        }
   5887 
   5888        // Delete any non-list items for now
   5889        // XXX Chrome moves it from the list element.  We should follow it.
   5890        nsresult rv = DeleteNodeWithTransaction(*lastChildContent);
   5891        if (NS_FAILED(rv)) {
   5892          NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   5893          return Err(rv);
   5894        }
   5895      }
   5896      // Delete the now-empty list
   5897      Result<EditorDOMPoint, nsresult> unwrapListElementResult =
   5898          RemoveBlockContainerWithTransaction(
   5899              MOZ_KnownLive(*content->AsElement()));
   5900      if (MOZ_UNLIKELY(unwrapListElementResult.isErr())) {
   5901        NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
   5902        return unwrapListElementResult.propagateErr();
   5903      }
   5904      const EditorDOMPoint& pointToPutCaret = unwrapListElementResult.inspect();
   5905      if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret.IsSet()) {
   5906        continue;
   5907      }
   5908      nsresult rv = CollapseSelectionTo(pointToPutCaret);
   5909      if (NS_FAILED(rv)) {
   5910        NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   5911        return Err(rv);
   5912      }
   5913      continue;
   5914    }
   5915 
   5916    if (useCSS) {
   5917      if (RefPtr<Element> element = content->GetAsElementOrParentElement()) {
   5918        const Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   5919            ChangeMarginStart(*element, ChangeMargin::Decrease, aEditingHost);
   5920        if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   5921          if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() ==
   5922                         NS_ERROR_EDITOR_DESTROYED)) {
   5923            return Err(NS_ERROR_EDITOR_DESTROYED);
   5924          }
   5925          NS_WARNING(
   5926              "HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed, "
   5927              "but ignored");
   5928        } else if (AllowsTransactionsToChangeSelection() &&
   5929                   pointToPutCaretOrError.inspect().IsSet()) {
   5930          nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
   5931          if (NS_FAILED(rv)) {
   5932            NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   5933            return Err(rv);
   5934          }
   5935        }
   5936      }
   5937      continue;
   5938    }
   5939  }
   5940 
   5941  if (!indentedParentElement) {
   5942    return SplitRangeOffFromNodeResult(leftContentOfLastOutdented,
   5943                                       middleContentOfLastOutdented,
   5944                                       rightContentOfLastOutdented);
   5945  }
   5946 
   5947  // We have a <blockquote> we haven't finished handling.
   5948  Result<SplitRangeOffFromNodeResult, nsresult> outdentResult =
   5949      OutdentPartOfBlock(*indentedParentElement, *firstContentToBeOutdented,
   5950                         *lastContentToBeOutdented, indentedParentIndentedWith,
   5951                         aEditingHost);
   5952  if (MOZ_UNLIKELY(outdentResult.isErr())) {
   5953    NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
   5954    return outdentResult;
   5955  }
   5956  // We will restore selection soon.  Therefore, callers do not need to restore
   5957  // the selection.
   5958  SplitRangeOffFromNodeResult unwrappedOutdentResult = outdentResult.unwrap();
   5959  unwrappedOutdentResult.ForgetCaretPointSuggestion();
   5960  return unwrappedOutdentResult;
   5961 }
   5962 
   5963 Result<SplitRangeOffFromNodeResult, nsresult>
   5964 HTMLEditor::RemoveBlockContainerElementWithTransactionBetween(
   5965    Element& aBlockContainerElement, nsIContent& aStartOfRange,
   5966    nsIContent& aEndOfRange, BlockInlineCheck aBlockInlineCheck) {
   5967  MOZ_ASSERT(IsEditActionDataAvailable());
   5968 
   5969  EditorDOMPoint pointToPutCaret;
   5970  Result<SplitRangeOffFromNodeResult, nsresult> splitResult =
   5971      SplitRangeOffFromElement(aBlockContainerElement, aStartOfRange,
   5972                               aEndOfRange);
   5973  if (MOZ_UNLIKELY(splitResult.isErr())) {
   5974    if (splitResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
   5975      NS_WARNING("HTMLEditor::SplitRangeOffFromElement() failed");
   5976      return splitResult;
   5977    }
   5978    NS_WARNING(
   5979        "HTMLEditor::SplitRangeOffFromElement() failed, but might be ignored");
   5980    return SplitRangeOffFromNodeResult(nullptr, nullptr, nullptr);
   5981  }
   5982  SplitRangeOffFromNodeResult unwrappedSplitResult = splitResult.unwrap();
   5983  unwrappedSplitResult.MoveCaretPointTo(pointToPutCaret,
   5984                                        {SuggestCaret::OnlyIfHasSuggestion});
   5985 
   5986  // Even if either split aBlockContainerElement or did not split it, we should
   5987  // unwrap the right most element which is split from aBlockContainerElement
   5988  // (or aBlockContainerElement itself if it was not split without errors).
   5989  Element* rightmostElement =
   5990      unwrappedSplitResult.GetRightmostContentAs<Element>();
   5991  MOZ_ASSERT(rightmostElement);
   5992  if (NS_WARN_IF(!rightmostElement)) {
   5993    return Err(NS_ERROR_FAILURE);
   5994  }
   5995 
   5996  {
   5997    // MOZ_KnownLive(rightmostElement) because it's grabbed by
   5998    // unwrappedSplitResult.
   5999    Result<EditorDOMPoint, nsresult> unwrapBlockElementResult =
   6000        RemoveBlockContainerWithTransaction(MOZ_KnownLive(*rightmostElement));
   6001    if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
   6002      NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
   6003      return unwrapBlockElementResult.propagateErr();
   6004    }
   6005    if (unwrapBlockElementResult.inspect().IsSet()) {
   6006      pointToPutCaret = unwrapBlockElementResult.unwrap();
   6007    }
   6008  }
   6009 
   6010  return SplitRangeOffFromNodeResult(
   6011      unwrappedSplitResult.GetLeftContent(), nullptr,
   6012      unwrappedSplitResult.GetRightContent(), std::move(pointToPutCaret));
   6013 }
   6014 
   6015 Result<SplitRangeOffFromNodeResult, nsresult>
   6016 HTMLEditor::SplitRangeOffFromElement(Element& aElementToSplit,
   6017                                     nsIContent& aStartOfMiddleElement,
   6018                                     nsIContent& aEndOfMiddleElement) {
   6019  MOZ_ASSERT(IsEditActionDataAvailable());
   6020 
   6021  // aStartOfMiddleElement and aEndOfMiddleElement must be exclusive
   6022  // descendants of aElementToSplit.
   6023  MOZ_ASSERT(
   6024      EditorUtils::IsDescendantOf(aStartOfMiddleElement, aElementToSplit));
   6025  MOZ_ASSERT(EditorUtils::IsDescendantOf(aEndOfMiddleElement, aElementToSplit));
   6026 
   6027  EditorDOMPoint pointToPutCaret;
   6028  // Split at the start.
   6029  Result<SplitNodeResult, nsresult> splitAtStartResult =
   6030      SplitNodeDeepWithTransaction(aElementToSplit,
   6031                                   EditorDOMPoint(&aStartOfMiddleElement),
   6032                                   SplitAtEdges::eDoNotCreateEmptyContainer);
   6033  if (MOZ_UNLIKELY(splitAtStartResult.isErr())) {
   6034    if (splitAtStartResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
   6035      NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed (at left)");
   6036      return Err(NS_ERROR_EDITOR_DESTROYED);
   6037    }
   6038    NS_WARNING(
   6039        "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
   6040        "eDoNotCreateEmptyContainer) at start of middle element failed");
   6041  } else {
   6042    splitAtStartResult.inspect().CopyCaretPointTo(
   6043        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   6044  }
   6045 
   6046  // Split at after the end
   6047  auto atAfterEnd = EditorDOMPoint::After(aEndOfMiddleElement);
   6048  Element* rightElement =
   6049      splitAtStartResult.isOk() && splitAtStartResult.inspect().DidSplit()
   6050          ? splitAtStartResult.inspect().GetNextContentAs<Element>()
   6051          : &aElementToSplit;
   6052  // MOZ_KnownLive(rightElement) because it's grabbed by splitAtStartResult or
   6053  // aElementToSplit whose lifetime is guaranteed by the caller.
   6054  Result<SplitNodeResult, nsresult> splitAtEndResult =
   6055      SplitNodeDeepWithTransaction(MOZ_KnownLive(*rightElement), atAfterEnd,
   6056                                   SplitAtEdges::eDoNotCreateEmptyContainer);
   6057  if (MOZ_UNLIKELY(splitAtEndResult.isErr())) {
   6058    if (splitAtEndResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
   6059      NS_WARNING(
   6060          "HTMLEditor::SplitNodeDeepWithTransaction() failed (at right)");
   6061      return Err(NS_ERROR_EDITOR_DESTROYED);
   6062    }
   6063    NS_WARNING(
   6064        "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
   6065        "eDoNotCreateEmptyContainer) after end of middle element failed");
   6066  } else {
   6067    splitAtEndResult.inspect().CopyCaretPointTo(
   6068        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   6069  }
   6070 
   6071  if (splitAtStartResult.isOk() && splitAtStartResult.inspect().DidSplit() &&
   6072      splitAtEndResult.isOk() && splitAtEndResult.inspect().DidSplit()) {
   6073    // Note that the middle node can be computed only with the latter split
   6074    // result.
   6075    return SplitRangeOffFromNodeResult(
   6076        splitAtStartResult.inspect().GetPreviousContent(),
   6077        splitAtEndResult.inspect().GetPreviousContent(),
   6078        splitAtEndResult.inspect().GetNextContent(),
   6079        std::move(pointToPutCaret));
   6080  }
   6081  if (splitAtStartResult.isOk() && splitAtStartResult.inspect().DidSplit()) {
   6082    return SplitRangeOffFromNodeResult(
   6083        splitAtStartResult.inspect().GetPreviousContent(),
   6084        splitAtStartResult.inspect().GetNextContent(), nullptr,
   6085        std::move(pointToPutCaret));
   6086  }
   6087  if (splitAtEndResult.isOk() && splitAtEndResult.inspect().DidSplit()) {
   6088    return SplitRangeOffFromNodeResult(
   6089        nullptr, splitAtEndResult.inspect().GetPreviousContent(),
   6090        splitAtEndResult.inspect().GetNextContent(),
   6091        std::move(pointToPutCaret));
   6092  }
   6093  return SplitRangeOffFromNodeResult(nullptr, &aElementToSplit, nullptr,
   6094                                     std::move(pointToPutCaret));
   6095 }
   6096 
   6097 Result<SplitRangeOffFromNodeResult, nsresult> HTMLEditor::OutdentPartOfBlock(
   6098    Element& aBlockElement, nsIContent& aStartOfOutdent,
   6099    nsIContent& aEndOfOutdent, BlockIndentedWith aBlockIndentedWith,
   6100    const Element& aEditingHost) {
   6101  MOZ_ASSERT(IsEditActionDataAvailable());
   6102 
   6103  Result<SplitRangeOffFromNodeResult, nsresult> splitResult =
   6104      SplitRangeOffFromElement(aBlockElement, aStartOfOutdent, aEndOfOutdent);
   6105  if (MOZ_UNLIKELY(splitResult.isErr())) {
   6106    NS_WARNING("HTMLEditor::SplitRangeOffFromElement() failed");
   6107    return splitResult;
   6108  }
   6109 
   6110  SplitRangeOffFromNodeResult unwrappedSplitResult = splitResult.unwrap();
   6111  Element* middleElement = unwrappedSplitResult.GetMiddleContentAs<Element>();
   6112  if (MOZ_UNLIKELY(!middleElement)) {
   6113    NS_WARNING(
   6114        "HTMLEditor::SplitRangeOffFromElement() didn't return middle content");
   6115    unwrappedSplitResult.IgnoreCaretPointSuggestion();
   6116    return Err(NS_ERROR_FAILURE);
   6117  }
   6118  if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*middleElement))) {
   6119    unwrappedSplitResult.IgnoreCaretPointSuggestion();
   6120    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   6121  }
   6122 
   6123  nsresult rv = unwrappedSplitResult.SuggestCaretPointTo(
   6124      *this, {SuggestCaret::OnlyIfHasSuggestion,
   6125              SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   6126              SuggestCaret::AndIgnoreTrivialError});
   6127  if (NS_FAILED(rv)) {
   6128    NS_WARNING("SplitRangeOffFromNodeResult::SuggestCaretPointTo() failed");
   6129    return Err(rv);
   6130  }
   6131  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   6132                       "SplitRangeOffFromNodeResult::SuggestCaretPointTo() "
   6133                       "failed, but ignored");
   6134 
   6135  if (aBlockIndentedWith == BlockIndentedWith::HTML) {
   6136    // MOZ_KnownLive(middleElement) because of grabbed by unwrappedSplitResult.
   6137    Result<EditorDOMPoint, nsresult> unwrapBlockElementResult =
   6138        RemoveBlockContainerWithTransaction(MOZ_KnownLive(*middleElement));
   6139    if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
   6140      NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
   6141      return unwrapBlockElementResult.propagateErr();
   6142    }
   6143    const EditorDOMPoint& pointToPutCaret = unwrapBlockElementResult.inspect();
   6144    if (AllowsTransactionsToChangeSelection() && pointToPutCaret.IsSet()) {
   6145      nsresult rv = CollapseSelectionTo(pointToPutCaret);
   6146      if (NS_FAILED(rv)) {
   6147        NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   6148        return Err(rv);
   6149      }
   6150    }
   6151    return SplitRangeOffFromNodeResult(unwrappedSplitResult.GetLeftContent(),
   6152                                       nullptr,
   6153                                       unwrappedSplitResult.GetRightContent());
   6154  }
   6155 
   6156  // MOZ_KnownLive(middleElement) because of grabbed by unwrappedSplitResult.
   6157  Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = ChangeMarginStart(
   6158      MOZ_KnownLive(*middleElement), ChangeMargin::Decrease, aEditingHost);
   6159  if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   6160    NS_WARNING("HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed");
   6161    return pointToPutCaretOrError.propagateErr();
   6162  }
   6163  if (AllowsTransactionsToChangeSelection() &&
   6164      pointToPutCaretOrError.inspect().IsSet()) {
   6165    nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
   6166    if (NS_FAILED(rv)) {
   6167      NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   6168      return Err(rv);
   6169    }
   6170  }
   6171  return unwrappedSplitResult;
   6172 }
   6173 
   6174 Result<CreateElementResult, nsresult> HTMLEditor::ChangeListElementType(
   6175    Element& aListElement, nsAtom& aNewListTag, nsAtom& aNewListItemTag) {
   6176  MOZ_ASSERT(IsEditActionDataAvailable());
   6177 
   6178  EditorDOMPoint pointToPutCaret;
   6179 
   6180  AutoTArray<OwningNonNull<nsIContent>, 32> listElementChildren;
   6181  HTMLEditUtils::CollectAllChildren(aListElement, listElementChildren);
   6182 
   6183  for (const OwningNonNull<nsIContent>& childContent : listElementChildren) {
   6184    if (!childContent->IsElement()) {
   6185      continue;
   6186    }
   6187    Element& childElement = *childContent->AsElement();
   6188    if (HTMLEditUtils::IsListItemElement(childElement) &&
   6189        !childContent->IsHTMLElement(&aNewListItemTag)) {
   6190      // MOZ_KnownLive(childElement) because its lifetime is guaranteed by
   6191      // listElementChildren.
   6192      Result<CreateElementResult, nsresult>
   6193          replaceWithNewListItemElementResult =
   6194              ReplaceContainerAndCloneAttributesWithTransaction(
   6195                  MOZ_KnownLive(childElement), aNewListItemTag);
   6196      if (MOZ_UNLIKELY(replaceWithNewListItemElementResult.isErr())) {
   6197        NS_WARNING(
   6198            "HTMLEditor::ReplaceContainerAndCloneAttributesWithTransaction() "
   6199            "failed");
   6200        return replaceWithNewListItemElementResult;
   6201      }
   6202      CreateElementResult unwrappedReplaceWithNewListItemElementResult =
   6203          replaceWithNewListItemElementResult.unwrap();
   6204      unwrappedReplaceWithNewListItemElementResult.MoveCaretPointTo(
   6205          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   6206      continue;
   6207    }
   6208    if (HTMLEditUtils::IsListElement(childElement) &&
   6209        !childElement.IsHTMLElement(&aNewListTag)) {
   6210      // XXX List elements shouldn't have other list elements as their
   6211      //     child.  Why do we handle such invalid tree?
   6212      //     -> Maybe, for bug 525888.
   6213      // MOZ_KnownLive(childElement) because its lifetime is guaranteed by
   6214      // listElementChildren.
   6215      Result<CreateElementResult, nsresult> convertListTypeResult =
   6216          ChangeListElementType(MOZ_KnownLive(childElement), aNewListTag,
   6217                                aNewListItemTag);
   6218      if (MOZ_UNLIKELY(convertListTypeResult.isErr())) {
   6219        NS_WARNING("HTMLEditor::ChangeListElementType() failed");
   6220        return convertListTypeResult;
   6221      }
   6222      CreateElementResult unwrappedConvertListTypeResult =
   6223          convertListTypeResult.unwrap();
   6224      unwrappedConvertListTypeResult.MoveCaretPointTo(
   6225          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   6226      continue;
   6227    }
   6228  }
   6229 
   6230  if (aListElement.IsHTMLElement(&aNewListTag)) {
   6231    return CreateElementResult(aListElement, std::move(pointToPutCaret));
   6232  }
   6233 
   6234  // XXX If we replace the list element, shouldn't we create it first and then,
   6235  //     move children into it before inserting the new list element into the
   6236  //     DOM tree? Then, we could reduce the cost of dispatching DOM mutation
   6237  //     events.
   6238  Result<CreateElementResult, nsresult> replaceWithNewListElementResult =
   6239      ReplaceContainerWithTransaction(aListElement, aNewListTag);
   6240  if (MOZ_UNLIKELY(replaceWithNewListElementResult.isErr())) {
   6241    NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
   6242    return replaceWithNewListElementResult;
   6243  }
   6244  CreateElementResult unwrappedReplaceWithNewListElementResult =
   6245      replaceWithNewListElementResult.unwrap();
   6246  unwrappedReplaceWithNewListElementResult.MoveCaretPointTo(
   6247      pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   6248  return CreateElementResult(
   6249      unwrappedReplaceWithNewListElementResult.UnwrapNewNode(),
   6250      std::move(pointToPutCaret));
   6251 }
   6252 
   6253 Result<EditorDOMPoint, nsresult> HTMLEditor::CreateStyleForInsertText(
   6254    const EditorDOMPoint& aPointToInsertText, const Element& aEditingHost) {
   6255  MOZ_ASSERT(IsEditActionDataAvailable());
   6256  MOZ_ASSERT(aPointToInsertText.IsSetAndValid());
   6257  MOZ_ASSERT(mPendingStylesToApplyToNewContent);
   6258 
   6259  const RefPtr<Element> documentRootElement = GetDocument()->GetRootElement();
   6260  if (NS_WARN_IF(!documentRootElement)) {
   6261    return Err(NS_ERROR_FAILURE);
   6262  }
   6263 
   6264  // process clearing any styles first
   6265  UniquePtr<PendingStyle> pendingStyle =
   6266      mPendingStylesToApplyToNewContent->TakeClearingStyle();
   6267 
   6268  EditorDOMPoint pointToPutCaret(aPointToInsertText);
   6269  {
   6270    // Transactions may set selection, but we will set selection if necessary.
   6271    AutoTransactionsConserveSelection dontChangeMySelection(*this);
   6272 
   6273    while (pendingStyle &&
   6274           pointToPutCaret.GetContainer() != documentRootElement) {
   6275      // MOZ_KnownLive because we own pendingStyle which guarantees the lifetime
   6276      // of its members.
   6277      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   6278          ClearStyleAt(pointToPutCaret, pendingStyle->ToInlineStyle(),
   6279                       pendingStyle->GetSpecifiedStyle(), aEditingHost);
   6280      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   6281        NS_WARNING("HTMLEditor::ClearStyleAt() failed");
   6282        return pointToPutCaretOrError;
   6283      }
   6284      pointToPutCaret = pointToPutCaretOrError.unwrap();
   6285      if (NS_WARN_IF(!pointToPutCaret.IsSetAndValid())) {
   6286        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   6287      }
   6288      pendingStyle = mPendingStylesToApplyToNewContent->TakeClearingStyle();
   6289    }
   6290  }
   6291 
   6292  // then process setting any styles
   6293  const int32_t relFontSize =
   6294      mPendingStylesToApplyToNewContent->TakeRelativeFontSize();
   6295  AutoTArray<EditorInlineStyleAndValue, 32> stylesToSet;
   6296  mPendingStylesToApplyToNewContent->TakeAllPreservedStyles(stylesToSet);
   6297  if (stylesToSet.IsEmpty() && !relFontSize) {
   6298    return pointToPutCaret;
   6299  }
   6300 
   6301  // We're in chrome, e.g., the email composer of Thunderbird, and there is
   6302  // relative font size changes, we need to keep using legacy path until we port
   6303  // IncrementOrDecrementFontSizeAsSubAction() to work with
   6304  // AutoInlineStyleSetter.
   6305  if (relFontSize) {
   6306    // we have at least one style to add; make a new text node to insert style
   6307    // nodes above.
   6308    EditorDOMPoint pointToInsertTextNode(pointToPutCaret);
   6309    if (pointToInsertTextNode.IsInTextNode()) {
   6310      // if we are in a text node, split it
   6311      Result<SplitNodeResult, nsresult> splitTextNodeResult =
   6312          SplitNodeDeepWithTransaction(
   6313              MOZ_KnownLive(*pointToInsertTextNode.ContainerAs<Text>()),
   6314              pointToInsertTextNode,
   6315              SplitAtEdges::eAllowToCreateEmptyContainer);
   6316      if (MOZ_UNLIKELY(splitTextNodeResult.isErr())) {
   6317        NS_WARNING(
   6318            "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
   6319            "eAllowToCreateEmptyContainer) failed");
   6320        return splitTextNodeResult.propagateErr();
   6321      }
   6322      SplitNodeResult unwrappedSplitTextNodeResult =
   6323          splitTextNodeResult.unwrap();
   6324      unwrappedSplitTextNodeResult.MoveCaretPointTo(
   6325          pointToPutCaret, *this,
   6326          {SuggestCaret::OnlyIfHasSuggestion,
   6327           SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
   6328      pointToInsertTextNode =
   6329          unwrappedSplitTextNodeResult.AtSplitPoint<EditorDOMPoint>();
   6330    }
   6331    if (!pointToInsertTextNode.IsInContentNode() ||
   6332        !HTMLEditUtils::IsContainerNode(
   6333            *pointToInsertTextNode.ContainerAs<nsIContent>())) {
   6334      return pointToPutCaret;
   6335    }
   6336    RefPtr<Text> newEmptyTextNode = CreateTextNode(u""_ns);
   6337    if (!newEmptyTextNode) {
   6338      NS_WARNING("EditorBase::CreateTextNode() failed");
   6339      return Err(NS_ERROR_FAILURE);
   6340    }
   6341    Result<CreateTextResult, nsresult> insertNewTextNodeResult =
   6342        InsertNodeWithTransaction<Text>(*newEmptyTextNode,
   6343                                        pointToInsertTextNode);
   6344    if (MOZ_UNLIKELY(insertNewTextNodeResult.isErr())) {
   6345      NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
   6346      return insertNewTextNodeResult.propagateErr();
   6347    }
   6348    insertNewTextNodeResult.inspect().IgnoreCaretPointSuggestion();
   6349    pointToPutCaret.Set(newEmptyTextNode, 0u);
   6350 
   6351    // FIXME: If the stylesToSet have background-color style, it may
   6352    // be applied shorter because outer <span> element height is not
   6353    // computed with inner element's height.
   6354    HTMLEditor::FontSize incrementOrDecrement =
   6355        relFontSize > 0 ? HTMLEditor::FontSize::incr
   6356                        : HTMLEditor::FontSize::decr;
   6357    for ([[maybe_unused]] uint32_t j : IntegerRange(Abs(relFontSize))) {
   6358      Result<CreateElementResult, nsresult> wrapTextInBigOrSmallElementResult =
   6359          SetFontSizeOnTextNode(*newEmptyTextNode, 0, UINT32_MAX,
   6360                                incrementOrDecrement);
   6361      if (MOZ_UNLIKELY(wrapTextInBigOrSmallElementResult.isErr())) {
   6362        NS_WARNING("HTMLEditor::SetFontSizeOnTextNode() failed");
   6363        return wrapTextInBigOrSmallElementResult.propagateErr();
   6364      }
   6365      // We don't need to update here because we'll suggest caret position
   6366      // which is computed above.
   6367      MOZ_ASSERT(pointToPutCaret.IsSet());
   6368      wrapTextInBigOrSmallElementResult.inspect().IgnoreCaretPointSuggestion();
   6369    }
   6370 
   6371    for (const EditorInlineStyleAndValue& styleToSet : stylesToSet) {
   6372      AutoInlineStyleSetter inlineStyleSetter(styleToSet);
   6373      // MOZ_KnownLive(...ContainerAs<nsIContent>()) because pointToPutCaret
   6374      // grabs the result.
   6375      Result<CaretPoint, nsresult> setStyleResult =
   6376          inlineStyleSetter.ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
   6377              *this, MOZ_KnownLive(*pointToPutCaret.ContainerAs<nsIContent>()));
   6378      if (MOZ_UNLIKELY(setStyleResult.isErr())) {
   6379        NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
   6380        return setStyleResult.propagateErr();
   6381      }
   6382      // We don't need to update here because we'll suggest caret position which
   6383      // is computed above.
   6384      MOZ_ASSERT(pointToPutCaret.IsSet());
   6385      setStyleResult.unwrap().IgnoreCaretPointSuggestion();
   6386    }
   6387    return pointToPutCaret;
   6388  }
   6389 
   6390  // If we have preserved commands except relative font style changes, we can
   6391  // use inline style setting code which reuse ancestors better.
   6392  AutoClonedRangeArray ranges(pointToPutCaret);
   6393  if (MOZ_UNLIKELY(ranges.Ranges().IsEmpty())) {
   6394    NS_WARNING("AutoClonedRangeArray::AutoClonedRangeArray() failed");
   6395    return Err(NS_ERROR_FAILURE);
   6396  }
   6397  nsresult rv = SetInlinePropertiesAroundRanges(ranges, stylesToSet);
   6398  if (NS_FAILED(rv)) {
   6399    NS_WARNING("HTMLEditor::SetInlinePropertiesAroundRanges() failed");
   6400    return Err(rv);
   6401  }
   6402  if (NS_WARN_IF(ranges.Ranges().IsEmpty())) {
   6403    return Err(NS_ERROR_FAILURE);
   6404  }
   6405  // Now `ranges` selects new styled contents and the range may not be
   6406  // collapsed.  We should use the deepest editable start point of the range
   6407  // to insert text.
   6408  nsINode* container = ranges.FirstRangeRef()->GetStartContainer();
   6409  if (MOZ_UNLIKELY(!container->IsContent())) {
   6410    container = ranges.FirstRangeRef()->GetChildAtStartOffset();
   6411    if (MOZ_UNLIKELY(!container)) {
   6412      NS_WARNING("How did we get lost insertion point?");
   6413      return Err(NS_ERROR_FAILURE);
   6414    }
   6415  }
   6416  pointToPutCaret =
   6417      HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
   6418          *container->AsContent(), {});
   6419  if (NS_WARN_IF(!pointToPutCaret.IsSet())) {
   6420    return Err(NS_ERROR_FAILURE);
   6421  }
   6422  return pointToPutCaret;
   6423 }
   6424 
   6425 Result<EditActionResult, nsresult> HTMLEditor::AlignAsSubAction(
   6426    const nsAString& aAlignType, const Element& aEditingHost) {
   6427  MOZ_ASSERT(IsEditActionDataAvailable());
   6428 
   6429  AutoPlaceholderBatch treatAsOneTransaction(
   6430      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
   6431  IgnoredErrorResult ignoredError;
   6432  AutoEditSubActionNotifier startToHandleEditSubAction(
   6433      *this, EditSubAction::eSetOrClearAlignment, nsIEditor::eNext,
   6434      ignoredError);
   6435  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
   6436    return Err(ignoredError.StealNSResult());
   6437  }
   6438  NS_WARNING_ASSERTION(
   6439      !ignoredError.Failed(),
   6440      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
   6441 
   6442  {
   6443    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
   6444    if (MOZ_UNLIKELY(result.isErr())) {
   6445      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
   6446      return result;
   6447    }
   6448    if (result.inspect().Canceled()) {
   6449      return result;
   6450    }
   6451  }
   6452 
   6453  if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
   6454    NS_WARNING("Some selection containers are not content node, but ignored");
   6455    return EditActionResult::IgnoredResult();
   6456  }
   6457 
   6458  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
   6459  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   6460    return Err(NS_ERROR_EDITOR_DESTROYED);
   6461  }
   6462  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   6463                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
   6464                       "failed, but ignored");
   6465 
   6466  if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
   6467    NS_WARNING("Mutation event listener might have changed the selection");
   6468    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   6469  }
   6470 
   6471  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
   6472    nsresult rv = EnsureCaretNotAfterInvisibleBRElement(aEditingHost);
   6473    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   6474      return Err(NS_ERROR_EDITOR_DESTROYED);
   6475    }
   6476    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   6477                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
   6478                         "failed, but ignored");
   6479    if (NS_SUCCEEDED(rv)) {
   6480      nsresult rv = PrepareInlineStylesForCaret();
   6481      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   6482        return Err(NS_ERROR_EDITOR_DESTROYED);
   6483      }
   6484      NS_WARNING_ASSERTION(
   6485          NS_SUCCEEDED(rv),
   6486          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
   6487    }
   6488  }
   6489 
   6490  AutoClonedSelectionRangeArray selectionRanges(SelectionRef());
   6491 
   6492  // XXX Why do we do this only when there is only one selection range?
   6493  if (!selectionRanges.IsCollapsed() &&
   6494      selectionRanges.Ranges().Length() == 1u) {
   6495    Result<EditorRawDOMRange, nsresult> extendedRange =
   6496        GetRangeExtendedToHardLineEdgesForBlockEditAction(
   6497            selectionRanges.FirstRangeRef(), aEditingHost);
   6498    if (MOZ_UNLIKELY(extendedRange.isErr())) {
   6499      NS_WARNING(
   6500          "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
   6501          "failed");
   6502      return extendedRange.propagateErr();
   6503    }
   6504    // Note that end point may be prior to start point.  So, we
   6505    // cannot use etStartAndEnd() here.
   6506    nsresult rv = selectionRanges.SetBaseAndExtent(
   6507        extendedRange.inspect().StartRef(), extendedRange.inspect().EndRef());
   6508    if (NS_FAILED(rv)) {
   6509      NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
   6510      return Err(rv);
   6511    }
   6512  }
   6513 
   6514  rv = AlignContentsAtRanges(selectionRanges, aAlignType, aEditingHost);
   6515  if (NS_FAILED(rv)) {
   6516    NS_WARNING("HTMLEditor::AlignContentsAtSelection() failed");
   6517    return Err(rv);
   6518  }
   6519 
   6520  if (selectionRanges.IsCollapsed()) {
   6521    // FIXME: If we get rid of the legacy mutation events, we should be able to
   6522    // just insert a line break without empty check.
   6523    Result<CreateLineBreakResult, nsresult>
   6524        insertPaddingBRElementResultOrError =
   6525            InsertPaddingBRElementIfInEmptyBlock(
   6526                selectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>(),
   6527                eNoStrip);
   6528    if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) {
   6529      NS_WARNING(
   6530          "HTMLEditor::InsertPaddingBRElementIfInEmptyBlock(eNoStrip) failed");
   6531      return insertPaddingBRElementResultOrError.propagateErr();
   6532    }
   6533    EditorDOMPoint pointToPutCaret;
   6534    insertPaddingBRElementResultOrError.unwrap().MoveCaretPointTo(
   6535        pointToPutCaret, *this,
   6536        {SuggestCaret::OnlyIfHasSuggestion,
   6537         SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
   6538    if (pointToPutCaret.IsSet()) {
   6539      nsresult rv = selectionRanges.Collapse(pointToPutCaret);
   6540      if (NS_FAILED(rv)) {
   6541        NS_WARNING("AutoClonedRangeArray::Collapse() failed");
   6542        return Err(rv);
   6543      }
   6544    }
   6545  }
   6546 
   6547  rv = selectionRanges.ApplyTo(SelectionRef());
   6548  if (NS_FAILED(rv)) {
   6549    NS_WARNING("AutoClonedRangeArray::ApplyTo() failed");
   6550    return Err(rv);
   6551  }
   6552 
   6553  if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) {
   6554    NS_WARNING("Mutation event listener might have changed the selection");
   6555    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   6556  }
   6557 
   6558  return EditActionResult::HandledResult();
   6559 }
   6560 
   6561 nsresult HTMLEditor::AlignContentsAtRanges(
   6562    AutoClonedSelectionRangeArray& aRanges, const nsAString& aAlignType,
   6563    const Element& aEditingHost) {
   6564  MOZ_ASSERT(IsEditActionDataAvailable());
   6565  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
   6566  MOZ_ASSERT(aRanges.IsInContent());
   6567 
   6568  if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(*this))) {
   6569    return NS_ERROR_FAILURE;
   6570  }
   6571 
   6572  EditorDOMPoint pointToPutCaret;
   6573 
   6574  // Convert the selection ranges into "promoted" selection ranges: This
   6575  // basically just expands the range to include the immediate block parent,
   6576  // and then further expands to include any ancestors whose children are all
   6577  // in the range
   6578  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
   6579  {
   6580    AutoClonedSelectionRangeArray extendedRanges(aRanges);
   6581    extendedRanges.ExtendRangesToWrapLines(
   6582        EditSubAction::eSetOrClearAlignment,
   6583        BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
   6584    Result<EditorDOMPoint, nsresult> splitResult =
   6585        extendedRanges
   6586            .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
   6587                *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
   6588    if (MOZ_UNLIKELY(splitResult.isErr())) {
   6589      NS_WARNING(
   6590          "AutoClonedRangeArray::"
   6591          "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
   6592          "failed");
   6593      return splitResult.unwrapErr();
   6594    }
   6595    if (splitResult.inspect().IsSet()) {
   6596      pointToPutCaret = splitResult.unwrap();
   6597    }
   6598    nsresult rv = extendedRanges.CollectEditTargetNodes(
   6599        *this, arrayOfContents, EditSubAction::eSetOrClearAlignment,
   6600        AutoClonedRangeArray::CollectNonEditableNodes::Yes);
   6601    if (NS_FAILED(rv)) {
   6602      NS_WARNING(
   6603          "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::"
   6604          "eSetOrClearAlignment, CollectNonEditableNodes::Yes) failed");
   6605      return rv;
   6606    }
   6607  }
   6608 
   6609  Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
   6610      MaybeSplitElementsAtEveryBRElement(arrayOfContents,
   6611                                         EditSubAction::eSetOrClearAlignment);
   6612  if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
   6613    NS_WARNING(
   6614        "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
   6615        "eSetOrClearAlignment) failed");
   6616    return splitAtBRElementsResult.inspectErr();
   6617  }
   6618  if (splitAtBRElementsResult.inspect().IsSet()) {
   6619    pointToPutCaret = splitAtBRElementsResult.unwrap();
   6620  }
   6621 
   6622  // If we don't have any nodes, or we have only a single br, then we are
   6623  // creating an empty alignment div.  We have to do some different things for
   6624  // these.
   6625  bool createEmptyDivElement = arrayOfContents.IsEmpty();
   6626  if (arrayOfContents.Length() == 1) {
   6627    const OwningNonNull<nsIContent>& content = arrayOfContents[0];
   6628 
   6629    if (HTMLEditUtils::IsAlignAttrSupported(content) &&
   6630        HTMLEditUtils::IsBlockElement(content,
   6631                                      BlockInlineCheck::UseHTMLDefaultStyle)) {
   6632      // The node is a table element, an hr, a paragraph, a div or a section
   6633      // header; in HTML 4, it can directly carry the ALIGN attribute and we
   6634      // don't need to make a div! If we are in CSS mode, all the work is done
   6635      // in SetBlockElementAlign().
   6636      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   6637          SetBlockElementAlign(MOZ_KnownLive(*content->AsElement()), aAlignType,
   6638                               EditTarget::OnlyDescendantsExceptTable);
   6639      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   6640        NS_WARNING("HTMLEditor::SetBlockElementAlign() failed");
   6641        return pointToPutCaretOrError.unwrapErr();
   6642      }
   6643      if (pointToPutCaretOrError.inspect().IsSet()) {
   6644        pointToPutCaret = pointToPutCaretOrError.unwrap();
   6645      }
   6646    }
   6647 
   6648    if (content->IsHTMLElement(nsGkAtoms::br)) {
   6649      // The special case createEmptyDivElement code (below) that consumes
   6650      // `<br>` elements can cause tables to split if the start node of the
   6651      // selection is not in a table cell or caption, for example parent is a
   6652      // `<tr>`.  Avoid this unnecessary splitting if possible by leaving
   6653      // createEmptyDivElement false so that we fall through to the normal case
   6654      // alignment code.
   6655      //
   6656      // XXX: It seems a little error prone for the createEmptyDivElement
   6657      //      special case code to assume that the start node of the selection
   6658      //      is the parent of the single node in the arrayOfContents, as the
   6659      //      paragraph above points out. Do we rely on the selection start
   6660      //      node because of the fact that arrayOfContents can be empty?  We
   6661      //      should probably revisit this issue. - kin
   6662 
   6663      const EditorDOMPoint firstRangeStartPoint =
   6664          pointToPutCaret.IsSet()
   6665              ? pointToPutCaret
   6666              : aRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
   6667      if (NS_WARN_IF(!firstRangeStartPoint.IsInContentNode())) {
   6668        return NS_ERROR_FAILURE;
   6669      }
   6670      nsIContent& parent = *firstRangeStartPoint.ContainerAs<nsIContent>();
   6671      createEmptyDivElement =
   6672          !HTMLEditUtils::IsAnyTableElementExceptColumnElement(parent) ||
   6673          HTMLEditUtils::IsTableCellOrCaptionElement(parent);
   6674    }
   6675  }
   6676 
   6677  if (createEmptyDivElement) {
   6678    if (MOZ_UNLIKELY(!pointToPutCaret.IsSet() && !aRanges.IsInContent())) {
   6679      NS_WARNING("Mutation event listener might have changed the selection");
   6680      return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
   6681    }
   6682    const EditorDOMPoint pointToInsertDivElement =
   6683        pointToPutCaret.IsSet()
   6684            ? pointToPutCaret
   6685            : aRanges.GetFirstRangeStartPoint<EditorDOMPoint>();
   6686    Result<CreateElementResult, nsresult> insertNewDivElementResult =
   6687        InsertDivElementToAlignContents(pointToInsertDivElement, aAlignType,
   6688                                        aEditingHost);
   6689    if (insertNewDivElementResult.isErr()) {
   6690      NS_WARNING("HTMLEditor::InsertDivElementToAlignContents() failed");
   6691      return insertNewDivElementResult.unwrapErr();
   6692    }
   6693    CreateElementResult unwrappedInsertNewDivElementResult =
   6694        insertNewDivElementResult.unwrap();
   6695    aRanges.ClearSavedRanges();
   6696    EditorDOMPoint pointToPutCaret =
   6697        unwrappedInsertNewDivElementResult.UnwrapCaretPoint();
   6698    nsresult rv = aRanges.Collapse(pointToPutCaret);
   6699    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   6700                         "AutoClonedRangeArray::Collapse() failed");
   6701    return rv;
   6702  }
   6703 
   6704  Result<CreateElementResult, nsresult> maybeCreateDivElementResult =
   6705      AlignNodesAndDescendants(arrayOfContents, aAlignType, aEditingHost);
   6706  if (MOZ_UNLIKELY(maybeCreateDivElementResult.isErr())) {
   6707    NS_WARNING("HTMLEditor::AlignNodesAndDescendants() failed");
   6708    return maybeCreateDivElementResult.unwrapErr();
   6709  }
   6710  maybeCreateDivElementResult.inspect().IgnoreCaretPointSuggestion();
   6711 
   6712  MOZ_ASSERT(aRanges.HasSavedRanges());
   6713  aRanges.RestoreFromSavedRanges();
   6714  // If restored range is collapsed outside the latest cased <div> element,
   6715  // we should move caret into the <div>.
   6716  if (maybeCreateDivElementResult.inspect().GetNewNode() &&
   6717      aRanges.IsCollapsed() && !aRanges.Ranges().IsEmpty()) {
   6718    const auto firstRangeStartRawPoint =
   6719        aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>();
   6720    if (MOZ_LIKELY(firstRangeStartRawPoint.IsSet())) {
   6721      Result<EditorRawDOMPoint, nsresult> pointInNewDivOrError =
   6722          HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
   6723              EditorRawDOMPoint>(
   6724              *maybeCreateDivElementResult.inspect().GetNewNode(),
   6725              firstRangeStartRawPoint);
   6726      if (MOZ_UNLIKELY(pointInNewDivOrError.isErr())) {
   6727        NS_WARNING(
   6728            "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, "
   6729            "but ignored");
   6730      } else if (pointInNewDivOrError.inspect().IsSet()) {
   6731        nsresult rv = aRanges.Collapse(pointInNewDivOrError.unwrap());
   6732        if (NS_FAILED(rv)) {
   6733          NS_WARNING("AutoClonedRangeArray::Collapse() failed");
   6734          return rv;
   6735        }
   6736      }
   6737    }
   6738  }
   6739  return NS_OK;
   6740 }
   6741 
   6742 Result<CreateElementResult, nsresult>
   6743 HTMLEditor::InsertDivElementToAlignContents(
   6744    const EditorDOMPoint& aPointToInsert, const nsAString& aAlignType,
   6745    const Element& aEditingHost) {
   6746  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
   6747  MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
   6748  MOZ_ASSERT(aPointToInsert.IsSetAndValid());
   6749 
   6750  if (NS_WARN_IF(!aPointToInsert.IsSet())) {
   6751    return Err(NS_ERROR_FAILURE);
   6752  }
   6753 
   6754  Result<CreateElementResult, nsresult> createNewDivElementResult =
   6755      InsertElementWithSplittingAncestorsWithTransaction(
   6756          *nsGkAtoms::div, aPointToInsert, BRElementNextToSplitPoint::Delete,
   6757          aEditingHost);
   6758  if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
   6759    NS_WARNING(
   6760        "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
   6761        "nsGkAtoms::div, BRElementNextToSplitPoint::Delete) failed");
   6762    return createNewDivElementResult;
   6763  }
   6764  CreateElementResult unwrappedCreateNewDivElementResult =
   6765      createNewDivElementResult.unwrap();
   6766  // We'll suggest start of the new <div>, so we don't need the suggested
   6767  // position.
   6768  unwrappedCreateNewDivElementResult.IgnoreCaretPointSuggestion();
   6769 
   6770  MOZ_ASSERT(unwrappedCreateNewDivElementResult.GetNewNode());
   6771  RefPtr<Element> newDivElement =
   6772      unwrappedCreateNewDivElementResult.UnwrapNewNode();
   6773  // Set up the alignment on the div, using HTML or CSS
   6774  Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   6775      SetBlockElementAlign(*newDivElement, aAlignType,
   6776                           EditTarget::OnlyDescendantsExceptTable);
   6777  if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   6778    NS_WARNING(
   6779        "HTMLEditor::SetBlockElementAlign(EditTarget::"
   6780        "OnlyDescendantsExceptTable) failed");
   6781    return pointToPutCaretOrError.propagateErr();
   6782  }
   6783  // We don't need the new suggested position too.
   6784 
   6785  // Put in a padding <br> element for empty last line so that it won't get
   6786  // deleted.
   6787  {
   6788    Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
   6789        InsertPaddingBRElementForEmptyLastLineWithTransaction(
   6790            EditorDOMPoint(newDivElement, 0u));
   6791    if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
   6792      NS_WARNING(
   6793          "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
   6794          "failed");
   6795      return insertPaddingBRElementResult;
   6796    }
   6797    insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
   6798  }
   6799 
   6800  return CreateElementResult(std::move(newDivElement),
   6801                             EditorDOMPoint(newDivElement, 0u));
   6802 }
   6803 
   6804 Result<CreateElementResult, nsresult> HTMLEditor::AlignNodesAndDescendants(
   6805    nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
   6806    const nsAString& aAlignType, const Element& aEditingHost) {
   6807  // Detect all the transitions in the array, where a transition means that
   6808  // adjacent nodes in the array don't have the same parent.
   6809  AutoTArray<bool, 64> transitionList;
   6810  HTMLEditor::MakeTransitionList(aArrayOfContents, transitionList);
   6811 
   6812  RefPtr<Element> latestCreatedDivElement;
   6813  EditorDOMPoint pointToPutCaret;
   6814 
   6815  // Okay, now go through all the nodes and give them an align attrib or put
   6816  // them in a div, or whatever is appropriate.  Woohoo!
   6817 
   6818  RefPtr<Element> createdDivElement;
   6819  const bool useCSS = IsCSSEnabled();
   6820  for (size_t i = 0; i < aArrayOfContents.Length(); i++) {
   6821    const OwningNonNull<nsIContent>& content = aArrayOfContents[i];
   6822 
   6823    // Ignore all non-editable nodes.  Leave them be.
   6824    if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
   6825      continue;
   6826    }
   6827 
   6828    // The node is a table element, an hr, a paragraph, a div or a section
   6829    // header; in HTML 4, it can directly carry the ALIGN attribute and we
   6830    // don't need to nest it, just set the alignment.  In CSS, assign the
   6831    // corresponding CSS styles in SetBlockElementAlign().
   6832    if (HTMLEditUtils::IsAlignAttrSupported(content)) {
   6833      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   6834          SetBlockElementAlign(MOZ_KnownLive(*content->AsElement()), aAlignType,
   6835                               EditTarget::NodeAndDescendantsExceptTable);
   6836      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   6837        NS_WARNING(
   6838            "HTMLEditor::SetBlockElementAlign(EditTarget::"
   6839            "NodeAndDescendantsExceptTable) failed");
   6840        return pointToPutCaretOrError.propagateErr();
   6841      }
   6842      if (pointToPutCaretOrError.inspect().IsSet()) {
   6843        pointToPutCaret = pointToPutCaretOrError.unwrap();
   6844      }
   6845      // Clear out createdDivElement so that we don't put nodes after this one
   6846      // into it
   6847      createdDivElement = nullptr;
   6848      continue;
   6849    }
   6850 
   6851    EditorDOMPoint atContent(content);
   6852    if (NS_WARN_IF(!atContent.IsInContentNode())) {
   6853      continue;
   6854    }
   6855 
   6856    // Skip insignificant formatting text nodes to prevent unnecessary
   6857    // structure splitting!
   6858    if (content->IsText() &&
   6859        ((HTMLEditUtils::IsAnyTableElementExceptColumnElement(
   6860              *atContent.ContainerAs<nsIContent>()) &&
   6861          !HTMLEditUtils::IsTableCellOrCaptionElement(
   6862              *atContent.ContainerAs<nsIContent>())) ||
   6863         HTMLEditUtils::IsListElement(*atContent.ContainerAs<nsIContent>()) ||
   6864         HTMLEditUtils::IsEmptyNode(
   6865             *content,
   6866             {EmptyCheckOption::TreatSingleBRElementAsVisible,
   6867              EmptyCheckOption::TreatNonEditableContentAsInvisible}))) {
   6868      continue;
   6869    }
   6870 
   6871    // If it's a list item, or a list inside a list, forget any "current" div,
   6872    // and instead put divs inside the appropriate block (td, li, etc.)
   6873    if (HTMLEditUtils::IsListItemElement(*content) ||
   6874        HTMLEditUtils::IsListElement(*content)) {
   6875      Element* listOrListItemElement = content->AsElement();
   6876      {
   6877        AutoEditorDOMPointOffsetInvalidator lockChild(atContent);
   6878        // MOZ_KnownLive(*listOrListItemElement): An element of aArrayOfContents
   6879        // which is array of OwningNonNull.
   6880        Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   6881            RemoveAlignFromDescendants(MOZ_KnownLive(*listOrListItemElement),
   6882                                       aAlignType,
   6883                                       EditTarget::OnlyDescendantsExceptTable);
   6884        if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   6885          NS_WARNING(
   6886              "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
   6887              "OnlyDescendantsExceptTable) failed");
   6888          return pointToPutCaretOrError.propagateErr();
   6889        }
   6890        if (pointToPutCaretOrError.inspect().IsSet()) {
   6891          pointToPutCaret = pointToPutCaretOrError.unwrap();
   6892        }
   6893      }
   6894      if (NS_WARN_IF(!atContent.IsSetAndValid())) {
   6895        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   6896      }
   6897 
   6898      if (useCSS) {
   6899        nsStyledElement* styledListOrListItemElement =
   6900            nsStyledElement::FromNode(listOrListItemElement);
   6901        if (styledListOrListItemElement &&
   6902            EditorElementStyle::Align().IsCSSSettable(
   6903                *styledListOrListItemElement)) {
   6904          // MOZ_KnownLive(*styledListOrListItemElement): An element of
   6905          // aArrayOfContents which is array of OwningNonNull.
   6906          Result<size_t, nsresult> result =
   6907              CSSEditUtils::SetCSSEquivalentToStyle(
   6908                  WithTransaction::Yes, *this,
   6909                  MOZ_KnownLive(*styledListOrListItemElement),
   6910                  EditorElementStyle::Align(), &aAlignType);
   6911          if (MOZ_UNLIKELY(result.isErr())) {
   6912            if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
   6913              return result.propagateErr();
   6914            }
   6915            NS_WARNING(
   6916                "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::"
   6917                "Align()) failed, but ignored");
   6918          }
   6919        }
   6920        createdDivElement = nullptr;
   6921        continue;
   6922      }
   6923 
   6924      if (HTMLEditUtils::IsListElement(*atContent.ContainerAs<nsIContent>())) {
   6925        // If we don't use CSS, add a content to list element: they have to
   6926        // be inside another list, i.e., >= second level of nesting.
   6927        // XXX AlignContentsInAllTableCellsAndListItems() handles only list
   6928        //     item elements and table cells.  Is it intentional?  Why don't
   6929        //     we need to align contents in other type blocks?
   6930        // MOZ_KnownLive(*listOrListItemElement): An element of aArrayOfContents
   6931        // which is array of OwningNonNull.
   6932        Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   6933            AlignContentsInAllTableCellsAndListItems(
   6934                MOZ_KnownLive(*listOrListItemElement), aAlignType);
   6935        if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   6936          NS_WARNING(
   6937              "HTMLEditor::AlignContentsInAllTableCellsAndListItems() failed");
   6938          return pointToPutCaretOrError.propagateErr();
   6939        }
   6940        if (pointToPutCaretOrError.inspect().IsSet()) {
   6941          pointToPutCaret = pointToPutCaretOrError.unwrap();
   6942        }
   6943        createdDivElement = nullptr;
   6944        continue;
   6945      }
   6946 
   6947      // Clear out createdDivElement so that we don't put nodes after this one
   6948      // into it
   6949    }
   6950 
   6951    // Need to make a div to put things in if we haven't already, or if this
   6952    // node doesn't go in div we used earlier.
   6953    if (!createdDivElement || transitionList[i]) {
   6954      // First, check that our element can contain a div.
   6955      if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(),
   6956                                         *nsGkAtoms::div)) {
   6957        // XXX Why do we return "OK" here rather than returning error or
   6958        //     doing continue?
   6959        return latestCreatedDivElement
   6960                   ? CreateElementResult(std::move(latestCreatedDivElement),
   6961                                         std::move(pointToPutCaret))
   6962                   : CreateElementResult::NotHandled(
   6963                         std::move(pointToPutCaret));
   6964      }
   6965 
   6966      Result<CreateElementResult, nsresult> createNewDivElementResult =
   6967          InsertElementWithSplittingAncestorsWithTransaction(
   6968              *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
   6969              aEditingHost);
   6970      if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
   6971        NS_WARNING(
   6972            "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
   6973            "nsGkAtoms::div) failed");
   6974        return createNewDivElementResult;
   6975      }
   6976      CreateElementResult unwrappedCreateNewDivElementResult =
   6977          createNewDivElementResult.unwrap();
   6978      if (unwrappedCreateNewDivElementResult.HasCaretPointSuggestion()) {
   6979        pointToPutCaret = unwrappedCreateNewDivElementResult.UnwrapCaretPoint();
   6980      }
   6981 
   6982      MOZ_ASSERT(unwrappedCreateNewDivElementResult.GetNewNode());
   6983      createdDivElement = unwrappedCreateNewDivElementResult.UnwrapNewNode();
   6984      // Set up the alignment on the div
   6985      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   6986          SetBlockElementAlign(*createdDivElement, aAlignType,
   6987                               EditTarget::OnlyDescendantsExceptTable);
   6988      if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   6989        if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() ==
   6990                       NS_ERROR_EDITOR_DESTROYED)) {
   6991          return pointToPutCaretOrError.propagateErr();
   6992        }
   6993        NS_WARNING(
   6994            "HTMLEditor::SetBlockElementAlign(EditTarget::"
   6995            "OnlyDescendantsExceptTable) failed, but ignored");
   6996      } else if (pointToPutCaretOrError.inspect().IsSet()) {
   6997        pointToPutCaret = pointToPutCaretOrError.unwrap();
   6998      }
   6999      latestCreatedDivElement = createdDivElement;
   7000    }
   7001 
   7002    const OwningNonNull<nsIContent> lastContent = [&]() {
   7003      nsIContent* lastContent = content;
   7004      for (; i + 1 < aArrayOfContents.Length(); i++) {
   7005        const OwningNonNull<nsIContent>& nextContent = aArrayOfContents[i + 1];
   7006        if (lastContent->GetNextSibling() != nextContent ||
   7007            !EditorUtils::IsEditableContent(content, EditorType::HTML) ||
   7008            !HTMLEditUtils::IsAlignAttrSupported(nextContent) ||
   7009            // If we meets an invisible `Text` in table or list, we don't move
   7010            // it to avoid to handle ancestors for them.  However, ignoring the
   7011            // empty `Text` nodes is more expensive than moving them here.
   7012            // Therefore, here does not check whether the following sibling of
   7013            // `content` is an empty `Text`.
   7014 
   7015            // In some cases, we reach here even if `content` is a list or a
   7016            // list item.  However, anyway we need to run a preparation for such
   7017            // element.  Therefore, we cannot move such type of elements with
   7018            // `content` here.
   7019            HTMLEditUtils::IsListItemElement(*nextContent) ||
   7020            HTMLEditUtils::IsListElement(*nextContent) ||
   7021            // Similarly, if the sibling is in the transitionList, we need to
   7022            // handle it separately.
   7023            transitionList[i + 1]) {
   7024          break;
   7025        }
   7026        lastContent = nextContent;
   7027      }
   7028      return OwningNonNull<nsIContent>(*lastContent);
   7029    }();
   7030 
   7031    // Tuck the node into the end of the active div
   7032    //
   7033    // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it alive.
   7034    Result<MoveNodeResult, nsresult> moveNodeResult =
   7035        MoveSiblingsToEndWithTransaction(MOZ_KnownLive(content), lastContent,
   7036                                         *createdDivElement);
   7037    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
   7038      NS_WARNING("HTMLEditor::MoveSiblingsToEndWithTransaction() failed");
   7039      return moveNodeResult.propagateErr();
   7040    }
   7041    MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
   7042    if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) {
   7043      pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint();
   7044    }
   7045  }
   7046 
   7047  return latestCreatedDivElement
   7048             ? CreateElementResult(std::move(latestCreatedDivElement),
   7049                                   std::move(pointToPutCaret))
   7050             : CreateElementResult::NotHandled(std::move(pointToPutCaret));
   7051 }
   7052 
   7053 Result<EditorDOMPoint, nsresult>
   7054 HTMLEditor::AlignContentsInAllTableCellsAndListItems(
   7055    Element& aElement, const nsAString& aAlignType) {
   7056  MOZ_ASSERT(IsEditActionDataAvailable());
   7057 
   7058  // Gather list of table cells or list items
   7059  AutoTArray<OwningNonNull<Element>, 64> arrayOfTableCellsAndListItems;
   7060  DOMIterator iter(aElement);
   7061  iter.AppendNodesToArray(
   7062      +[](nsINode& aNode, void*) -> bool {
   7063        MOZ_ASSERT(Element::FromNode(&aNode));
   7064        return HTMLEditUtils::IsTableCellElement(*aNode.AsElement()) ||
   7065               HTMLEditUtils::IsListItemElement(*aNode.AsElement());
   7066      },
   7067      arrayOfTableCellsAndListItems);
   7068 
   7069  // Now that we have the list, align their contents as requested
   7070  EditorDOMPoint pointToPutCaret;
   7071  for (auto& tableCellOrListItemElement : arrayOfTableCellsAndListItems) {
   7072    // MOZ_KnownLive because 'arrayOfTableCellsAndListItems' is guaranteed to
   7073    // keep it alive.
   7074    Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   7075        AlignBlockContentsWithDivElement(
   7076            MOZ_KnownLive(tableCellOrListItemElement), aAlignType);
   7077    if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   7078      NS_WARNING("HTMLEditor::AlignBlockContentsWithDivElement() failed");
   7079      return pointToPutCaretOrError;
   7080    }
   7081    if (pointToPutCaretOrError.inspect().IsSet()) {
   7082      pointToPutCaret = pointToPutCaretOrError.unwrap();
   7083    }
   7084  }
   7085 
   7086  return pointToPutCaret;
   7087 }
   7088 
   7089 Result<EditorDOMPoint, nsresult> HTMLEditor::AlignBlockContentsWithDivElement(
   7090    Element& aBlockElement, const nsAString& aAlignType) {
   7091  MOZ_ASSERT(IsEditActionDataAvailable());
   7092 
   7093  // XXX Chrome wraps text into a <div> only when the container has different
   7094  // blocks.  At that time, Chrome seems treating non-editable nodes as a line
   7095  // break.  So, their behavior is also odd so that it does not make sense to
   7096  // follow their behavior when there is non-editable content.
   7097 
   7098  // XXX I don't understand why we should NOT align non-editable children
   7099  //     with modifying EDITABLE `<div>` element.
   7100  const nsCOMPtr<nsIContent> firstEditableContent =
   7101      HTMLEditUtils::GetFirstChild(aBlockElement,
   7102                                   {WalkTreeOption::IgnoreNonEditableNode});
   7103  if (!firstEditableContent) {
   7104    // This block has no editable content, nothing to align.
   7105    return EditorDOMPoint();
   7106  }
   7107 
   7108  // If there is only one editable content and it's a `<div>` element,
   7109  // just set `align` attribute of it.
   7110  const nsCOMPtr<nsIContent> lastEditableContent = HTMLEditUtils::GetLastChild(
   7111      aBlockElement, {WalkTreeOption::IgnoreNonEditableNode});
   7112  if (firstEditableContent == lastEditableContent &&
   7113      firstEditableContent->IsHTMLElement(nsGkAtoms::div)) {
   7114    // XXX Chrome uses `style="text-align: foo"` instead of the legacy `align`
   7115    // attribute.  That does not allow to align the child blocks center and they
   7116    // put the style to every blocks in the selection range. So, it requires a
   7117    // complicated change to follow their behavior.
   7118    nsresult rv = SetAttributeOrEquivalent(
   7119        MOZ_KnownLive(firstEditableContent->AsElement()), nsGkAtoms::align,
   7120        aAlignType, false);
   7121    if (NS_WARN_IF(Destroyed())) {
   7122      NS_WARNING(
   7123          "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) caused "
   7124          "destroying the editor");
   7125      return Err(NS_ERROR_EDITOR_DESTROYED);
   7126    }
   7127    if (NS_FAILED(rv)) {
   7128      NS_WARNING(
   7129          "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
   7130      return Err(rv);
   7131    }
   7132    return EditorDOMPoint();
   7133  }
   7134 
   7135  // Otherwise, we need to insert a `<div>` element to set `align` attribute.
   7136  Result<CreateElementResult, nsresult> createNewDivElementResultOrError =
   7137      CreateAndInsertElement(
   7138          WithTransaction::Yes, *nsGkAtoms::div,
   7139          EditorDOMPoint(&aBlockElement, 0u),
   7140          // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
   7141          [&](HTMLEditor& aHTMLEditor, Element& aDivElement,
   7142              const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
   7143            MOZ_ASSERT(!aDivElement.IsInComposedDoc());
   7144            // If aDivElement has not been connected yet, we do not need
   7145            // transaction of setting align attribute here.
   7146            nsresult rv = aHTMLEditor.SetAttributeOrEquivalent(
   7147                &aDivElement, nsGkAtoms::align, aAlignType, false);
   7148            if (NS_FAILED(rv)) {
   7149              NS_WARNING(
   7150                  "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align, "
   7151                  "\"...\", false) failed");
   7152              return rv;
   7153            }
   7154            if (!aBlockElement.HasChildren()) {
   7155              return NS_OK;
   7156            }
   7157            // FIXME: This will move non-editable children between
   7158            // firstEditableContent and lastEditableContent.  So, the design of
   7159            // this method is odd.
   7160            Result<MoveNodeResult, nsresult> moveChildrenResultOrError =
   7161                aHTMLEditor.MoveSiblingsWithTransaction(
   7162                    *firstEditableContent, *lastEditableContent,
   7163                    EditorDOMPoint(&aDivElement, 0));
   7164            if (MOZ_UNLIKELY(moveChildrenResultOrError.isErr())) {
   7165              NS_WARNING_ASSERTION(
   7166                  moveChildrenResultOrError.isOk(),
   7167                  "HTMLEditor::MoveSiblingsWithTransaction() failed");
   7168              return moveChildrenResultOrError.unwrapErr();
   7169            }
   7170            moveChildrenResultOrError.unwrap().IgnoreCaretPointSuggestion();
   7171            return NS_OK;
   7172          });
   7173  if (MOZ_UNLIKELY(createNewDivElementResultOrError.isErr())) {
   7174    NS_WARNING(
   7175        "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes, "
   7176        "nsGkAtoms::div) failed");
   7177    return createNewDivElementResultOrError.propagateErr();
   7178  }
   7179  return createNewDivElementResultOrError.unwrap().UnwrapCaretPoint();
   7180 }
   7181 
   7182 Result<EditorRawDOMRange, nsresult>
   7183 HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction(
   7184    const nsRange* aRange, const Element& aEditingHost) const {
   7185  MOZ_ASSERT(IsEditActionDataAvailable());
   7186 
   7187  // This tweaks selections to be more "natural".
   7188  // Idea here is to adjust edges of selection ranges so that they do not cross
   7189  // breaks or block boundaries unless something editable beyond that boundary
   7190  // is also selected.  This adjustment makes it much easier for the various
   7191  // block operations to determine what nodes to act on.
   7192  if (NS_WARN_IF(!aRange) || NS_WARN_IF(!aRange->IsPositioned())) {
   7193    return Err(NS_ERROR_FAILURE);
   7194  }
   7195 
   7196  const EditorRawDOMPoint startPoint(aRange->StartRef());
   7197  if (NS_WARN_IF(!startPoint.IsSet())) {
   7198    return Err(NS_ERROR_FAILURE);
   7199  }
   7200  const EditorRawDOMPoint endPoint(aRange->EndRef());
   7201  if (NS_WARN_IF(!endPoint.IsSet())) {
   7202    return Err(NS_ERROR_FAILURE);
   7203  }
   7204 
   7205  // adjusted values default to original values
   7206  EditorRawDOMRange newRange(startPoint, endPoint);
   7207 
   7208  {
   7209    // Is there any intervening visible white-space?  If so we can't push
   7210    // selection past that, it would visibly change meaning of users selection.
   7211    const WSScanResult prevVisibleThingOfEndPoint =
   7212        WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   7213            {
   7214                // We should refer only the default style of HTML because we
   7215                // need to wrap any elements with a specific HTML element.  So
   7216                // we should not refer actual style.  For example, we want to
   7217                // reformat parent HTML block element even if selected in a
   7218                // blocked phrase element or non-HTMLelement.
   7219                WSRunScanner::Option::ReferHTMLDefaultStyle,
   7220            },
   7221            endPoint, &aEditingHost);
   7222    if (MOZ_UNLIKELY(prevVisibleThingOfEndPoint.Failed())) {
   7223      NS_WARNING(
   7224          "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary() failed");
   7225      return Err(NS_ERROR_FAILURE);
   7226    }
   7227    if (prevVisibleThingOfEndPoint.ReachedSomethingNonTextContent()) {
   7228      // eThisBlock and eOtherBlock conveniently distinguish cases
   7229      // of going "down" into a block and "up" out of a block.
   7230      if (prevVisibleThingOfEndPoint.ReachedOtherBlockElement()) {
   7231        // endpoint is just after the close of a block.
   7232        if (nsIContent* child = HTMLEditUtils::GetLastLeafContent(
   7233                *prevVisibleThingOfEndPoint.ElementPtr(),
   7234                {LeafNodeType::LeafNodeOrChildBlock},
   7235                BlockInlineCheck::UseHTMLDefaultStyle)) {
   7236          newRange.SetEnd(EditorRawDOMPoint::After(*child));
   7237        }
   7238        // else block is empty - we can leave selection alone here, i think.
   7239      } else if (prevVisibleThingOfEndPoint.ReachedCurrentBlockBoundary() ||
   7240                 prevVisibleThingOfEndPoint
   7241                     .ReachedInlineEditingHostBoundary()) {
   7242        // endpoint is just after start of this block
   7243        if (nsIContent* child = HTMLEditUtils::GetPreviousContent(
   7244                endPoint, {WalkTreeOption::IgnoreNonEditableNode},
   7245                BlockInlineCheck::UseHTMLDefaultStyle, &aEditingHost)) {
   7246          newRange.SetEnd(EditorRawDOMPoint::After(*child));
   7247        }
   7248        // else block is empty - we can leave selection alone here, i think.
   7249      } else if (prevVisibleThingOfEndPoint.ReachedBRElement()) {
   7250        // endpoint is just after break.  lets adjust it to before it.
   7251        newRange.SetEnd(prevVisibleThingOfEndPoint
   7252                            .PointAtReachedContent<EditorRawDOMPoint>());
   7253      }
   7254    }
   7255  }
   7256  {
   7257    // Is there any intervening visible white-space?  If so we can't push
   7258    // selection past that, it would visibly change meaning of users selection.
   7259    const WSScanResult nextVisibleThingOfStartPoint =
   7260        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   7261            {WSRunScanner::Option::ReferHTMLDefaultStyle}, startPoint,
   7262            &aEditingHost);
   7263    if (MOZ_UNLIKELY(nextVisibleThingOfStartPoint.Failed())) {
   7264      NS_WARNING(
   7265          "WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary() failed");
   7266      return Err(NS_ERROR_FAILURE);
   7267    }
   7268    if (nextVisibleThingOfStartPoint.ReachedSomethingNonTextContent()) {
   7269      // eThisBlock and eOtherBlock conveniently distinguish cases
   7270      // of going "down" into a block and "up" out of a block.
   7271      if (nextVisibleThingOfStartPoint.ReachedOtherBlockElement()) {
   7272        // startpoint is just before the start of a block.
   7273        if (nsIContent* child = HTMLEditUtils::GetFirstLeafContent(
   7274                *nextVisibleThingOfStartPoint.ElementPtr(),
   7275                {LeafNodeType::LeafNodeOrChildBlock},
   7276                BlockInlineCheck::UseHTMLDefaultStyle)) {
   7277          newRange.SetStart(EditorRawDOMPoint(child));
   7278        }
   7279        // else block is empty - we can leave selection alone here, i think.
   7280      } else if (nextVisibleThingOfStartPoint.ReachedCurrentBlockBoundary() ||
   7281                 nextVisibleThingOfStartPoint
   7282                     .ReachedInlineEditingHostBoundary()) {
   7283        // startpoint is just before end of this block
   7284        if (nsIContent* child = HTMLEditUtils::GetNextContent(
   7285                startPoint, {WalkTreeOption::IgnoreNonEditableNode},
   7286                BlockInlineCheck::UseHTMLDefaultStyle, &aEditingHost)) {
   7287          newRange.SetStart(EditorRawDOMPoint(child));
   7288        }
   7289        // else block is empty - we can leave selection alone here, i think.
   7290      } else if (nextVisibleThingOfStartPoint.ReachedBRElement()) {
   7291        // startpoint is just before a break.  lets adjust it to after it.
   7292        // XXX If it's an invisible <br>, does this work? Will the following
   7293        // checks solve that?
   7294        newRange.SetStart(nextVisibleThingOfStartPoint
   7295                              .PointAfterReachedContent<EditorRawDOMPoint>());
   7296      }
   7297    }
   7298  }
   7299 
   7300  // There is a demented possibility we have to check for.  We might have a very
   7301  // strange selection that is not collapsed and yet does not contain any
   7302  // editable content, and satisfies some of the above conditions that cause
   7303  // tweaking.  In this case we don't want to tweak the selection into a block
   7304  // it was never in, etc.  There are a variety of strategies one might use to
   7305  // try to detect these cases, but I think the most straightforward is to see
   7306  // if the adjusted locations "cross" the old values: i.e., new end before old
   7307  // start, or new start after old end.  If so then just leave things alone.
   7308 
   7309  Maybe<int32_t> comp = nsContentUtils::ComparePoints(
   7310      startPoint.ToRawRangeBoundary(), newRange.EndRef().ToRawRangeBoundary());
   7311 
   7312  if (NS_WARN_IF(!comp)) {
   7313    return Err(NS_ERROR_FAILURE);
   7314  }
   7315 
   7316  if (*comp == 1) {
   7317    return EditorRawDOMRange();  // New end before old start.
   7318  }
   7319 
   7320  comp = nsContentUtils::ComparePoints(newRange.StartRef().ToRawRangeBoundary(),
   7321                                       endPoint.ToRawRangeBoundary());
   7322 
   7323  if (NS_WARN_IF(!comp)) {
   7324    return Err(NS_ERROR_FAILURE);
   7325  }
   7326 
   7327  if (*comp == 1) {
   7328    return EditorRawDOMRange();  // New start after old end.
   7329  }
   7330 
   7331  return newRange;
   7332 }
   7333 
   7334 template <typename EditorDOMRangeType>
   7335 already_AddRefed<nsRange> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
   7336    const EditorDOMRangeType& aRange) {
   7337  MOZ_DIAGNOSTIC_ASSERT(aRange.IsPositioned());
   7338  return CreateRangeIncludingAdjuscentWhiteSpaces(aRange.StartRef(),
   7339                                                  aRange.EndRef());
   7340 }
   7341 
   7342 template <typename EditorDOMPointType1, typename EditorDOMPointType2>
   7343 already_AddRefed<nsRange> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
   7344    const EditorDOMPointType1& aStartPoint,
   7345    const EditorDOMPointType2& aEndPoint) {
   7346  MOZ_DIAGNOSTIC_ASSERT(!aStartPoint.IsInNativeAnonymousSubtree());
   7347  MOZ_DIAGNOSTIC_ASSERT(!aEndPoint.IsInNativeAnonymousSubtree());
   7348 
   7349  if (!aStartPoint.IsInContentNode() || !aEndPoint.IsInContentNode()) {
   7350    NS_WARNING_ASSERTION(aStartPoint.IsSet(), "aStartPoint was not set");
   7351    NS_WARNING_ASSERTION(aEndPoint.IsSet(), "aEndPoint was not set");
   7352    return nullptr;
   7353  }
   7354 
   7355  const Element* const editingHost = ComputeEditingHost();
   7356  if (NS_WARN_IF(!editingHost)) {
   7357    return nullptr;
   7358  }
   7359 
   7360  EditorDOMPoint startPoint = aStartPoint.template To<EditorDOMPoint>();
   7361  EditorDOMPoint endPoint = aEndPoint.template To<EditorDOMPoint>();
   7362  AutoClonedRangeArray::
   7363      UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement(
   7364          startPoint, endPoint, *editingHost);
   7365 
   7366  if (NS_WARN_IF(!startPoint.IsInContentNode()) ||
   7367      NS_WARN_IF(!endPoint.IsInContentNode())) {
   7368    NS_WARNING(
   7369        "AutoClonedRangeArray::"
   7370        "UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement() "
   7371        "failed");
   7372    return nullptr;
   7373  }
   7374 
   7375  // For text actions, we want to look backwards (or forwards, as
   7376  // appropriate) for additional white-space or nbsp's.  We may have to act
   7377  // on these later even though they are outside of the initial selection.
   7378  // Even if they are in another node!
   7379  // XXX Those scanners do not treat siblings of the text nodes.  Perhaps,
   7380  //     we should use `WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo()`
   7381  //     and `WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces()` instead.
   7382  if (startPoint.IsInTextNode()) {
   7383    while (!startPoint.IsStartOfContainer()) {
   7384      if (!startPoint.IsPreviousCharASCIISpaceOrNBSP()) {
   7385        break;
   7386      }
   7387      MOZ_ALWAYS_TRUE(startPoint.RewindOffset());
   7388    }
   7389  }
   7390  if (!startPoint.GetChildOrContainerIfDataNode() ||
   7391      !startPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
   7392          editingHost)) {
   7393    return nullptr;
   7394  }
   7395  if (endPoint.IsInTextNode()) {
   7396    while (!endPoint.IsEndOfContainer()) {
   7397      if (!endPoint.IsCharASCIISpaceOrNBSP()) {
   7398        break;
   7399      }
   7400      MOZ_ALWAYS_TRUE(endPoint.AdvanceOffset());
   7401    }
   7402  }
   7403  EditorDOMPoint lastRawPoint(endPoint);
   7404  if (!lastRawPoint.IsStartOfContainer()) {
   7405    lastRawPoint.RewindOffset();
   7406  }
   7407  if (!lastRawPoint.GetChildOrContainerIfDataNode() ||
   7408      !lastRawPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
   7409          editingHost)) {
   7410    return nullptr;
   7411  }
   7412 
   7413  RefPtr<nsRange> range =
   7414      nsRange::Create(startPoint.ToRawRangeBoundary(),
   7415                      endPoint.ToRawRangeBoundary(), IgnoreErrors());
   7416  NS_WARNING_ASSERTION(range, "nsRange::Create() failed");
   7417  return range.forget();
   7418 }
   7419 
   7420 Result<EditorDOMPoint, nsresult> HTMLEditor::MaybeSplitElementsAtEveryBRElement(
   7421    nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
   7422    EditSubAction aEditSubAction) {
   7423  // Post-process the list to break up inline containers that contain br's, but
   7424  // only for operations that might care, like making lists or paragraphs
   7425  switch (aEditSubAction) {
   7426    case EditSubAction::eCreateOrRemoveBlock:
   7427    case EditSubAction::eFormatBlockForHTMLCommand:
   7428    case EditSubAction::eMergeBlockContents:
   7429    case EditSubAction::eCreateOrChangeList:
   7430    case EditSubAction::eSetOrClearAlignment:
   7431    case EditSubAction::eSetPositionToAbsolute:
   7432    case EditSubAction::eIndent:
   7433    case EditSubAction::eOutdent: {
   7434      EditorDOMPoint pointToPutCaret;
   7435      for (size_t index : Reversed(IntegerRange(aArrayOfContents.Length()))) {
   7436        OwningNonNull<nsIContent>& content = aArrayOfContents[index];
   7437        if (HTMLEditUtils::IsInlineContent(
   7438                content, BlockInlineCheck::UseHTMLDefaultStyle) &&
   7439            HTMLEditUtils::IsContainerNode(content) && !content->IsText()) {
   7440          AutoTArray<OwningNonNull<nsIContent>, 24> arrayOfInlineContents;
   7441          // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
   7442          // alive.
   7443          Result<EditorDOMPoint, nsresult> splitResult =
   7444              SplitElementsAtEveryBRElement(MOZ_KnownLive(content),
   7445                                            arrayOfInlineContents);
   7446          if (splitResult.isErr()) {
   7447            NS_WARNING("HTMLEditor::SplitElementsAtEveryBRElement() failed");
   7448            return splitResult;
   7449          }
   7450          if (splitResult.inspect().IsSet()) {
   7451            pointToPutCaret = splitResult.unwrap();
   7452          }
   7453          // Put these nodes in aArrayOfContents, replacing the current node
   7454          aArrayOfContents.RemoveElementAt(index);
   7455          aArrayOfContents.InsertElementsAt(index, arrayOfInlineContents);
   7456        }
   7457      }
   7458      return pointToPutCaret;
   7459    }
   7460    default:
   7461      return EditorDOMPoint();
   7462  }
   7463 }
   7464 
   7465 Result<EditorDOMPoint, nsresult>
   7466 HTMLEditor::SplitInlineAncestorsAtRangeBoundaries(
   7467    RangeItem& aRangeItem, BlockInlineCheck aBlockInlineCheck,
   7468    const Element& aEditingHost,
   7469    const nsIContent* aAncestorLimiter /* = nullptr */) {
   7470  MOZ_ASSERT(IsEditActionDataAvailable());
   7471 
   7472  EditorDOMPoint pointToPutCaret;
   7473  if (!aRangeItem.Collapsed() && aRangeItem.mEndContainer &&
   7474      aRangeItem.mEndContainer->IsContent()) {
   7475    nsCOMPtr<nsIContent> mostAncestorInlineContentAtEnd =
   7476        HTMLEditUtils::GetMostDistantAncestorInlineElement(
   7477            *aRangeItem.mEndContainer->AsContent(), aBlockInlineCheck,
   7478            &aEditingHost, aAncestorLimiter);
   7479 
   7480    if (mostAncestorInlineContentAtEnd) {
   7481      Result<SplitNodeResult, nsresult> splitEndInlineResult =
   7482          SplitNodeDeepWithTransaction(
   7483              *mostAncestorInlineContentAtEnd, aRangeItem.EndPoint(),
   7484              SplitAtEdges::eDoNotCreateEmptyContainer);
   7485      if (MOZ_UNLIKELY(splitEndInlineResult.isErr())) {
   7486        NS_WARNING(
   7487            "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
   7488            "eDoNotCreateEmptyContainer) failed");
   7489        return splitEndInlineResult.propagateErr();
   7490      }
   7491      SplitNodeResult unwrappedSplitEndInlineResult =
   7492          splitEndInlineResult.unwrap();
   7493      unwrappedSplitEndInlineResult.MoveCaretPointTo(
   7494          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   7495      if (pointToPutCaret.IsInContentNode() &&
   7496          MOZ_UNLIKELY(
   7497              &aEditingHost !=
   7498              ComputeEditingHost(*pointToPutCaret.ContainerAs<nsIContent>()))) {
   7499        NS_WARNING(
   7500            "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
   7501            "eDoNotCreateEmptyContainer) caused changing editing host");
   7502        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   7503      }
   7504      const auto splitPointAtEnd =
   7505          unwrappedSplitEndInlineResult.AtSplitPoint<EditorRawDOMPoint>();
   7506      if (MOZ_UNLIKELY(!splitPointAtEnd.IsSet())) {
   7507        NS_WARNING(
   7508            "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
   7509            "eDoNotCreateEmptyContainer) didn't return split point");
   7510        return Err(NS_ERROR_FAILURE);
   7511      }
   7512      aRangeItem.mEndContainer = splitPointAtEnd.GetContainer();
   7513      aRangeItem.mEndOffset = splitPointAtEnd.Offset();
   7514    }
   7515  }
   7516 
   7517  if (!aRangeItem.mStartContainer || !aRangeItem.mStartContainer->IsContent()) {
   7518    return pointToPutCaret;
   7519  }
   7520 
   7521  nsCOMPtr<nsIContent> mostAncestorInlineContentAtStart =
   7522      HTMLEditUtils::GetMostDistantAncestorInlineElement(
   7523          *aRangeItem.mStartContainer->AsContent(), aBlockInlineCheck,
   7524          &aEditingHost, aAncestorLimiter);
   7525 
   7526  if (mostAncestorInlineContentAtStart) {
   7527    Result<SplitNodeResult, nsresult> splitStartInlineResult =
   7528        SplitNodeDeepWithTransaction(*mostAncestorInlineContentAtStart,
   7529                                     aRangeItem.StartPoint(),
   7530                                     SplitAtEdges::eDoNotCreateEmptyContainer);
   7531    if (MOZ_UNLIKELY(splitStartInlineResult.isErr())) {
   7532      NS_WARNING(
   7533          "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
   7534          "eDoNotCreateEmptyContainer) failed");
   7535      return splitStartInlineResult.propagateErr();
   7536    }
   7537    SplitNodeResult unwrappedSplitStartInlineResult =
   7538        splitStartInlineResult.unwrap();
   7539    // XXX Why don't we check editing host like above??
   7540    unwrappedSplitStartInlineResult.MoveCaretPointTo(
   7541        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   7542    // XXX If we split only here because of collapsed range, we're modifying
   7543    //     only start point of aRangeItem.  Shouldn't we modify end point here
   7544    //     if it's collapsed?
   7545    const auto splitPointAtStart =
   7546        unwrappedSplitStartInlineResult.AtSplitPoint<EditorRawDOMPoint>();
   7547    if (MOZ_UNLIKELY(!splitPointAtStart.IsSet())) {
   7548      NS_WARNING(
   7549          "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
   7550          "eDoNotCreateEmptyContainer) didn't return split point");
   7551      return Err(NS_ERROR_FAILURE);
   7552    }
   7553    aRangeItem.mStartContainer = splitPointAtStart.GetContainer();
   7554    aRangeItem.mStartOffset = splitPointAtStart.Offset();
   7555  }
   7556 
   7557  return pointToPutCaret;
   7558 }
   7559 
   7560 Result<EditorDOMPoint, nsresult> HTMLEditor::SplitElementsAtEveryBRElement(
   7561    nsIContent& aMostAncestorToBeSplit,
   7562    nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents) {
   7563  MOZ_ASSERT(IsEditActionDataAvailable());
   7564 
   7565  // First build up a list of all the break nodes inside the inline container.
   7566  AutoTArray<OwningNonNull<HTMLBRElement>, 24> arrayOfBRElements;
   7567  DOMIterator iter(aMostAncestorToBeSplit);
   7568  iter.AppendAllNodesToArray(arrayOfBRElements);
   7569 
   7570  // If there aren't any breaks, just put inNode itself in the array
   7571  if (arrayOfBRElements.IsEmpty()) {
   7572    aOutArrayOfContents.AppendElement(aMostAncestorToBeSplit);
   7573    return EditorDOMPoint();
   7574  }
   7575 
   7576  // Else we need to bust up aMostAncestorToBeSplit along all the breaks
   7577  nsCOMPtr<nsIContent> nextContent = &aMostAncestorToBeSplit;
   7578  EditorDOMPoint pointToPutCaret;
   7579  for (OwningNonNull<HTMLBRElement>& brElement : arrayOfBRElements) {
   7580    EditorDOMPoint atBRNode(brElement);
   7581    if (NS_WARN_IF(!atBRNode.IsSet())) {
   7582      return Err(NS_ERROR_FAILURE);
   7583    }
   7584    Result<SplitNodeResult, nsresult> splitNodeResult =
   7585        SplitNodeDeepWithTransaction(
   7586            *nextContent, atBRNode, SplitAtEdges::eAllowToCreateEmptyContainer);
   7587    if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
   7588      NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
   7589      return splitNodeResult.propagateErr();
   7590    }
   7591    SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
   7592    unwrappedSplitNodeResult.MoveCaretPointTo(
   7593        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   7594    // Put previous node at the split point.
   7595    if (nsIContent* previousContent =
   7596            unwrappedSplitNodeResult.GetPreviousContent()) {
   7597      // Might not be a left node.  A break might have been at the very
   7598      // beginning of inline container, in which case
   7599      // SplitNodeDeepWithTransaction() would not actually split anything.
   7600      aOutArrayOfContents.AppendElement(*previousContent);
   7601    }
   7602 
   7603    // Move break outside of container and also put in node list
   7604    // MOZ_KnownLive because 'arrayOfBRElements' is guaranteed to keep it alive.
   7605    Result<MoveNodeResult, nsresult> moveBRElementResult =
   7606        MoveNodeWithTransaction(
   7607            MOZ_KnownLive(brElement),
   7608            unwrappedSplitNodeResult.AtNextContent<EditorDOMPoint>());
   7609    if (MOZ_UNLIKELY(moveBRElementResult.isErr())) {
   7610      NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
   7611      return moveBRElementResult.propagateErr();
   7612    }
   7613    MoveNodeResult unwrappedMoveBRElementResult = moveBRElementResult.unwrap();
   7614    unwrappedMoveBRElementResult.MoveCaretPointTo(
   7615        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   7616    aOutArrayOfContents.AppendElement(brElement);
   7617 
   7618    nextContent = unwrappedSplitNodeResult.GetNextContent();
   7619  }
   7620 
   7621  // Now tack on remaining next node.
   7622  aOutArrayOfContents.AppendElement(*nextContent);
   7623 
   7624  return pointToPutCaret;
   7625 }
   7626 
   7627 // static
   7628 void HTMLEditor::MakeTransitionList(
   7629    const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
   7630    nsTArray<bool>& aTransitionArray) {
   7631  nsINode* prevParent = nullptr;
   7632  aTransitionArray.EnsureLengthAtLeast(aArrayOfContents.Length());
   7633  for (uint32_t i = 0; i < aArrayOfContents.Length(); i++) {
   7634    aTransitionArray[i] = aArrayOfContents[i]->GetParentNode() != prevParent;
   7635    prevParent = aArrayOfContents[i]->GetParentNode();
   7636  }
   7637 }
   7638 
   7639 Result<CreateElementResult, nsresult>
   7640 HTMLEditor::WrapContentsInBlockquoteElementsWithTransaction(
   7641    const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
   7642    const Element& aEditingHost) {
   7643  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
   7644 
   7645  // The idea here is to put the nodes into a minimal number of blockquotes.
   7646  // When the user blockquotes something, they expect one blockquote.  That
   7647  // may not be possible (for instance, if they have two table cells selected,
   7648  // you need two blockquotes inside the cells).
   7649  RefPtr<Element> curBlock, blockElementToPutCaret;
   7650  nsCOMPtr<nsINode> prevParent;
   7651 
   7652  EditorDOMPoint pointToPutCaret;
   7653  for (size_t i = 0; i < aArrayOfContents.Length(); i++) {
   7654    const OwningNonNull<nsIContent>& content = aArrayOfContents[i];
   7655 
   7656    const auto IsNewBlockRequired = [](const nsIContent& aContent) {
   7657      return HTMLEditUtils::IsAnyTableElementExceptTableElementAndColumElement(
   7658                 aContent) ||
   7659             HTMLEditUtils::IsListItemElement(aContent);
   7660    };
   7661 
   7662    if (IsNewBlockRequired(content)) {
   7663      // Forget any previous block
   7664      curBlock = nullptr;
   7665      // Recursion time
   7666      AutoTArray<OwningNonNull<nsIContent>, 24> childContents;
   7667      HTMLEditUtils::CollectAllChildren(*content, childContents);
   7668      Result<CreateElementResult, nsresult>
   7669          wrapChildrenInAnotherBlockquoteResult =
   7670              WrapContentsInBlockquoteElementsWithTransaction(childContents,
   7671                                                              aEditingHost);
   7672      if (MOZ_UNLIKELY(wrapChildrenInAnotherBlockquoteResult.isErr())) {
   7673        NS_WARNING(
   7674            "HTMLEditor::WrapContentsInBlockquoteElementsWithTransaction() "
   7675            "failed");
   7676        return wrapChildrenInAnotherBlockquoteResult;
   7677      }
   7678      CreateElementResult unwrappedWrapChildrenInAnotherBlockquoteResult =
   7679          wrapChildrenInAnotherBlockquoteResult.unwrap();
   7680      unwrappedWrapChildrenInAnotherBlockquoteResult.MoveCaretPointTo(
   7681          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   7682      if (unwrappedWrapChildrenInAnotherBlockquoteResult.GetNewNode()) {
   7683        blockElementToPutCaret =
   7684            unwrappedWrapChildrenInAnotherBlockquoteResult.UnwrapNewNode();
   7685      }
   7686    }
   7687 
   7688    // If the node has different parent than previous node, further nodes in a
   7689    // new parent
   7690    if (prevParent) {
   7691      if (prevParent != content->GetParentNode()) {
   7692        // Forget any previous blockquote node we were using
   7693        curBlock = nullptr;
   7694        prevParent = content->GetParentNode();
   7695      }
   7696    } else {
   7697      prevParent = content->GetParentNode();
   7698    }
   7699 
   7700    // If no curBlock, make one
   7701    if (!curBlock) {
   7702      Result<CreateElementResult, nsresult> createNewBlockquoteElementResult =
   7703          InsertElementWithSplittingAncestorsWithTransaction(
   7704              *nsGkAtoms::blockquote, EditorDOMPoint(content),
   7705              BRElementNextToSplitPoint::Keep, aEditingHost);
   7706      if (MOZ_UNLIKELY(createNewBlockquoteElementResult.isErr())) {
   7707        NS_WARNING(
   7708            "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
   7709            "nsGkAtoms::blockquote) failed");
   7710        return createNewBlockquoteElementResult;
   7711      }
   7712      CreateElementResult unwrappedCreateNewBlockquoteElementResult =
   7713          createNewBlockquoteElementResult.unwrap();
   7714      unwrappedCreateNewBlockquoteElementResult.MoveCaretPointTo(
   7715          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   7716      MOZ_ASSERT(unwrappedCreateNewBlockquoteElementResult.GetNewNode());
   7717      blockElementToPutCaret =
   7718          unwrappedCreateNewBlockquoteElementResult.GetNewNode();
   7719      curBlock = unwrappedCreateNewBlockquoteElementResult.UnwrapNewNode();
   7720    }
   7721 
   7722    const OwningNonNull<nsIContent> lastContent = [&]() {
   7723      nsIContent* lastContent = content;
   7724      for (; i + 1 < aArrayOfContents.Length(); i++) {
   7725        const OwningNonNull<nsIContent>& nextContent = aArrayOfContents[i + 1];
   7726        if (lastContent->GetNextSibling() == nextContent ||
   7727            !IsNewBlockRequired(nextContent)) {
   7728          break;
   7729        }
   7730        lastContent = nextContent;
   7731      }
   7732      return OwningNonNull<nsIContent>(*lastContent);
   7733    }();
   7734 
   7735    // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to/ keep it alive.
   7736    Result<MoveNodeResult, nsresult> moveNodeResult =
   7737        MoveSiblingsToEndWithTransaction(MOZ_KnownLive(content), lastContent,
   7738                                         *curBlock);
   7739    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
   7740      NS_WARNING("HTMLEditor::MoveSiblingsToEndWithTransaction() failed");
   7741      return moveNodeResult.propagateErr();
   7742    }
   7743    MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
   7744    unwrappedMoveNodeResult.MoveCaretPointTo(
   7745        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   7746  }
   7747  return blockElementToPutCaret
   7748             ? CreateElementResult(std::move(blockElementToPutCaret),
   7749                                   std::move(pointToPutCaret))
   7750             : CreateElementResult::NotHandled(std::move(pointToPutCaret));
   7751 }
   7752 
   7753 Result<EditorDOMPoint, nsresult>
   7754 HTMLEditor::RemoveBlockContainerElementsWithTransaction(
   7755    const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
   7756    FormatBlockMode aFormatBlockMode, BlockInlineCheck aBlockInlineCheck) {
   7757  MOZ_ASSERT(IsEditActionDataAvailable());
   7758  MOZ_ASSERT(aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand);
   7759 
   7760  // Intent of this routine is to be used for converting to/from headers,
   7761  // paragraphs, pre, and address.  Those blocks that pretty much just contain
   7762  // inline things...
   7763  RefPtr<Element> blockElement;
   7764  nsCOMPtr<nsIContent> firstContent, lastContent;
   7765  EditorDOMPoint pointToPutCaret;
   7766  for (const auto& content : aArrayOfContents) {
   7767    // If the current node is a format element, remove it.
   7768    if (HTMLEditUtils::IsFormatElementForParagraphStateCommand(content)) {
   7769      // Process any partial progress saved
   7770      if (blockElement) {
   7771        Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
   7772            RemoveBlockContainerElementWithTransactionBetween(
   7773                *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
   7774        if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
   7775          NS_WARNING(
   7776              "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
   7777              "failed");
   7778          return unwrapBlockElementResult.propagateErr();
   7779        }
   7780        unwrapBlockElementResult.unwrap().MoveCaretPointTo(
   7781            pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   7782        firstContent = lastContent = blockElement = nullptr;
   7783      }
   7784      if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
   7785        continue;
   7786      }
   7787      // Remove current block
   7788      Result<EditorDOMPoint, nsresult> unwrapFormatBlockResult =
   7789          RemoveBlockContainerWithTransaction(
   7790              MOZ_KnownLive(*content->AsElement()));
   7791      if (MOZ_UNLIKELY(unwrapFormatBlockResult.isErr())) {
   7792        NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
   7793        return unwrapFormatBlockResult;
   7794      }
   7795      if (unwrapFormatBlockResult.inspect().IsSet()) {
   7796        pointToPutCaret = unwrapFormatBlockResult.unwrap();
   7797      }
   7798      continue;
   7799    }
   7800 
   7801    // XXX How about, <th>, <thead>, <tfoot>, <dt>, <dl>?
   7802    if (content->IsAnyOfHTMLElements(
   7803            nsGkAtoms::table, nsGkAtoms::tr, nsGkAtoms::tbody, nsGkAtoms::td,
   7804            nsGkAtoms::li, nsGkAtoms::blockquote, nsGkAtoms::div) ||
   7805        HTMLEditUtils::IsListElement(*content)) {
   7806      // Process any partial progress saved
   7807      if (blockElement) {
   7808        Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
   7809            RemoveBlockContainerElementWithTransactionBetween(
   7810                *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
   7811        if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
   7812          NS_WARNING(
   7813              "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
   7814              "failed");
   7815          return unwrapBlockElementResult.propagateErr();
   7816        }
   7817        unwrapBlockElementResult.unwrap().MoveCaretPointTo(
   7818            pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   7819        firstContent = lastContent = blockElement = nullptr;
   7820      }
   7821      if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
   7822        continue;
   7823      }
   7824      // Recursion time
   7825      AutoTArray<OwningNonNull<nsIContent>, 24> childContents;
   7826      HTMLEditUtils::CollectAllChildren(*content, childContents);
   7827      Result<EditorDOMPoint, nsresult> removeBlockContainerElementsResult =
   7828          RemoveBlockContainerElementsWithTransaction(
   7829              childContents, aFormatBlockMode, aBlockInlineCheck);
   7830      if (MOZ_UNLIKELY(removeBlockContainerElementsResult.isErr())) {
   7831        NS_WARNING(
   7832            "HTMLEditor::RemoveBlockContainerElementsWithTransaction() failed");
   7833        return removeBlockContainerElementsResult;
   7834      }
   7835      if (removeBlockContainerElementsResult.inspect().IsSet()) {
   7836        pointToPutCaret = removeBlockContainerElementsResult.unwrap();
   7837      }
   7838      continue;
   7839    }
   7840 
   7841    if (HTMLEditUtils::IsInlineContent(content, aBlockInlineCheck)) {
   7842      if (blockElement) {
   7843        // If so, is this node a descendant?
   7844        if (EditorUtils::IsDescendantOf(*content, *blockElement)) {
   7845          // Then we don't need to do anything different for this node
   7846          lastContent = content;
   7847          continue;
   7848        }
   7849        // Otherwise, we have progressed beyond end of blockElement, so let's
   7850        // handle it now.  We need to remove the portion of blockElement that
   7851        // contains [firstContent - lastContent].
   7852        Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
   7853            RemoveBlockContainerElementWithTransactionBetween(
   7854                *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
   7855        if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
   7856          NS_WARNING(
   7857              "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
   7858              "failed");
   7859          return unwrapBlockElementResult.propagateErr();
   7860        }
   7861        unwrapBlockElementResult.unwrap().MoveCaretPointTo(
   7862            pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   7863        firstContent = lastContent = blockElement = nullptr;
   7864        // Fall out and handle content
   7865      }
   7866      blockElement = HTMLEditUtils::GetAncestorElement(
   7867          content, HTMLEditUtils::ClosestEditableBlockElement,
   7868          aBlockInlineCheck);
   7869      if (!blockElement ||
   7870          !HTMLEditUtils::IsFormatElementForParagraphStateCommand(
   7871              *blockElement) ||
   7872          !HTMLEditUtils::IsRemovableNode(*blockElement)) {
   7873        // Not a block kind that we care about.
   7874        blockElement = nullptr;
   7875      } else {
   7876        firstContent = lastContent = content;
   7877      }
   7878      continue;
   7879    }
   7880 
   7881    if (blockElement) {
   7882      // Some node that is already sans block style.  Skip over it and process
   7883      // any partial progress saved.
   7884      Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
   7885          RemoveBlockContainerElementWithTransactionBetween(
   7886              *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
   7887      if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
   7888        NS_WARNING(
   7889            "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
   7890            "failed");
   7891        return unwrapBlockElementResult.propagateErr();
   7892      }
   7893      unwrapBlockElementResult.unwrap().MoveCaretPointTo(
   7894          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   7895      firstContent = lastContent = blockElement = nullptr;
   7896      continue;
   7897    }
   7898  }
   7899  // Process any partial progress saved
   7900  if (blockElement) {
   7901    Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult =
   7902        RemoveBlockContainerElementWithTransactionBetween(
   7903            *blockElement, *firstContent, *lastContent, aBlockInlineCheck);
   7904    if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
   7905      NS_WARNING(
   7906          "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() "
   7907          "failed");
   7908      return unwrapBlockElementResult.propagateErr();
   7909    }
   7910    unwrapBlockElementResult.unwrap().MoveCaretPointTo(
   7911        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   7912    firstContent = lastContent = blockElement = nullptr;
   7913  }
   7914  return pointToPutCaret;
   7915 }
   7916 
   7917 Result<CreateElementResult, nsresult>
   7918 HTMLEditor::CreateOrChangeFormatContainerElement(
   7919    nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
   7920    const nsStaticAtom& aNewFormatTagName, FormatBlockMode aFormatBlockMode,
   7921    const Element& aEditingHost) {
   7922  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
   7923 
   7924  // Intent of this routine is to be used for converting to/from headers,
   7925  // paragraphs, pre, and address.  Those blocks that pretty much just contain
   7926  // inline things...
   7927  RefPtr<Element> newBlock, curBlock, blockElementToPutCaret;
   7928  // If we found a <br> element which should be moved into curBlock, this keeps
   7929  // storing the <br> element after removing it from the tree.
   7930  RefPtr<Element> pendingBRElementToMoveCurBlock;
   7931  EditorDOMPoint pointToPutCaret;
   7932  for (size_t i = 0; i < aArrayOfContents.Length(); i++) {
   7933    const OwningNonNull<nsIContent>& content = aArrayOfContents[i];
   7934 
   7935    EditorDOMPoint atContent(content);
   7936    if (NS_WARN_IF(!atContent.IsInContentNode())) {
   7937      // If given node has been removed from the document, let's ignore it
   7938      // since the following code may need its parent replace it with new
   7939      // block.
   7940      curBlock = nullptr;
   7941      newBlock = nullptr;
   7942      pendingBRElementToMoveCurBlock = nullptr;
   7943      continue;
   7944    }
   7945 
   7946    const auto IsSameFormatBlockOrNonEditableBlock =
   7947        [&aNewFormatTagName](const nsIContent& aContent) {
   7948          return aContent.IsHTMLElement(&aNewFormatTagName) ||
   7949                 (!EditorUtils::IsEditableContent(aContent, EditorType::HTML) &&
   7950                  HTMLEditUtils::IsBlockElement(
   7951                      aContent, BlockInlineCheck::UseHTMLDefaultStyle));
   7952        };
   7953 
   7954    const auto IsMozDivOrFormatBlock =
   7955        [&aFormatBlockMode](const nsIContent& aContent) {
   7956          return HTMLEditUtils::IsMozDivElement(aContent) ||
   7957                 HTMLEditor::IsFormatElement(aFormatBlockMode, aContent);
   7958        };
   7959 
   7960    const auto IsNewFormatBlockRequired = [](const nsIContent& aContent) {
   7961      return aContent.IsHTMLElement(nsGkAtoms::table) ||
   7962             HTMLEditUtils::IsListElement(aContent) ||
   7963             aContent.IsAnyOfHTMLElements(
   7964                 nsGkAtoms::tbody, nsGkAtoms::tr, nsGkAtoms::td, nsGkAtoms::li,
   7965                 nsGkAtoms::blockquote, nsGkAtoms::div);
   7966    };
   7967 
   7968    const auto IsMovableInlineContent = [&aNewFormatTagName](
   7969                                            const nsIContent& aContent) {
   7970      return HTMLEditUtils::IsInlineContent(
   7971                 aContent, BlockInlineCheck::UseHTMLDefaultStyle) &&
   7972             // If content is a non editable, drop it if we are going to <pre>.
   7973             !(&aNewFormatTagName == nsGkAtoms::pre &&
   7974               !EditorUtils::IsEditableContent(aContent, EditorType::HTML));
   7975    };
   7976 
   7977    const auto IsMovableInlineContentSibling = [&](const nsIContent& aContent) {
   7978      return !IsSameFormatBlockOrNonEditableBlock(aContent) &&
   7979             !IsMozDivOrFormatBlock(aContent) &&
   7980             !IsNewFormatBlockRequired(aContent) &&
   7981             !aContent.IsHTMLElement(nsGkAtoms::br) &&
   7982             IsMovableInlineContent(aContent);
   7983    };
   7984 
   7985    // Is it already the right kind of block, or an uneditable block?
   7986    if (IsSameFormatBlockOrNonEditableBlock(content)) {
   7987      // Forget any previous block used for previous inline nodes
   7988      curBlock = nullptr;
   7989      pendingBRElementToMoveCurBlock = nullptr;
   7990      // Do nothing to this block
   7991      continue;
   7992    }
   7993 
   7994    // If content is a format element, replace it with a new block of correct
   7995    // type.
   7996    // XXX: pre can't hold everything the others can
   7997    if (IsMozDivOrFormatBlock(content)) {
   7998      // Forget any previous block used for previous inline nodes
   7999      curBlock = nullptr;
   8000      pendingBRElementToMoveCurBlock = nullptr;
   8001      RefPtr<Element> expectedContainerOfNewBlock =
   8002          atContent.IsContainerHTMLElement(nsGkAtoms::dl) &&
   8003                  HTMLEditUtils::IsSplittableNode(
   8004                      *atContent.ContainerAs<Element>())
   8005              ? atContent.GetContainerParentAs<Element>()
   8006              : atContent.GetContainerAs<Element>();
   8007      Result<CreateElementResult, nsresult> replaceWithNewBlockElementResult =
   8008          ReplaceContainerAndCloneAttributesWithTransaction(
   8009              MOZ_KnownLive(*content->AsElement()), aNewFormatTagName);
   8010      if (MOZ_UNLIKELY(replaceWithNewBlockElementResult.isErr())) {
   8011        NS_WARNING(
   8012            "EditorBase::ReplaceContainerAndCloneAttributesWithTransaction() "
   8013            "failed");
   8014        return replaceWithNewBlockElementResult;
   8015      }
   8016      CreateElementResult unwrappedReplaceWithNewBlockElementResult =
   8017          replaceWithNewBlockElementResult.unwrap();
   8018      // If the new block element was moved to different element or removed by
   8019      // the web app via mutation event listener, we should stop handling this
   8020      // action since we cannot handle each of a lot of edge cases.
   8021      if (NS_WARN_IF(unwrappedReplaceWithNewBlockElementResult.GetNewNode()
   8022                         ->GetParentNode() != expectedContainerOfNewBlock)) {
   8023        unwrappedReplaceWithNewBlockElementResult.IgnoreCaretPointSuggestion();
   8024        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   8025      }
   8026      unwrappedReplaceWithNewBlockElementResult.MoveCaretPointTo(
   8027          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   8028      newBlock = unwrappedReplaceWithNewBlockElementResult.UnwrapNewNode();
   8029      continue;
   8030    }
   8031 
   8032    if (IsNewFormatBlockRequired(content)) {
   8033      // Forget any previous block used for previous inline nodes
   8034      curBlock = nullptr;
   8035      pendingBRElementToMoveCurBlock = nullptr;
   8036      // Recursion time
   8037      AutoTArray<OwningNonNull<nsIContent>, 24> childContents;
   8038      HTMLEditUtils::CollectAllChildren(*content, childContents);
   8039      if (!childContents.IsEmpty()) {
   8040        Result<CreateElementResult, nsresult> wrapChildrenInBlockElementResult =
   8041            CreateOrChangeFormatContainerElement(
   8042                childContents, aNewFormatTagName, aFormatBlockMode,
   8043                aEditingHost);
   8044        if (MOZ_UNLIKELY(wrapChildrenInBlockElementResult.isErr())) {
   8045          NS_WARNING(
   8046              "HTMLEditor::CreateOrChangeFormatContainerElement() failed");
   8047          return wrapChildrenInBlockElementResult;
   8048        }
   8049        CreateElementResult unwrappedWrapChildrenInBlockElementResult =
   8050            wrapChildrenInBlockElementResult.unwrap();
   8051        unwrappedWrapChildrenInBlockElementResult.MoveCaretPointTo(
   8052            pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   8053        if (unwrappedWrapChildrenInBlockElementResult.GetNewNode()) {
   8054          blockElementToPutCaret =
   8055              unwrappedWrapChildrenInBlockElementResult.UnwrapNewNode();
   8056        }
   8057        continue;
   8058      }
   8059 
   8060      // Make sure we can put a block here
   8061      Result<CreateElementResult, nsresult> createNewBlockElementResult =
   8062          InsertElementWithSplittingAncestorsWithTransaction(
   8063              aNewFormatTagName, atContent, BRElementNextToSplitPoint::Keep,
   8064              aEditingHost);
   8065      if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) {
   8066        NS_WARNING(
   8067            nsPrintfCString(
   8068                "HTMLEditor::"
   8069                "InsertElementWithSplittingAncestorsWithTransaction(%s) failed",
   8070                nsAtomCString(&aNewFormatTagName).get())
   8071                .get());
   8072        return createNewBlockElementResult;
   8073      }
   8074      CreateElementResult unwrappedCreateNewBlockElementResult =
   8075          createNewBlockElementResult.unwrap();
   8076      unwrappedCreateNewBlockElementResult.MoveCaretPointTo(
   8077          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   8078      MOZ_ASSERT(unwrappedCreateNewBlockElementResult.GetNewNode());
   8079      blockElementToPutCaret =
   8080          unwrappedCreateNewBlockElementResult.UnwrapNewNode();
   8081      continue;
   8082    }
   8083 
   8084    if (content->IsHTMLElement(nsGkAtoms::br)) {
   8085      if (curBlock) {
   8086        if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand) {
   8087          // If the node is a break, we honor it by putting further nodes in a
   8088          // new parent.
   8089 
   8090          // Forget any previous block used for previous inline nodes.
   8091          curBlock = nullptr;
   8092          pendingBRElementToMoveCurBlock = nullptr;
   8093        } else {
   8094          // If the node is a break, we need to move it into end of the curBlock
   8095          // if we'll move following content into curBlock.
   8096          pendingBRElementToMoveCurBlock = content->AsElement();
   8097        }
   8098        // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
   8099        // alive.
   8100        nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
   8101        if (NS_FAILED(rv)) {
   8102          NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   8103          return Err(rv);
   8104        }
   8105        continue;
   8106      }
   8107 
   8108      // The break is the first (or even only) node we encountered.  Create a
   8109      // block for it.
   8110      Result<CreateElementResult, nsresult> createNewBlockElementResult =
   8111          InsertElementWithSplittingAncestorsWithTransaction(
   8112              aNewFormatTagName, atContent, BRElementNextToSplitPoint::Keep,
   8113              aEditingHost);
   8114      if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) {
   8115        NS_WARNING(nsPrintfCString("HTMLEditor::"
   8116                                   "InsertElementWithSplittingAncestorsWith"
   8117                                   "Transaction(%s) failed",
   8118                                   nsAtomCString(&aNewFormatTagName).get())
   8119                       .get());
   8120        return createNewBlockElementResult;
   8121      }
   8122      CreateElementResult unwrappedCreateNewBlockElementResult =
   8123          createNewBlockElementResult.unwrap();
   8124      unwrappedCreateNewBlockElementResult.MoveCaretPointTo(
   8125          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   8126      RefPtr<Element> newBlockElement =
   8127          unwrappedCreateNewBlockElementResult.UnwrapNewNode();
   8128      MOZ_ASSERT(newBlockElement);
   8129      blockElementToPutCaret = newBlockElement;
   8130      // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
   8131      // alive.
   8132      Result<MoveNodeResult, nsresult> moveNodeResult =
   8133          MoveNodeToEndWithTransaction(MOZ_KnownLive(content),
   8134                                       *newBlockElement);
   8135      if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
   8136        NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
   8137        return moveNodeResult.propagateErr();
   8138      }
   8139      MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
   8140      unwrappedMoveNodeResult.MoveCaretPointTo(
   8141          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   8142      curBlock = std::move(newBlockElement);
   8143      continue;
   8144    }
   8145 
   8146    if (!IsMovableInlineContent(content)) {
   8147      // Do nothing to this block
   8148      continue;
   8149    }
   8150    MOZ_ASSERT(IsMovableInlineContentSibling(content));
   8151 
   8152    // If no curBlock, make one
   8153    if (!curBlock) {
   8154      Result<CreateElementResult, nsresult> createNewBlockElementResult =
   8155          InsertElementWithSplittingAncestorsWithTransaction(
   8156              aNewFormatTagName, atContent, BRElementNextToSplitPoint::Keep,
   8157              aEditingHost);
   8158      if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) {
   8159        NS_WARNING(nsPrintfCString("HTMLEditor::"
   8160                                   "InsertElementWithSplittingAncestorsWith"
   8161                                   "Transaction(%s) failed",
   8162                                   nsAtomCString(&aNewFormatTagName).get())
   8163                       .get());
   8164        return createNewBlockElementResult;
   8165      }
   8166      CreateElementResult unwrappedCreateNewBlockElementResult =
   8167          createNewBlockElementResult.unwrap();
   8168      unwrappedCreateNewBlockElementResult.MoveCaretPointTo(
   8169          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   8170      MOZ_ASSERT(unwrappedCreateNewBlockElementResult.GetNewNode());
   8171      blockElementToPutCaret =
   8172          unwrappedCreateNewBlockElementResult.GetNewNode();
   8173      curBlock = unwrappedCreateNewBlockElementResult.UnwrapNewNode();
   8174 
   8175      // Update container of content.
   8176      atContent.Set(content);
   8177      if (NS_WARN_IF(!atContent.IsSet())) {
   8178        // This is possible due to mutation events, let's not assert
   8179        return Err(NS_ERROR_UNEXPECTED);
   8180      }
   8181    } else if (pendingBRElementToMoveCurBlock) {
   8182      Result<CreateElementResult, nsresult> insertBRElementResult =
   8183          InsertNodeWithTransaction<Element>(
   8184              *pendingBRElementToMoveCurBlock,
   8185              EditorDOMPoint::AtEndOf(*curBlock));
   8186      if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
   8187        NS_WARNING("EditorBase::InsertNodeWithTransaction<Element>() failed");
   8188        return insertBRElementResult.propagateErr();
   8189      }
   8190      insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
   8191      pendingBRElementToMoveCurBlock = nullptr;
   8192    }
   8193 
   8194    // This is a continuation of some inline nodes that belong together in
   8195    // the same block item.  Use curBlock.
   8196    const OwningNonNull<nsIContent> lastContent = [&]() {
   8197      nsIContent* lastContent = content;
   8198      for (; i + 1 < aArrayOfContents.Length(); i++) {
   8199        const OwningNonNull<nsIContent>& nextContent = aArrayOfContents[i + 1];
   8200        if (lastContent->GetNextSibling() != nextContent ||
   8201            !IsMovableInlineContentSibling(nextContent)) {
   8202          break;
   8203        }
   8204        lastContent = nextContent;
   8205      }
   8206      return OwningNonNull<nsIContent>(*lastContent);
   8207    }();
   8208    // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
   8209    // alive.  We could try to make that a rvalue ref and create a const array
   8210    // on the stack here, but callers are passing in auto arrays, and we don't
   8211    // want to introduce copies..
   8212    Result<MoveNodeResult, nsresult> moveNodeResult =
   8213        MoveSiblingsToEndWithTransaction(MOZ_KnownLive(content), lastContent,
   8214                                         *curBlock);
   8215    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
   8216      NS_WARNING("HTMLEditor::MoveSiblingsToEndWithTransaction() failed");
   8217      return moveNodeResult.propagateErr();
   8218    }
   8219    MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
   8220    unwrappedMoveNodeResult.MoveCaretPointTo(
   8221        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   8222  }
   8223  return blockElementToPutCaret
   8224             ? CreateElementResult(std::move(blockElementToPutCaret),
   8225                                   std::move(pointToPutCaret))
   8226             : CreateElementResult::NotHandled(std::move(pointToPutCaret));
   8227 }
   8228 
   8229 Result<SplitNodeResult, nsresult>
   8230 HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction(
   8231    const nsAtom& aTag, const EditorDOMPoint& aStartOfDeepestRightNode,
   8232    const Element& aEditingHost) {
   8233  MOZ_ASSERT(IsEditActionDataAvailable());
   8234 
   8235  if (NS_WARN_IF(!aEditingHost.IsInComposedDoc())) {
   8236    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   8237  }
   8238 
   8239  if (NS_WARN_IF(!aStartOfDeepestRightNode.IsSet())) {
   8240    return Err(NS_ERROR_INVALID_ARG);
   8241  }
   8242  MOZ_ASSERT(aStartOfDeepestRightNode.IsSetAndValid());
   8243 
   8244  // The point must be descendant of editing host.
   8245  // XXX Isn't it a valid case if it points a direct child of aEditingHost?
   8246  if (NS_WARN_IF(
   8247          !aStartOfDeepestRightNode.GetContainer()->IsInclusiveDescendantOf(
   8248              &aEditingHost))) {
   8249    return Err(NS_ERROR_INVALID_ARG);
   8250  }
   8251 
   8252  // Look for a node that can legally contain the tag.
   8253  const EditorDOMPoint pointToInsert =
   8254      HTMLEditUtils::GetInsertionPointInInclusiveAncestor(
   8255          aTag, aStartOfDeepestRightNode, &aEditingHost);
   8256  if (MOZ_UNLIKELY(!pointToInsert.IsSet())) {
   8257    NS_WARNING(
   8258        "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() reached "
   8259        "editing host");
   8260    return Err(NS_ERROR_FAILURE);
   8261  }
   8262  // If the point itself can contain the tag, we don't need to split any
   8263  // ancestor nodes.  In this case, we should return the given split point
   8264  // as is.
   8265  if (pointToInsert.GetContainer() == aStartOfDeepestRightNode.GetContainer()) {
   8266    return SplitNodeResult::NotHandled(aStartOfDeepestRightNode);
   8267  }
   8268 
   8269  Result<SplitNodeResult, nsresult> splitNodeResult =
   8270      SplitNodeDeepWithTransaction(MOZ_KnownLive(*pointToInsert.GetChild()),
   8271                                   aStartOfDeepestRightNode,
   8272                                   SplitAtEdges::eAllowToCreateEmptyContainer);
   8273  NS_WARNING_ASSERTION(splitNodeResult.isOk(),
   8274                       "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
   8275                       "eAllowToCreateEmptyContainer) failed");
   8276  return splitNodeResult;
   8277 }
   8278 
   8279 Result<CreateElementResult, nsresult>
   8280 HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(
   8281    const nsAtom& aTagName, const EditorDOMPoint& aPointToInsert,
   8282    BRElementNextToSplitPoint aBRElementNextToSplitPoint,
   8283    const Element& aEditingHost,
   8284    const InitializeInsertingElement& aInitializer) {
   8285  MOZ_ASSERT(aPointToInsert.IsSetAndValid());
   8286 
   8287  const nsCOMPtr<nsIContent> childAtPointToInsert = aPointToInsert.GetChild();
   8288  Result<SplitNodeResult, nsresult> splitNodeResult =
   8289      MaybeSplitAncestorsForInsertWithTransaction(aTagName, aPointToInsert,
   8290                                                  aEditingHost);
   8291  if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
   8292    NS_WARNING(
   8293        "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() failed");
   8294    return splitNodeResult.propagateErr();
   8295  }
   8296  SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
   8297  DebugOnly<bool> wasCaretPositionSuggestedAtSplit =
   8298      unwrappedSplitNodeResult.HasCaretPointSuggestion();
   8299  // We'll update selection below, and nobody touches selection until then.
   8300  // Therefore, we don't need to touch selection here.
   8301  unwrappedSplitNodeResult.IgnoreCaretPointSuggestion();
   8302 
   8303  // If current handling node has been moved from the container by a
   8304  // mutation event listener when we need to do something more for it,
   8305  // we should stop handling this action since we cannot handle each
   8306  // edge case.
   8307  if (childAtPointToInsert &&
   8308      NS_WARN_IF(!childAtPointToInsert->IsInclusiveDescendantOf(
   8309          unwrappedSplitNodeResult.DidSplit()
   8310              ? unwrappedSplitNodeResult.GetNextContent()
   8311              : aPointToInsert.GetContainer()))) {
   8312    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   8313  }
   8314 
   8315  auto splitPoint = unwrappedSplitNodeResult.AtSplitPoint<EditorDOMPoint>();
   8316  if (aBRElementNextToSplitPoint == BRElementNextToSplitPoint::Delete) {
   8317    // Consume a trailing br, if any.  This is to keep an alignment from
   8318    // creating extra lines, if possible.
   8319    if (nsCOMPtr<nsIContent> maybeBRContent = HTMLEditUtils::GetNextContent(
   8320            splitPoint,
   8321            {WalkTreeOption::IgnoreNonEditableNode,
   8322             WalkTreeOption::StopAtBlockBoundary},
   8323            BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)) {
   8324      if (maybeBRContent->IsHTMLElement(nsGkAtoms::br) &&
   8325          splitPoint.GetChild()) {
   8326        // Making use of html structure... if next node after where we are
   8327        // putting our div is not a block, then the br we found is in same
   8328        // block we are, so it's safe to consume it.
   8329        if (nsIContent* nextEditableSibling = HTMLEditUtils::GetNextSibling(
   8330                *splitPoint.GetChild(),
   8331                {WalkTreeOption::IgnoreNonEditableNode})) {
   8332          if (!HTMLEditUtils::IsBlockElement(
   8333                  *nextEditableSibling,
   8334                  BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
   8335            AutoEditorDOMPointChildInvalidator lockOffset(splitPoint);
   8336            nsresult rv = DeleteNodeWithTransaction(*maybeBRContent);
   8337            if (NS_FAILED(rv)) {
   8338              NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   8339              return Err(rv);
   8340            }
   8341          }
   8342        }
   8343      }
   8344    }
   8345  }
   8346 
   8347  Result<CreateElementResult, nsresult> createNewElementResult =
   8348      CreateAndInsertElement(WithTransaction::Yes, aTagName, splitPoint,
   8349                             aInitializer);
   8350  if (MOZ_UNLIKELY(createNewElementResult.isErr())) {
   8351    NS_WARNING(
   8352        "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
   8353    return createNewElementResult;
   8354  }
   8355  MOZ_ASSERT_IF(wasCaretPositionSuggestedAtSplit,
   8356                createNewElementResult.inspect().HasCaretPointSuggestion());
   8357  MOZ_ASSERT(createNewElementResult.inspect().GetNewNode());
   8358 
   8359  // If the new block element was moved to different element or removed by
   8360  // the web app via mutation event listener, we should stop handling this
   8361  // action since we cannot handle each of a lot of edge cases.
   8362  if (NS_WARN_IF(
   8363          createNewElementResult.inspect().GetNewNode()->GetParentNode() !=
   8364          splitPoint.GetContainer())) {
   8365    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   8366  }
   8367 
   8368  return createNewElementResult;
   8369 }
   8370 
   8371 nsresult HTMLEditor::JoinNearestEditableNodesWithTransaction(
   8372    nsIContent& aNodeLeft, nsIContent& aNodeRight,
   8373    EditorDOMPoint* aNewFirstChildOfRightNode) {
   8374  MOZ_ASSERT(IsEditActionDataAvailable());
   8375  MOZ_ASSERT(aNewFirstChildOfRightNode);
   8376 
   8377  // Caller responsible for left and right node being the same type
   8378  if (NS_WARN_IF(!aNodeLeft.GetParentNode())) {
   8379    return NS_ERROR_FAILURE;
   8380  }
   8381  // If they don't have the same parent, first move the right node to after
   8382  // the left one
   8383  if (aNodeLeft.GetParentNode() != aNodeRight.GetParentNode()) {
   8384    Result<MoveNodeResult, nsresult> moveNodeResult =
   8385        MoveNodeWithTransaction(aNodeRight, EditorDOMPoint(&aNodeLeft));
   8386    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
   8387      NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
   8388      return moveNodeResult.unwrapErr();
   8389    }
   8390    nsresult rv = moveNodeResult.inspect().SuggestCaretPointTo(
   8391        *this, {SuggestCaret::OnlyIfHasSuggestion,
   8392                SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   8393                SuggestCaret::AndIgnoreTrivialError});
   8394    if (NS_FAILED(rv)) {
   8395      NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
   8396      return rv;
   8397    }
   8398    NS_WARNING_ASSERTION(
   8399        rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   8400        "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
   8401  }
   8402 
   8403  // Separate join rules for differing blocks
   8404  if (HTMLEditUtils::IsListElement(aNodeLeft) || aNodeLeft.IsText()) {
   8405    // For lists, merge shallow (wouldn't want to combine list items)
   8406    Result<JoinNodesResult, nsresult> joinNodesResult =
   8407        JoinNodesWithTransaction(aNodeLeft, aNodeRight);
   8408    if (MOZ_UNLIKELY(joinNodesResult.isErr())) {
   8409      NS_WARNING("HTMLEditor::JoinNodesWithTransaction failed");
   8410      return joinNodesResult.unwrapErr();
   8411    }
   8412    *aNewFirstChildOfRightNode =
   8413        joinNodesResult.inspect().AtJoinedPoint<EditorDOMPoint>();
   8414    return NS_OK;
   8415  }
   8416 
   8417  // Remember the last left child, and first right child
   8418  nsCOMPtr<nsIContent> lastEditableChildOfLeftContent =
   8419      HTMLEditUtils::GetLastChild(aNodeLeft,
   8420                                  {WalkTreeOption::IgnoreNonEditableNode});
   8421  if (MOZ_UNLIKELY(NS_WARN_IF(!lastEditableChildOfLeftContent))) {
   8422    return NS_ERROR_FAILURE;
   8423  }
   8424 
   8425  nsCOMPtr<nsIContent> firstEditableChildOfRightContent =
   8426      HTMLEditUtils::GetFirstChild(aNodeRight,
   8427                                   {WalkTreeOption::IgnoreNonEditableNode});
   8428  if (NS_WARN_IF(!firstEditableChildOfRightContent)) {
   8429    return NS_ERROR_FAILURE;
   8430  }
   8431 
   8432  // For list items, divs, etc., merge smart
   8433  Result<JoinNodesResult, nsresult> joinNodesResult =
   8434      JoinNodesWithTransaction(aNodeLeft, aNodeRight);
   8435  if (MOZ_UNLIKELY(joinNodesResult.isErr())) {
   8436    NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
   8437    return joinNodesResult.unwrapErr();
   8438  }
   8439 
   8440  if ((lastEditableChildOfLeftContent->IsText() ||
   8441       lastEditableChildOfLeftContent->IsElement()) &&
   8442      HTMLEditUtils::CanContentsBeJoined(*lastEditableChildOfLeftContent,
   8443                                         *firstEditableChildOfRightContent)) {
   8444    nsresult rv = JoinNearestEditableNodesWithTransaction(
   8445        *lastEditableChildOfLeftContent, *firstEditableChildOfRightContent,
   8446        aNewFirstChildOfRightNode);
   8447    NS_WARNING_ASSERTION(
   8448        NS_SUCCEEDED(rv),
   8449        "HTMLEditor::JoinNearestEditableNodesWithTransaction() failed");
   8450    return rv;
   8451  }
   8452  *aNewFirstChildOfRightNode =
   8453      joinNodesResult.inspect().AtJoinedPoint<EditorDOMPoint>();
   8454  return NS_OK;
   8455 }
   8456 
   8457 Element* HTMLEditor::GetMostDistantAncestorMailCiteElement(
   8458    const nsINode& aNode) const {
   8459  Element* mailCiteElement = nullptr;
   8460  const bool isPlaintextEditor = IsPlaintextMailComposer();
   8461  for (Element* element : aNode.InclusiveAncestorsOfType<Element>()) {
   8462    if ((isPlaintextEditor && element->IsHTMLElement(nsGkAtoms::pre)) ||
   8463        HTMLEditUtils::IsMailCiteElement(*element)) {
   8464      mailCiteElement = element;
   8465      continue;
   8466    }
   8467    if (element->IsHTMLElement(nsGkAtoms::body)) {
   8468      break;
   8469    }
   8470  }
   8471  return mailCiteElement;
   8472 }
   8473 
   8474 nsresult HTMLEditor::CacheInlineStyles(Element& Element) {
   8475  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
   8476 
   8477  nsresult rv = GetInlineStyles(
   8478      Element, *TopLevelEditSubActionDataRef().mCachedPendingStyles);
   8479  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   8480                       "HTMLEditor::GetInlineStyles() failed");
   8481  return rv;
   8482 }
   8483 
   8484 nsresult HTMLEditor::GetInlineStyles(
   8485    Element& aElement, AutoPendingStyleCacheArray& aPendingStyleCacheArray) {
   8486  MOZ_ASSERT(IsEditActionDataAvailable());
   8487  MOZ_ASSERT(aPendingStyleCacheArray.IsEmpty());
   8488 
   8489  if (!IsCSSEnabled()) {
   8490    // In the HTML styling mode, we should preserve the order of inline styles
   8491    // specified with HTML elements, then, we can keep same order as original
   8492    // one when we create new elements to apply the styles at new place.
   8493    // XXX Currently, we don't preserve all inline parents, therefore, we cannot
   8494    //     restore all inline elements as-is.  Perhaps, we should store all
   8495    //     inline elements with more details (e.g., all attributes), and store
   8496    //     same elements.  For example, web apps may give style as:
   8497    //     em {
   8498    //       font-style: italic;
   8499    //     }
   8500    //     em em {
   8501    //       font-style: normal;
   8502    //       font-weight: bold;
   8503    //     }
   8504    //     but we cannot restore the style as-is.
   8505    nsString value;
   8506    const bool givenElementIsEditable =
   8507        HTMLEditUtils::IsSimplyEditableNode(aElement);
   8508    auto NeedToAppend = [&](nsStaticAtom& aTagName, nsStaticAtom* aAttribute) {
   8509      if (mPendingStylesToApplyToNewContent->GetStyleState(
   8510              aTagName, aAttribute) != PendingStyleState::NotUpdated) {
   8511        return false;  // The style has already been changed.
   8512      }
   8513      if (aPendingStyleCacheArray.Contains(aTagName, aAttribute)) {
   8514        return false;  // Already preserved
   8515      }
   8516      return true;
   8517    };
   8518    for (Element* const inclusiveAncestor :
   8519         aElement.InclusiveAncestorsOfType<Element>()) {
   8520      if (HTMLEditUtils::IsBlockElement(
   8521              *inclusiveAncestor,
   8522              BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
   8523          (givenElementIsEditable &&
   8524           !HTMLEditUtils::IsSimplyEditableNode(*inclusiveAncestor))) {
   8525        break;
   8526      }
   8527      if (inclusiveAncestor->IsAnyOfHTMLElements(
   8528              nsGkAtoms::b, nsGkAtoms::i, nsGkAtoms::u, nsGkAtoms::s,
   8529              nsGkAtoms::strike, nsGkAtoms::tt, nsGkAtoms::em,
   8530              nsGkAtoms::strong, nsGkAtoms::dfn, nsGkAtoms::code,
   8531              nsGkAtoms::samp, nsGkAtoms::var, nsGkAtoms::cite, nsGkAtoms::abbr,
   8532              nsGkAtoms::acronym, nsGkAtoms::sub, nsGkAtoms::sup)) {
   8533        nsStaticAtom& tagName = const_cast<nsStaticAtom&>(
   8534            *inclusiveAncestor->NodeInfo()->NameAtom()->AsStatic());
   8535        if (NeedToAppend(tagName, nullptr)) {
   8536          aPendingStyleCacheArray.AppendElement(
   8537              PendingStyleCache(tagName, nullptr, EmptyString()));
   8538        }
   8539        continue;
   8540      }
   8541      if (inclusiveAncestor->IsHTMLElement(nsGkAtoms::font)) {
   8542        if (NeedToAppend(*nsGkAtoms::font, nsGkAtoms::face)) {
   8543          inclusiveAncestor->GetAttr(nsGkAtoms::face, value);
   8544          if (!value.IsEmpty()) {
   8545            aPendingStyleCacheArray.AppendElement(
   8546                PendingStyleCache(*nsGkAtoms::font, nsGkAtoms::face, value));
   8547            value.Truncate();
   8548          }
   8549        }
   8550        if (NeedToAppend(*nsGkAtoms::font, nsGkAtoms::size)) {
   8551          inclusiveAncestor->GetAttr(nsGkAtoms::size, value);
   8552          if (!value.IsEmpty()) {
   8553            aPendingStyleCacheArray.AppendElement(
   8554                PendingStyleCache(*nsGkAtoms::font, nsGkAtoms::size, value));
   8555            value.Truncate();
   8556          }
   8557        }
   8558        if (NeedToAppend(*nsGkAtoms::font, nsGkAtoms::color)) {
   8559          inclusiveAncestor->GetAttr(nsGkAtoms::color, value);
   8560          if (!value.IsEmpty()) {
   8561            aPendingStyleCacheArray.AppendElement(
   8562                PendingStyleCache(*nsGkAtoms::font, nsGkAtoms::color, value));
   8563            value.Truncate();
   8564          }
   8565        }
   8566        continue;
   8567      }
   8568    }
   8569    return NS_OK;
   8570  }
   8571 
   8572  for (nsStaticAtom* property : {nsGkAtoms::b,
   8573                                 nsGkAtoms::i,
   8574                                 nsGkAtoms::u,
   8575                                 nsGkAtoms::s,
   8576                                 nsGkAtoms::strike,
   8577                                 nsGkAtoms::face,
   8578                                 nsGkAtoms::size,
   8579                                 nsGkAtoms::color,
   8580                                 nsGkAtoms::tt,
   8581                                 nsGkAtoms::em,
   8582                                 nsGkAtoms::strong,
   8583                                 nsGkAtoms::dfn,
   8584                                 nsGkAtoms::code,
   8585                                 nsGkAtoms::samp,
   8586                                 nsGkAtoms::var,
   8587                                 nsGkAtoms::cite,
   8588                                 nsGkAtoms::abbr,
   8589                                 nsGkAtoms::acronym,
   8590                                 nsGkAtoms::background_color,
   8591                                 nsGkAtoms::sub,
   8592                                 nsGkAtoms::sup}) {
   8593    const EditorInlineStyle style =
   8594        property == nsGkAtoms::face || property == nsGkAtoms::size ||
   8595                property == nsGkAtoms::color
   8596            ? EditorInlineStyle(*nsGkAtoms::font, property)
   8597            : EditorInlineStyle(*property);
   8598    // If type-in state is set, don't intervene
   8599    const PendingStyleState styleState =
   8600        mPendingStylesToApplyToNewContent->GetStyleState(*style.mHTMLProperty,
   8601                                                         style.mAttribute);
   8602    if (styleState != PendingStyleState::NotUpdated) {
   8603      continue;
   8604    }
   8605    bool isSet = false;
   8606    nsString value;  // Don't use nsAutoString here because it requires memcpy
   8607                     // at creating new PendingStyleCache instance.
   8608    // Don't use CSS for <font size>, we don't support it usefully (bug 780035)
   8609    if (property == nsGkAtoms::size) {
   8610      isSet = HTMLEditUtils::IsInlineStyleSetByElement(aElement, style, nullptr,
   8611                                                       &value);
   8612    } else if (style.IsCSSSettable(aElement)) {
   8613      Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
   8614          CSSEditUtils::IsComputedCSSEquivalentTo(*this, aElement, style,
   8615                                                  value);
   8616      if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
   8617        NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
   8618        return isComputedCSSEquivalentToStyleOrError.unwrapErr();
   8619      }
   8620      isSet = isComputedCSSEquivalentToStyleOrError.unwrap();
   8621    }
   8622    if (isSet) {
   8623      aPendingStyleCacheArray.AppendElement(
   8624          style.ToPendingStyleCache(std::move(value)));
   8625    }
   8626  }
   8627  return NS_OK;
   8628 }
   8629 
   8630 nsresult HTMLEditor::ReapplyCachedStyles() {
   8631  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
   8632 
   8633  // The idea here is to examine our cached list of styles and see if any have
   8634  // been removed.  If so, add typeinstate for them, so that they will be
   8635  // reinserted when new content is added.
   8636 
   8637  if (TopLevelEditSubActionDataRef().mCachedPendingStyles->IsEmpty() ||
   8638      !SelectionRef().RangeCount()) {
   8639    return NS_OK;
   8640  }
   8641 
   8642  // remember if we are in css mode
   8643  const bool useCSS = IsCSSEnabled();
   8644 
   8645  const RangeBoundary& atStartOfSelection =
   8646      SelectionRef().GetRangeAt(0)->StartRef();
   8647  const RefPtr<Element> startContainerElement =
   8648      atStartOfSelection.GetContainer() &&
   8649              atStartOfSelection.GetContainer()->IsContent()
   8650          ? atStartOfSelection.GetContainer()->GetAsElementOrParentElement()
   8651          : nullptr;
   8652  if (NS_WARN_IF(!startContainerElement)) {
   8653    return NS_OK;
   8654  }
   8655 
   8656  AutoPendingStyleCacheArray styleCacheArrayAtInsertionPoint;
   8657  nsresult rv =
   8658      GetInlineStyles(*startContainerElement, styleCacheArrayAtInsertionPoint);
   8659  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
   8660    return NS_ERROR_EDITOR_DESTROYED;
   8661  }
   8662  if (NS_FAILED(rv)) {
   8663    NS_WARNING("HTMLEditor::GetInlineStyles() failed, but ignored");
   8664    return NS_OK;
   8665  }
   8666 
   8667  for (PendingStyleCache& styleCacheBeforeEdit :
   8668       Reversed(*TopLevelEditSubActionDataRef().mCachedPendingStyles)) {
   8669    bool isFirst = false, isAny = false, isAll = false;
   8670    nsAutoString currentValue;
   8671    const EditorInlineStyle inlineStyle = styleCacheBeforeEdit.ToInlineStyle();
   8672    if (useCSS && inlineStyle.IsCSSSettable(*startContainerElement)) {
   8673      // check computed style first in css case
   8674      // MOZ_KnownLive(styleCacheBeforeEdit.*) because they are nsStaticAtom
   8675      // and its instances are alive until shutting down.
   8676      Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
   8677          CSSEditUtils::IsComputedCSSEquivalentTo(*this, *startContainerElement,
   8678                                                  inlineStyle, currentValue);
   8679      if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
   8680        NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
   8681        return isComputedCSSEquivalentToStyleOrError.unwrapErr();
   8682      }
   8683      isAny = isComputedCSSEquivalentToStyleOrError.unwrap();
   8684    }
   8685    if (!isAny) {
   8686      // then check typeinstate and html style
   8687      nsresult rv = GetInlinePropertyBase(
   8688          inlineStyle, &styleCacheBeforeEdit.AttributeValueOrCSSValueRef(),
   8689          &isFirst, &isAny, &isAll, &currentValue);
   8690      if (NS_FAILED(rv)) {
   8691        NS_WARNING("HTMLEditor::GetInlinePropertyBase() failed");
   8692        return rv;
   8693      }
   8694    }
   8695    // This style has disappeared through deletion.  Let's add the styles to
   8696    // mPendingStylesToApplyToNewContent when same style isn't applied to the
   8697    // node already.
   8698    if (isAny &&
   8699        !IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
   8700      continue;
   8701    }
   8702    AutoPendingStyleCacheArray::index_type index =
   8703        styleCacheArrayAtInsertionPoint.IndexOf(
   8704            styleCacheBeforeEdit.TagRef(), styleCacheBeforeEdit.GetAttribute());
   8705    if (index == AutoPendingStyleCacheArray::NoIndex ||
   8706        styleCacheBeforeEdit.AttributeValueOrCSSValueRef() !=
   8707            styleCacheArrayAtInsertionPoint.ElementAt(index)
   8708                .AttributeValueOrCSSValueRef()) {
   8709      mPendingStylesToApplyToNewContent->PreserveStyle(styleCacheBeforeEdit);
   8710    }
   8711  }
   8712  return NS_OK;
   8713 }
   8714 
   8715 nsresult HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange(
   8716    const RawRangeBoundary& aStartRef, const RawRangeBoundary& aEndRef) {
   8717  MOZ_ASSERT(IsEditActionDataAvailable());
   8718 
   8719  AutoTArray<OwningNonNull<Element>, 64> arrayOfEmptyElements;
   8720  DOMIterator iter;
   8721  if (NS_FAILED(iter.Init(aStartRef, aEndRef))) {
   8722    NS_WARNING("DOMIterator::Init() failed");
   8723    return NS_ERROR_FAILURE;
   8724  }
   8725  iter.AppendNodesToArray(
   8726      +[](nsINode& aNode, void* aSelf) {
   8727        MOZ_ASSERT(Element::FromNode(&aNode));
   8728        MOZ_ASSERT(aSelf);
   8729        Element& element = *aNode.AsElement();
   8730        if (!EditorUtils::IsEditableContent(element, EditorType::HTML) ||
   8731            (!HTMLEditUtils::IsListItemElement(element) &&
   8732             !HTMLEditUtils::IsTableCellOrCaptionElement(element))) {
   8733          return false;
   8734        }
   8735        return HTMLEditUtils::IsEmptyNode(
   8736            element, {EmptyCheckOption::TreatSingleBRElementAsVisible,
   8737                      EmptyCheckOption::TreatNonEditableContentAsInvisible});
   8738      },
   8739      arrayOfEmptyElements, this);
   8740 
   8741  // Put padding <br> elements for empty <li> and <td>.
   8742  EditorDOMPoint pointToPutCaret;
   8743  for (auto& emptyElement : arrayOfEmptyElements) {
   8744    // Need to put br at END of node.  It may have empty containers in it and
   8745    // still pass the "IsEmptyNode" test, and we want the br's to be after
   8746    // them.  Also, we want the br to be after the selection if the selection
   8747    // is in this node.
   8748    EditorDOMPoint endOfNode(EditorDOMPoint::AtEndOf(emptyElement));
   8749    Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
   8750        InsertPaddingBRElementForEmptyLastLineWithTransaction(endOfNode);
   8751    if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
   8752      NS_WARNING(
   8753          "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
   8754          "failed");
   8755      return insertPaddingBRElementResult.unwrapErr();
   8756    }
   8757    CreateElementResult unwrappedInsertPaddingBRElementResult =
   8758        insertPaddingBRElementResult.unwrap();
   8759    unwrappedInsertPaddingBRElementResult.MoveCaretPointTo(
   8760        pointToPutCaret, *this,
   8761        {SuggestCaret::OnlyIfHasSuggestion,
   8762         SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
   8763  }
   8764  if (pointToPutCaret.IsSet()) {
   8765    nsresult rv = CollapseSelectionTo(pointToPutCaret);
   8766    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
   8767      NS_WARNING(
   8768          "EditorBase::CollapseSelectionTo() caused destroying the editor");
   8769      return NS_ERROR_EDITOR_DESTROYED;
   8770    }
   8771    NS_WARNING_ASSERTION(
   8772        NS_SUCCEEDED(rv),
   8773        "EditorBase::CollapseSelectionTo() failed, but ignored");
   8774  }
   8775  return NS_OK;
   8776 }
   8777 
   8778 void HTMLEditor::SetSelectionInterlinePosition() {
   8779  MOZ_ASSERT(IsEditActionDataAvailable());
   8780  MOZ_ASSERT(SelectionRef().IsCollapsed());
   8781 
   8782  // Get the (collapsed) selection location
   8783  const nsRange* firstRange = SelectionRef().GetRangeAt(0);
   8784  if (NS_WARN_IF(!firstRange)) {
   8785    return;
   8786  }
   8787 
   8788  EditorDOMPoint atCaret(firstRange->StartRef());
   8789  if (NS_WARN_IF(!atCaret.IsSet())) {
   8790    return;
   8791  }
   8792  MOZ_ASSERT(atCaret.IsSetAndValid());
   8793 
   8794  // First, let's check to see if we are after a `<br>`.  We take care of this
   8795  // special-case first so that we don't accidentally fall through into one of
   8796  // the other conditionals.
   8797  // XXX Although I don't understand "interline position", if caret is
   8798  //     immediately after non-editable contents, but previous editable
   8799  //     content is `<br>`, does this do right thing?
   8800  if (Element* editingHost = ComputeEditingHost()) {
   8801    if (nsIContent* previousEditableContentInBlock =
   8802            HTMLEditUtils::GetPreviousContent(
   8803                atCaret,
   8804                {WalkTreeOption::IgnoreNonEditableNode,
   8805                 WalkTreeOption::StopAtBlockBoundary},
   8806                BlockInlineCheck::UseComputedDisplayStyle, editingHost)) {
   8807      if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br)) {
   8808        DebugOnly<nsresult> rvIgnored = SelectionRef().SetInterlinePosition(
   8809            InterlinePosition::StartOfNextLine);
   8810        NS_WARNING_ASSERTION(
   8811            NS_SUCCEEDED(rvIgnored),
   8812            "Selection::SetInterlinePosition(InterlinePosition::"
   8813            "StartOfNextLine) failed, but ignored");
   8814        return;
   8815      }
   8816    }
   8817  }
   8818 
   8819  if (!atCaret.GetChild()) {
   8820    return;
   8821  }
   8822 
   8823  // If caret is immediately after a block, set interline position to "right".
   8824  // XXX Although I don't understand "interline position", if caret is
   8825  //     immediately after non-editable contents, but previous editable
   8826  //     content is a block, does this do right thing?
   8827  if (nsIContent* previousEditableContentInBlockAtCaret =
   8828          HTMLEditUtils::GetPreviousSibling(
   8829              *atCaret.GetChild(), {WalkTreeOption::IgnoreNonEditableNode})) {
   8830    if (HTMLEditUtils::IsBlockElement(
   8831            *previousEditableContentInBlockAtCaret,
   8832            BlockInlineCheck::UseComputedDisplayStyle)) {
   8833      DebugOnly<nsresult> rvIgnored = SelectionRef().SetInterlinePosition(
   8834          InterlinePosition::StartOfNextLine);
   8835      NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   8836                           "Selection::SetInterlinePosition(InterlinePosition::"
   8837                           "StartOfNextLine) failed, but ignored");
   8838      return;
   8839    }
   8840  }
   8841 
   8842  // If caret is immediately before a block, set interline position to "left".
   8843  // XXX Although I don't understand "interline position", if caret is
   8844  //     immediately before non-editable contents, but next editable
   8845  //     content is a block, does this do right thing?
   8846  if (nsIContent* nextEditableContentInBlockAtCaret =
   8847          HTMLEditUtils::GetNextSibling(
   8848              *atCaret.GetChild(), {WalkTreeOption::IgnoreNonEditableNode})) {
   8849    if (HTMLEditUtils::IsBlockElement(
   8850            *nextEditableContentInBlockAtCaret,
   8851            BlockInlineCheck::UseComputedDisplayStyle)) {
   8852      DebugOnly<nsresult> rvIgnored =
   8853          SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine);
   8854      NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
   8855                           "Selection::SetInterlinePosition(InterlinePosition::"
   8856                           "EndOfLine) failed, but ignored");
   8857    }
   8858  }
   8859 }
   8860 
   8861 nsresult HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement(
   8862    nsIEditor::EDirection aDirectionAndAmount) {
   8863  MOZ_ASSERT(IsEditActionDataAvailable());
   8864  MOZ_ASSERT(SelectionRef().IsCollapsed());
   8865 
   8866  auto point = GetFirstSelectionStartPoint<EditorDOMPoint>();
   8867  if (NS_WARN_IF(!point.IsInContentNode())) {
   8868    return NS_ERROR_FAILURE;
   8869  }
   8870 
   8871  // If selection start is not editable, climb up the tree until editable one.
   8872  while (!EditorUtils::IsEditableContent(*point.ContainerAs<nsIContent>(),
   8873                                         EditorType::HTML)) {
   8874    point.Set(point.GetContainer());
   8875    if (NS_WARN_IF(!point.IsInContentNode())) {
   8876      return NS_ERROR_FAILURE;
   8877    }
   8878  }
   8879 
   8880  // If caret is in empty block element, we need to insert a `<br>` element
   8881  // because the block should have one-line height.
   8882  // XXX Even if only a part of the block is editable, shouldn't we put
   8883  //     caret if the block element is now empty?
   8884  if (Element* const editableBlockElement =
   8885          HTMLEditUtils::GetInclusiveAncestorElement(
   8886              *point.ContainerAs<nsIContent>(),
   8887              HTMLEditUtils::ClosestEditableBlockElement,
   8888              BlockInlineCheck::UseComputedDisplayStyle)) {
   8889    if (editableBlockElement &&
   8890        HTMLEditUtils::IsEmptyNode(
   8891            *editableBlockElement,
   8892            {EmptyCheckOption::TreatSingleBRElementAsVisible}) &&
   8893        HTMLEditUtils::CanNodeContain(*point.GetContainer(), *nsGkAtoms::br)) {
   8894      Element* bodyOrDocumentElement = GetRoot();
   8895      if (NS_WARN_IF(!bodyOrDocumentElement)) {
   8896        return NS_ERROR_FAILURE;
   8897      }
   8898      if (point.GetContainer() == bodyOrDocumentElement) {
   8899        // Our root node is completely empty. Don't add a <br> here.
   8900        // AfterEditInner() will add one for us when it calls
   8901        // EditorBase::MaybeCreatePaddingBRElementForEmptyEditor().
   8902        // XXX This kind of dependency between methods makes us spaghetti.
   8903        //     Let's handle it here later.
   8904        // XXX This looks odd check.  If active editing host is not a
   8905        //     `<body>`, what are we doing?
   8906        return NS_OK;
   8907      }
   8908      Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
   8909          InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
   8910      if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
   8911        NS_WARNING(
   8912            "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
   8913            ") failed");
   8914        return insertPaddingBRElementResult.unwrapErr();
   8915      }
   8916      nsresult rv = insertPaddingBRElementResult.inspect().SuggestCaretPointTo(
   8917          *this, {SuggestCaret::OnlyIfHasSuggestion,
   8918                  SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
   8919                  SuggestCaret::AndIgnoreTrivialError});
   8920      if (NS_FAILED(rv)) {
   8921        NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
   8922        return rv;
   8923      }
   8924      NS_WARNING_ASSERTION(
   8925          rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
   8926          "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
   8927      return NS_OK;
   8928    }
   8929  }
   8930 
   8931  // XXX Perhaps, we should do something if we're in a data node but not
   8932  //     a text node.
   8933  if (point.IsInTextNode()) {
   8934    return NS_OK;
   8935  }
   8936 
   8937  // Do we need to insert a padding <br> element for empty last line?  We do
   8938  // if we are:
   8939  // 1) prior node is in same block where selection is AND
   8940  // 2) prior node is a br AND
   8941  // 3) that br is not visible
   8942  RefPtr<Element> editingHost = ComputeEditingHost();
   8943  if (!editingHost) {
   8944    return NS_OK;
   8945  }
   8946 
   8947  if (nsCOMPtr<nsIContent> previousEditableContent =
   8948          HTMLEditUtils::GetPreviousContent(
   8949              point, {WalkTreeOption::IgnoreNonEditableNode},
   8950              BlockInlineCheck::UseComputedDisplayStyle, editingHost)) {
   8951    // If caret and previous editable content are in same block element
   8952    // (even if it's a non-editable element), we should put a padding <br>
   8953    // element at end of the block.
   8954    const Element* const blockElementContainingCaret =
   8955        HTMLEditUtils::GetInclusiveAncestorElement(
   8956            *point.ContainerAs<nsIContent>(),
   8957            HTMLEditUtils::ClosestBlockElement,
   8958            BlockInlineCheck::UseComputedDisplayStyle);
   8959    const Element* const blockElementContainingPreviousEditableContent =
   8960        HTMLEditUtils::GetAncestorElement(
   8961            *previousEditableContent, HTMLEditUtils::ClosestBlockElement,
   8962            BlockInlineCheck::UseComputedDisplayStyle);
   8963    // If previous editable content of caret is in same block and a `<br>`
   8964    // element, we need to adjust interline position.
   8965    if (blockElementContainingCaret &&
   8966        blockElementContainingCaret ==
   8967            blockElementContainingPreviousEditableContent &&
   8968        point.ContainerAs<nsIContent>()->GetEditingHost() ==
   8969            previousEditableContent->GetEditingHost() &&
   8970        previousEditableContent &&
   8971        previousEditableContent->IsHTMLElement(nsGkAtoms::br)) {
   8972      // If it's an invisible `<br>` element, we need to insert a padding
   8973      // `<br>` element for making empty line have one-line height.
   8974      if (HTMLEditUtils::IsInvisibleBRElement(*previousEditableContent) &&
   8975          !EditorUtils::IsPaddingBRElementForEmptyLastLine(
   8976              *previousEditableContent)) {
   8977        AutoEditorDOMPointChildInvalidator lockOffset(point);
   8978        Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
   8979            InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
   8980        if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
   8981          NS_WARNING(
   8982              "HTMLEditor::"
   8983              "InsertPaddingBRElementForEmptyLastLineWithTransaction() failed");
   8984          return insertPaddingBRElementResult.unwrapErr();
   8985        }
   8986        insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
   8987        nsresult rv = CollapseSelectionTo(EditorRawDOMPoint(
   8988            insertPaddingBRElementResult.inspect().GetNewNode(),
   8989            InterlinePosition::StartOfNextLine));
   8990        if (NS_FAILED(rv)) {
   8991          NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   8992          return rv;
   8993        }
   8994      }
   8995      // If it's a visible `<br>` element and next editable content is a
   8996      // padding `<br>` element, we need to set interline position.
   8997      else if (nsIContent* nextEditableContentInBlock =
   8998                   HTMLEditUtils::GetNextContent(
   8999                       *previousEditableContent,
   9000                       {WalkTreeOption::IgnoreNonEditableNode,
   9001                        WalkTreeOption::StopAtBlockBoundary},
   9002                       BlockInlineCheck::UseComputedDisplayStyle,
   9003                       editingHost)) {
   9004        if (EditorUtils::IsPaddingBRElementForEmptyLastLine(
   9005                *nextEditableContentInBlock)) {
   9006          // Make it stick to the padding `<br>` element so that it will be
   9007          // on blank line.
   9008          DebugOnly<nsresult> rvIgnored = SelectionRef().SetInterlinePosition(
   9009              InterlinePosition::StartOfNextLine);
   9010          NS_WARNING_ASSERTION(
   9011              NS_SUCCEEDED(rvIgnored),
   9012              "Selection::SetInterlinePosition(InterlinePosition::"
   9013              "StartOfNextLine) failed, but ignored");
   9014        }
   9015      }
   9016    }
   9017  }
   9018 
   9019  // If previous editable content in same block is `<br>`, text node, `<img>`
   9020  //  or `<hr>`, current caret position is fine.
   9021  if (nsIContent* const previousEditableContentInBlock =
   9022          HTMLEditUtils::GetPreviousContent(
   9023              point,
   9024              {WalkTreeOption::IgnoreNonEditableNode,
   9025               WalkTreeOption::StopAtBlockBoundary},
   9026              BlockInlineCheck::UseComputedDisplayStyle, editingHost)) {
   9027    if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br) ||
   9028        previousEditableContentInBlock->IsText() ||
   9029        HTMLEditUtils::IsImageElement(*previousEditableContentInBlock) ||
   9030        previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::hr)) {
   9031      return NS_OK;
   9032    }
   9033  }
   9034 
   9035  // If next editable content in same block is `<br>`, text node, `<img>` or
   9036  // `<hr>`, current caret position is fine.
   9037  if (nsIContent* nextEditableContentInBlock = HTMLEditUtils::GetNextContent(
   9038          point,
   9039          {WalkTreeOption::IgnoreNonEditableNode,
   9040           WalkTreeOption::StopAtBlockBoundary},
   9041          BlockInlineCheck::UseComputedDisplayStyle, editingHost)) {
   9042    if (nextEditableContentInBlock->IsText() ||
   9043        nextEditableContentInBlock->IsAnyOfHTMLElements(
   9044            nsGkAtoms::br, nsGkAtoms::img, nsGkAtoms::hr)) {
   9045      return NS_OK;
   9046    }
   9047  }
   9048 
   9049  // Otherwise, look for a near editable content towards edit action direction.
   9050 
   9051  // If there is no editable content, keep current caret position.
   9052  // XXX Why do we treat `nsIEditor::ePreviousWord` etc as forward direction?
   9053  nsIContent* nearEditableContent = HTMLEditUtils::GetAdjacentContentToPutCaret(
   9054      point,
   9055      aDirectionAndAmount == nsIEditor::ePrevious ? WalkTreeDirection::Backward
   9056                                                  : WalkTreeDirection::Forward,
   9057      *editingHost);
   9058  if (!nearEditableContent) {
   9059    return NS_OK;
   9060  }
   9061 
   9062  EditorRawDOMPoint pointToPutCaret =
   9063      HTMLEditUtils::GetGoodCaretPointFor<EditorRawDOMPoint>(
   9064          *nearEditableContent, aDirectionAndAmount);
   9065  if (!pointToPutCaret.IsSet()) {
   9066    NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed");
   9067    return NS_ERROR_FAILURE;
   9068  }
   9069  nsresult rv = CollapseSelectionTo(pointToPutCaret);
   9070  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   9071                       "EditorBase::CollapseSelectionTo() failed");
   9072  return rv;
   9073 }
   9074 
   9075 nsresult HTMLEditor::RemoveEmptyNodesIn(const EditorDOMRange& aRange) {
   9076  MOZ_ASSERT(IsEditActionDataAvailable());
   9077  MOZ_ASSERT(aRange.IsPositioned());
   9078 
   9079  // Some general notes on the algorithm used here: the goal is to examine all
   9080  // the nodes in aRange, and remove the empty ones.  We do this by
   9081  // using a content iterator to traverse all the nodes in the range, and
   9082  // placing the empty nodes into an array.  After finishing the iteration,
   9083  // we delete the empty nodes in the array.  (They cannot be deleted as we
   9084  // find them because that would invalidate the iterator.)
   9085  //
   9086  // Since checking to see if a node is empty can be costly for nodes with
   9087  // many descendants, there are some optimizations made.  I rely on the fact
   9088  // that the iterator is post-order: it will visit children of a node before
   9089  // visiting the parent node.  So if I find that a child node is not empty, I
   9090  // know that its parent is not empty without even checking.  So I put the
   9091  // parent on a "skipList" which is just a voidArray of nodes I can skip the
   9092  // empty check on.  If I encounter a node on the skiplist, i skip the
   9093  // processing for that node and replace its slot in the skiplist with that
   9094  // node's parent.
   9095  //
   9096  // An interesting idea is to go ahead and regard parent nodes that are NOT
   9097  // on the skiplist as being empty (without even doing the IsEmptyNode check)
   9098  // on the theory that if they weren't empty, we would have encountered a
   9099  // non-empty child earlier and thus put this parent node on the skiplist.
   9100  //
   9101  // Unfortunately I can't use that strategy here, because the range may
   9102  // include some children of a node while excluding others.  Thus I could
   9103  // find all the _examined_ children empty, but still not have an empty
   9104  // parent.
   9105 
   9106  const RawRangeBoundary endOfRange = [&]() {
   9107    // If the range is not collapsed and end of the range is start of a
   9108    // container, it means that the inclusive ancestor empty element may be
   9109    // created by splitting the left nodes.
   9110    if (aRange.Collapsed() || !aRange.IsInContentNodes() ||
   9111        !aRange.EndRef().IsStartOfContainer()) {
   9112      return aRange.EndRef().ToRawRangeBoundary();
   9113    }
   9114    nsINode* const commonAncestor =
   9115        nsContentUtils::GetClosestCommonInclusiveAncestor(
   9116            aRange.StartRef().ContainerAs<nsIContent>(),
   9117            aRange.EndRef().ContainerAs<nsIContent>());
   9118    if (!commonAncestor) {
   9119      return aRange.EndRef().ToRawRangeBoundary();
   9120    }
   9121    nsIContent* maybeRightContent = nullptr;
   9122    for (nsIContent* content : aRange.EndRef()
   9123                                   .ContainerAs<nsIContent>()
   9124                                   ->InclusiveAncestorsOfType<nsIContent>()) {
   9125      if (!HTMLEditUtils::IsSimplyEditableNode(*content) ||
   9126          content == commonAncestor) {
   9127        break;
   9128      }
   9129      if (aRange.StartRef().ContainerAs<nsIContent>() == content) {
   9130        break;
   9131      }
   9132      EmptyCheckOptions options = {
   9133          EmptyCheckOption::TreatListItemAsVisible,
   9134          EmptyCheckOption::TreatTableCellAsVisible,
   9135          EmptyCheckOption::TreatNonEditableContentAsInvisible};
   9136      if (!HTMLEditUtils::IsBlockElement(
   9137              *content, BlockInlineCheck::UseComputedDisplayStyle)) {
   9138        options += EmptyCheckOption::TreatSingleBRElementAsVisible;
   9139      }
   9140      if (!HTMLEditUtils::IsEmptyNode(*content, options)) {
   9141        break;
   9142      }
   9143      maybeRightContent = content;
   9144    }
   9145    if (!maybeRightContent) {
   9146      return aRange.EndRef().ToRawRangeBoundary();
   9147    }
   9148    return EditorRawDOMPoint::After(*maybeRightContent).ToRawRangeBoundary();
   9149  }();
   9150 
   9151  PostContentIterator postOrderIter;
   9152  nsresult rv =
   9153      postOrderIter.Init(aRange.StartRef().ToRawRangeBoundary(), endOfRange);
   9154  if (NS_FAILED(rv)) {
   9155    NS_WARNING("PostContentIterator::Init() failed");
   9156    return rv;
   9157  }
   9158 
   9159  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfEmptyContents,
   9160      arrayOfEmptyCites;
   9161 
   9162  // Collect empty nodes first.
   9163  {
   9164    const bool isMailEditor = IsMailEditor();
   9165    AutoTArray<OwningNonNull<nsIContent>, 64> knownNonEmptyContents;
   9166    Maybe<AutoClonedSelectionRangeArray> maybeSelectionRanges;
   9167    for (; !postOrderIter.IsDone(); postOrderIter.Next()) {
   9168      MOZ_ASSERT(postOrderIter.GetCurrentNode()->IsContent());
   9169 
   9170      nsIContent* content = postOrderIter.GetCurrentNode()->AsContent();
   9171      nsIContent* parentContent = content->GetParent();
   9172 
   9173      size_t idx = knownNonEmptyContents.IndexOf(content);
   9174      if (idx != decltype(knownNonEmptyContents)::NoIndex) {
   9175        // This node is on our skip list.  Skip processing for this node, and
   9176        // replace its value in the skip list with the value of its parent
   9177        if (parentContent) {
   9178          knownNonEmptyContents[idx] = parentContent;
   9179        }
   9180        continue;
   9181      }
   9182 
   9183      const bool isEmptyNode = [&]() {
   9184        if (!content->IsElement()) {
   9185          return false;
   9186        }
   9187        Element& element = *content->AsElement();
   9188        const bool isMailCite =
   9189            isMailEditor && HTMLEditUtils::IsMailCiteElement(element);
   9190        const bool isCandidate = [&]() {
   9191          if (element.IsHTMLElement(nsGkAtoms::body)) {
   9192            // Don't delete the body
   9193            return false;
   9194          }
   9195          if (isMailCite || element.IsHTMLElement(nsGkAtoms::a) ||
   9196              HTMLEditUtils::IsInlineStyleElement(element) ||
   9197              HTMLEditUtils::IsListElement(element) ||
   9198              element.IsHTMLElement(nsGkAtoms::div)) {
   9199            // Only consider certain nodes to be empty for purposes of removal
   9200            return true;
   9201          }
   9202          if (HTMLEditUtils::IsFormatElementForFormatBlockCommand(element) ||
   9203              HTMLEditUtils::IsListItemElement(element) ||
   9204              element.IsHTMLElement(nsGkAtoms::blockquote)) {
   9205            // These node types are candidates if selection is not in them.  If
   9206            // it is one of these, don't delete if selection inside.  This is so
   9207            // we can create empty headings, etc., for the user to type into.
   9208            if (maybeSelectionRanges.isNothing()) {
   9209              maybeSelectionRanges.emplace(SelectionRef());
   9210            }
   9211            return !maybeSelectionRanges
   9212                        ->IsAtLeastOneContainerOfRangeBoundariesInclusiveDescendantOf(
   9213                            element);
   9214          }
   9215          return false;
   9216        }();
   9217 
   9218        if (!isCandidate) {
   9219          return false;
   9220        }
   9221 
   9222        // We delete mailcites even if they have a solo br in them.  Other
   9223        // nodes we require to be empty.
   9224        HTMLEditUtils::EmptyCheckOptions options{
   9225            EmptyCheckOption::TreatListItemAsVisible,
   9226            EmptyCheckOption::TreatTableCellAsVisible};
   9227        if (!isMailCite) {
   9228          options += EmptyCheckOption::TreatSingleBRElementAsVisible;
   9229        } else {
   9230          // XXX Maybe unnecessary to specify this.
   9231          options += EmptyCheckOption::TreatNonEditableContentAsInvisible;
   9232        }
   9233        if (!HTMLEditUtils::IsEmptyNode(*content, options)) {
   9234          return false;
   9235        }
   9236 
   9237        if (isMailCite) {
   9238          // mailcites go on a separate list from other empty nodes
   9239          arrayOfEmptyCites.AppendElement(*content);
   9240        }
   9241        // Don't delete non-editable nodes in this method because this is a
   9242        // clean up method to remove unnecessary nodes of the result of
   9243        // editing.  So, we shouldn't delete non-editable nodes which were
   9244        // there before editing.  Additionally, if the element is some special
   9245        // elements such as <body>, we shouldn't delete it.
   9246        else if (HTMLEditUtils::IsSimplyEditableNode(*content) &&
   9247                 HTMLEditUtils::IsRemovableNode(*content)) {
   9248          arrayOfEmptyContents.AppendElement(*content);
   9249        }
   9250        return true;
   9251      }();
   9252      if (!isEmptyNode && parentContent) {
   9253        knownNonEmptyContents.AppendElement(*parentContent);
   9254      }
   9255    }  // end of the for-loop iterating with postOrderIter
   9256  }
   9257 
   9258  // now delete the empty nodes
   9259  for (OwningNonNull<nsIContent>& emptyContent : arrayOfEmptyContents) {
   9260    // MOZ_KnownLive due to bug 1622253
   9261    nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(emptyContent));
   9262    if (NS_FAILED(rv)) {
   9263      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   9264      return rv;
   9265    }
   9266  }
   9267 
   9268  // Now delete the empty mailcites.  This is a separate step because we want
   9269  // to pull out any br's and preserve them.
   9270  EditorDOMPoint pointToPutCaret;
   9271  for (OwningNonNull<nsIContent>& emptyCite : arrayOfEmptyCites) {
   9272    if (!HTMLEditUtils::IsEmptyNode(
   9273            emptyCite,
   9274            {EmptyCheckOption::TreatSingleBRElementAsVisible,
   9275             EmptyCheckOption::TreatListItemAsVisible,
   9276             EmptyCheckOption::TreatTableCellAsVisible,
   9277             EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
   9278      // We are deleting a cite that has just a `<br>`.  We want to delete cite,
   9279      // but preserve `<br>`.
   9280      Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError =
   9281          InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement,
   9282                          EditorDOMPoint(emptyCite));
   9283      if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) {
   9284        NS_WARNING(
   9285            "HTMLEditor::InsertLineBreak(WithTransaction::Yes, "
   9286            "LineBreakType::BRElement) failed");
   9287        return insertBRElementResultOrError.unwrapErr();
   9288      }
   9289      CreateLineBreakResult insertBRElementResult =
   9290          insertBRElementResultOrError.unwrap();
   9291      MOZ_ASSERT(insertBRElementResult.Handled());
   9292      // XXX Is this intentional selection change?
   9293      insertBRElementResult.MoveCaretPointTo(
   9294          pointToPutCaret, *this,
   9295          {SuggestCaret::OnlyIfHasSuggestion,
   9296           SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
   9297    }
   9298    // MOZ_KnownLive because 'arrayOfEmptyCites' is guaranteed to keep it alive.
   9299    nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(emptyCite));
   9300    if (NS_FAILED(rv)) {
   9301      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   9302      return rv;
   9303    }
   9304  }
   9305  // XXX Is this intentional selection change?
   9306  if (pointToPutCaret.IsSet()) {
   9307    nsresult rv = CollapseSelectionTo(pointToPutCaret);
   9308    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
   9309      NS_WARNING(
   9310          "EditorBase::CollapseSelectionTo() caused destroying the editor");
   9311      return NS_ERROR_EDITOR_DESTROYED;
   9312    }
   9313    NS_WARNING_ASSERTION(
   9314        NS_SUCCEEDED(rv),
   9315        "EditorBase::CollapseSelectionTo() failed, but ignored");
   9316  }
   9317 
   9318  return NS_OK;
   9319 }
   9320 
   9321 nsresult HTMLEditor::LiftUpListItemElement(
   9322    Element& aListItemElement,
   9323    LiftUpFromAllParentListElements aLiftUpFromAllParentListElements) {
   9324  MOZ_ASSERT(IsEditActionDataAvailable());
   9325 
   9326  if (!HTMLEditUtils::IsListItemElement(aListItemElement)) {
   9327    return NS_ERROR_INVALID_ARG;
   9328  }
   9329 
   9330  if (NS_WARN_IF(!aListItemElement.GetParentElement()) ||
   9331      NS_WARN_IF(!aListItemElement.GetParentElement()->GetParentNode())) {
   9332    return NS_ERROR_FAILURE;
   9333  }
   9334 
   9335  // if it's first or last list item, don't need to split the list
   9336  // otherwise we do.
   9337  const bool isFirstListItem = HTMLEditUtils::IsFirstChild(
   9338      aListItemElement, {WalkTreeOption::IgnoreNonEditableNode});
   9339  const bool isLastListItem = HTMLEditUtils::IsLastChild(
   9340      aListItemElement, {WalkTreeOption::IgnoreNonEditableNode});
   9341 
   9342  Element* leftListElement = aListItemElement.GetParentElement();
   9343  if (NS_WARN_IF(!leftListElement)) {
   9344    return NS_ERROR_FAILURE;
   9345  }
   9346 
   9347  // If it's at middle of parent list element, split the parent list element.
   9348  // Then, aListItem becomes the first list item of the right list element.
   9349  if (!isFirstListItem && !isLastListItem) {
   9350    EditorDOMPoint atListItemElement(&aListItemElement);
   9351    if (NS_WARN_IF(!atListItemElement.IsSet())) {
   9352      return NS_ERROR_FAILURE;
   9353    }
   9354    MOZ_ASSERT(atListItemElement.IsSetAndValid());
   9355    Result<SplitNodeResult, nsresult> splitListItemParentResult =
   9356        SplitNodeWithTransaction(atListItemElement);
   9357    if (MOZ_UNLIKELY(splitListItemParentResult.isErr())) {
   9358      NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
   9359      return splitListItemParentResult.unwrapErr();
   9360    }
   9361    nsresult rv = splitListItemParentResult.inspect().SuggestCaretPointTo(
   9362        *this, {SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
   9363    if (NS_FAILED(rv)) {
   9364      NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
   9365      return rv;
   9366    }
   9367 
   9368    leftListElement =
   9369        splitListItemParentResult.inspect().GetPreviousContentAs<Element>();
   9370    if (MOZ_UNLIKELY(!leftListElement)) {
   9371      NS_WARNING(
   9372          "HTMLEditor::SplitNodeWithTransaction() didn't return left list "
   9373          "element");
   9374      return NS_ERROR_FAILURE;
   9375    }
   9376  }
   9377 
   9378  // In most cases, insert the list item into the new left list node..
   9379  EditorDOMPoint pointToInsertListItem(leftListElement);
   9380  if (NS_WARN_IF(!pointToInsertListItem.IsInContentNode())) {
   9381    return NS_ERROR_FAILURE;
   9382  }
   9383 
   9384  // But when the list item was the first child of the right list, it should
   9385  // be inserted between the both list elements.  This allows user to hit
   9386  // Enter twice at a list item breaks the parent list node.
   9387  if (!isFirstListItem) {
   9388    DebugOnly<bool> advanced = pointToInsertListItem.AdvanceOffset();
   9389    NS_WARNING_ASSERTION(advanced,
   9390                         "Failed to advance offset to right list node");
   9391  }
   9392 
   9393  EditorDOMPoint pointToPutCaret;
   9394  {
   9395    Result<MoveNodeResult, nsresult> moveListItemElementResult =
   9396        MoveNodeWithTransaction(aListItemElement, pointToInsertListItem);
   9397    if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
   9398      NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
   9399      return moveListItemElementResult.unwrapErr();
   9400    }
   9401    MoveNodeResult unwrappedMoveListItemElementResult =
   9402        moveListItemElementResult.unwrap();
   9403    unwrappedMoveListItemElementResult.MoveCaretPointTo(
   9404        pointToPutCaret, *this,
   9405        {SuggestCaret::OnlyIfHasSuggestion,
   9406         SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
   9407  }
   9408 
   9409  // Unwrap list item contents if they are no longer in a list
   9410  // XXX If the parent list element is a child of another list element
   9411  //     (although invalid tree), the list item element won't be unwrapped.
   9412  //     That makes the parent ancestor element tree valid, but might be
   9413  //     unexpected result.
   9414  // XXX If aListItemElement is <dl> or <dd> and current parent is <ul> or <ol>,
   9415  //     the list items won't be unwrapped.  If aListItemElement is <li> and its
   9416  //     current parent is <dl>, there is same issue.
   9417  if (!HTMLEditUtils::IsListElement(
   9418          *pointToInsertListItem.ContainerAs<nsIContent>()) &&
   9419      HTMLEditUtils::IsListItemElement(aListItemElement)) {
   9420    Result<EditorDOMPoint, nsresult> unwrapOrphanListItemElementResult =
   9421        RemoveBlockContainerWithTransaction(aListItemElement);
   9422    if (MOZ_UNLIKELY(unwrapOrphanListItemElementResult.isErr())) {
   9423      NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
   9424      return unwrapOrphanListItemElementResult.unwrapErr();
   9425    }
   9426    if (AllowsTransactionsToChangeSelection() &&
   9427        unwrapOrphanListItemElementResult.inspect().IsSet()) {
   9428      pointToPutCaret = unwrapOrphanListItemElementResult.unwrap();
   9429    }
   9430    if (!pointToPutCaret.IsSet()) {
   9431      return NS_OK;
   9432    }
   9433    nsresult rv = CollapseSelectionTo(pointToPutCaret);
   9434    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   9435                         "EditorBase::CollapseSelectionTo() failed");
   9436    return rv;
   9437  }
   9438 
   9439  if (pointToPutCaret.IsSet()) {
   9440    nsresult rv = CollapseSelectionTo(pointToPutCaret);
   9441    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
   9442      NS_WARNING("EditorBase::CollapseSelectionTo() failed");
   9443      return rv;
   9444    }
   9445    NS_WARNING_ASSERTION(
   9446        NS_SUCCEEDED(rv),
   9447        "EditorBase::CollapseSelectionTo() failed, but ignored");
   9448  }
   9449 
   9450  if (aLiftUpFromAllParentListElements == LiftUpFromAllParentListElements::No) {
   9451    return NS_OK;
   9452  }
   9453  // XXX If aListItemElement is moved to unexpected element by mutation event
   9454  //     listener, shouldn't we stop calling this?
   9455  nsresult rv = LiftUpListItemElement(aListItemElement,
   9456                                      LiftUpFromAllParentListElements::Yes);
   9457  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   9458                       "HTMLEditor::LiftUpListItemElement("
   9459                       "LiftUpFromAllParentListElements::Yes) failed");
   9460  return rv;
   9461 }
   9462 
   9463 nsresult HTMLEditor::DestroyListStructureRecursively(Element& aListElement) {
   9464  MOZ_ASSERT(IsEditActionDataAvailable());
   9465  MOZ_ASSERT(HTMLEditUtils::IsListElement(aListElement));
   9466 
   9467  // XXX If mutation event listener inserts new child into `aListElement`,
   9468  //     this becomes infinite loop so that we should set limit of the
   9469  //     loop count from original child count.
   9470  while (aListElement.GetFirstChild()) {
   9471    const OwningNonNull<nsIContent> child = *aListElement.GetFirstChild();
   9472 
   9473    if (HTMLEditUtils::IsListItemElement(*child)) {
   9474      // XXX Using LiftUpListItemElement() is too expensive for this purpose.
   9475      //     Looks like the reason why this method uses it is, only this loop
   9476      //     wants to work with first child of aListElement.  However, what it
   9477      //     actually does is removing <li> as container.  Perhaps, we should
   9478      //     decide destination first, and then, move contents in `child`.
   9479      // XXX If aListElement is is a child of another list element (although
   9480      //     it's invalid tree), this moves the list item to outside of
   9481      //     aListElement's parent.  Is that really intentional behavior?
   9482      nsresult rv = LiftUpListItemElement(
   9483          MOZ_KnownLive(*child->AsElement()),
   9484          HTMLEditor::LiftUpFromAllParentListElements::Yes);
   9485      if (NS_FAILED(rv)) {
   9486        NS_WARNING(
   9487            "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
   9488            ":Yes) failed");
   9489        return rv;
   9490      }
   9491      continue;
   9492    }
   9493 
   9494    if (HTMLEditUtils::IsListElement(*child)) {
   9495      nsresult rv =
   9496          DestroyListStructureRecursively(MOZ_KnownLive(*child->AsElement()));
   9497      if (NS_FAILED(rv)) {
   9498        NS_WARNING("HTMLEditor::DestroyListStructureRecursively() failed");
   9499        return rv;
   9500      }
   9501      continue;
   9502    }
   9503 
   9504    // Delete any non-list items for now
   9505    // XXX This is not HTML5 aware.  HTML5 allows all list elements to have
   9506    //     <script> and <template> and <dl> element to have <div> to group
   9507    //     some <dt> and <dd> elements.  So, this may break valid children.
   9508    nsresult rv = DeleteNodeWithTransaction(*child);
   9509    if (NS_FAILED(rv)) {
   9510      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   9511      return rv;
   9512    }
   9513  }
   9514 
   9515  // Delete the now-empty list
   9516  const Result<EditorDOMPoint, nsresult> unwrapListElementResult =
   9517      RemoveBlockContainerWithTransaction(aListElement);
   9518  if (MOZ_UNLIKELY(unwrapListElementResult.isErr())) {
   9519    NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
   9520    return unwrapListElementResult.inspectErr();
   9521  }
   9522  const EditorDOMPoint& pointToPutCaret = unwrapListElementResult.inspect();
   9523  if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret.IsSet()) {
   9524    return NS_OK;
   9525  }
   9526  nsresult rv = CollapseSelectionTo(pointToPutCaret);
   9527  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   9528                       "EditorBase::CollapseSelectionTo() failed");
   9529  return rv;
   9530 }
   9531 
   9532 nsresult HTMLEditor::EnsureSelectionInBodyOrDocumentElement() {
   9533  MOZ_ASSERT(IsEditActionDataAvailable());
   9534 
   9535  RefPtr<Element> bodyOrDocumentElement = GetRoot();
   9536  if (NS_WARN_IF(!bodyOrDocumentElement)) {
   9537    return NS_ERROR_FAILURE;
   9538  }
   9539 
   9540  const auto atCaret = GetFirstSelectionStartPoint<EditorRawDOMPoint>();
   9541  if (NS_WARN_IF(!atCaret.IsSet())) {
   9542    return NS_ERROR_FAILURE;
   9543  }
   9544 
   9545  // XXX This does wrong things.  Web apps can put any elements as sibling
   9546  //     of `<body>` element.  Therefore, this collapses `Selection` into
   9547  //     the `<body>` element which `HTMLDocument.body` is set to.  So,
   9548  //     this makes users impossible to modify content outside of the
   9549  //     `<body>` element even if caret is in an editing host.
   9550 
   9551  // Check that selection start container is inside the <body> element.
   9552  // XXXsmaug this code is insane.
   9553  nsINode* temp = atCaret.GetContainer();
   9554  while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) {
   9555    temp = temp->GetParentOrShadowHostNode();
   9556  }
   9557 
   9558  // If we aren't in the <body> element, force the issue.
   9559  if (!temp) {
   9560    nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
   9561    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
   9562      NS_WARNING(
   9563          "EditorBase::CollapseSelectionToStartOf() caused destroying the "
   9564          "editor");
   9565      return NS_ERROR_EDITOR_DESTROYED;
   9566    }
   9567    NS_WARNING_ASSERTION(
   9568        NS_SUCCEEDED(rv),
   9569        "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
   9570    return NS_OK;
   9571  }
   9572 
   9573  const auto selectionEndPoint = GetFirstSelectionEndPoint<EditorRawDOMPoint>();
   9574  if (NS_WARN_IF(!selectionEndPoint.IsSet())) {
   9575    return NS_ERROR_FAILURE;
   9576  }
   9577 
   9578  // check that selNode is inside body
   9579  // XXXsmaug this code is insane.
   9580  temp = selectionEndPoint.GetContainer();
   9581  while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) {
   9582    temp = temp->GetParentOrShadowHostNode();
   9583  }
   9584 
   9585  // If we aren't in the <body> element, force the issue.
   9586  if (!temp) {
   9587    nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
   9588    if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
   9589      NS_WARNING(
   9590          "EditorBase::CollapseSelectionToStartOf() caused destroying the "
   9591          "editor");
   9592      return NS_ERROR_EDITOR_DESTROYED;
   9593    }
   9594    NS_WARNING_ASSERTION(
   9595        NS_SUCCEEDED(rv),
   9596        "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
   9597  }
   9598 
   9599  return NS_OK;
   9600 }
   9601 
   9602 Result<CreateLineBreakResult, nsresult>
   9603 HTMLEditor::InsertPaddingBRElementIfInEmptyBlock(
   9604    const EditorDOMPoint& aPoint,
   9605    nsIEditor::EStripWrappers aDeleteEmptyInlines) {
   9606  MOZ_ASSERT(IsEditActionDataAvailable());
   9607 
   9608  if (MOZ_UNLIKELY(!aPoint.IsInContentNode())) {
   9609    return CreateLineBreakResult::NotHandled();
   9610  }
   9611 
   9612  const RefPtr<Element> editableBlockElement =
   9613      HTMLEditUtils::GetInclusiveAncestorElement(
   9614          *aPoint.ContainerAs<nsIContent>(),
   9615          HTMLEditUtils::ClosestEditableBlockElement,
   9616          BlockInlineCheck::UseComputedDisplayStyle);
   9617 
   9618  if (!editableBlockElement ||
   9619      !HTMLEditUtils::IsEmptyNode(
   9620          *editableBlockElement,
   9621          {EmptyCheckOption::TreatSingleBRElementAsVisible,
   9622           EmptyCheckOption::TreatBlockAsVisible})) {
   9623    return CreateLineBreakResult::NotHandled();
   9624  }
   9625 
   9626  EditorDOMPoint pointToInsertLineBreak;
   9627  if (aDeleteEmptyInlines == nsIEditor::eStrip &&
   9628      aPoint.ContainerAs<nsIContent>() != editableBlockElement) {
   9629    nsCOMPtr<nsIContent> emptyInlineAncestor =
   9630        HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
   9631            *aPoint.ContainerAs<nsIContent>(),
   9632            BlockInlineCheck::UseComputedDisplayStyle);
   9633    if (!emptyInlineAncestor) {
   9634      emptyInlineAncestor = aPoint.ContainerAs<nsIContent>();
   9635    }
   9636    nsresult rv = DeleteNodeWithTransaction(*emptyInlineAncestor);
   9637    if (NS_FAILED(rv)) {
   9638      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   9639      return Err(rv);
   9640    }
   9641    pointToInsertLineBreak = EditorDOMPoint(editableBlockElement, 0u);
   9642  } else {
   9643    pointToInsertLineBreak = aPoint;
   9644  }
   9645 
   9646  // TODO: Use InsertLineBreak instead even if we're inserting a <br>.
   9647  Result<CreateElementResult, nsresult> insertPaddingLineBreakResultOrError =
   9648      InsertPaddingBRElementForEmptyLastLineWithTransaction(
   9649          pointToInsertLineBreak);
   9650  if (MOZ_UNLIKELY(insertPaddingLineBreakResultOrError.isErr())) {
   9651    NS_WARNING(
   9652        "EditorBase::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
   9653        "failed");
   9654    return insertPaddingLineBreakResultOrError.propagateErr();
   9655  }
   9656  CreateElementResult insertPaddingLineBreakResult =
   9657      insertPaddingLineBreakResultOrError.unwrap();
   9658  RefPtr<HTMLBRElement> paddingBRElement =
   9659      HTMLBRElement::FromNodeOrNull(insertPaddingLineBreakResult.GetNewNode());
   9660  if (NS_WARN_IF(!paddingBRElement)) {
   9661    return Err(NS_ERROR_FAILURE);
   9662  }
   9663  if (NS_WARN_IF(!paddingBRElement->IsInComposedDoc())) {
   9664    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   9665  }
   9666  insertPaddingLineBreakResult.IgnoreCaretPointSuggestion();
   9667  return CreateLineBreakResult(EditorLineBreak(std::move(paddingBRElement)),
   9668                               EditorDOMPoint(paddingBRElement));
   9669 }
   9670 
   9671 Result<CreateLineBreakResult, nsresult>
   9672 HTMLEditor::InsertPaddingBRElementIfNeeded(
   9673    const EditorDOMPoint& aPoint, nsIEditor::EStripWrappers aDeleteEmptyInlines,
   9674    const Element& aEditingHost) {
   9675  MOZ_ASSERT(aPoint.IsInContentNode());
   9676  MOZ_ASSERT(HTMLEditUtils::NodeIsEditableOrNotInComposedDoc(
   9677      *aPoint.ContainerAs<nsIContent>()));
   9678 
   9679  auto pointToInsertPaddingBR = [&]() MOZ_NEVER_INLINE_DEBUG -> EditorDOMPoint {
   9680    // If the point is immediately before a block boundary which is for a
   9681    // mailcite in plaintext mail composer (it is a <span> styled as block), we
   9682    // should not treat it as a block because it's required by the serializer to
   9683    // give the mailcite contents are not appear with outer content in the same
   9684    // lines.
   9685    if (IsPlaintextMailComposer()) {
   9686      const WSScanResult nextVisibleThing =
   9687          WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   9688              {WSRunScanner::Option::OnlyEditableNodes}, aPoint);
   9689      if (nextVisibleThing.ReachedBlockBoundary() &&
   9690          HTMLEditUtils::IsMailCiteElement(*nextVisibleThing.ElementPtr()) &&
   9691          HTMLEditUtils::IsInlineContent(
   9692              *nextVisibleThing.ElementPtr(),
   9693              BlockInlineCheck::UseHTMLDefaultStyle)) {
   9694        return nextVisibleThing.ReachedCurrentBlockBoundary()
   9695                   ? EditorDOMPoint::AtEndOf(*nextVisibleThing.ElementPtr())
   9696                   : EditorDOMPoint(nextVisibleThing.ElementPtr());
   9697      }
   9698    }
   9699    return HTMLEditUtils::LineRequiresPaddingLineBreakToBeVisible(aPoint,
   9700                                                                  aEditingHost);
   9701  }();
   9702  if (!pointToInsertPaddingBR.IsSet()) {
   9703    return CreateLineBreakResult::NotHandled();
   9704  }
   9705  if (aDeleteEmptyInlines == nsIEditor::eStrip &&
   9706      pointToInsertPaddingBR.IsContainerElement() &&
   9707      HTMLEditUtils::IsEmptyInlineContainer(
   9708          *pointToInsertPaddingBR.ContainerAs<Element>(),
   9709          {EmptyCheckOption::TreatSingleBRElementAsVisible,
   9710           EmptyCheckOption::TreatBlockAsVisible,
   9711           EmptyCheckOption::TreatListItemAsVisible,
   9712           EmptyCheckOption::TreatTableCellAsVisible},
   9713          BlockInlineCheck::UseComputedDisplayStyle)) {
   9714    RefPtr<Element> emptyInlineAncestor =
   9715        HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
   9716            *pointToInsertPaddingBR.ContainerAs<nsIContent>(),
   9717            BlockInlineCheck::UseComputedDisplayStyle);
   9718    if (!emptyInlineAncestor) {
   9719      emptyInlineAncestor = pointToInsertPaddingBR.ContainerAs<Element>();
   9720    }
   9721    AutoTrackDOMPoint trackPointToInsertPaddingBR(RangeUpdaterRef(),
   9722                                                  &pointToInsertPaddingBR);
   9723    nsresult rv = DeleteNodeWithTransaction(*emptyInlineAncestor);
   9724    if (NS_FAILED(rv)) {
   9725      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   9726      return Err(rv);
   9727    }
   9728  }
   9729 
   9730  // Padding <br> elements may appear and disappear a lot even during IME has a
   9731  // composition.  Therefore, IME may be confused with the mutation if we use
   9732  // normal <br> element since it does not match with expectation of IME.  For
   9733  // hiding the mutations from IME, we need to set the new <br> element flag to
   9734  // NS_PADDING_FOR_EMPTY_LAST_LINE.
   9735  Result<CreateElementResult, nsresult> insertPaddingBRResultOrError =
   9736      InsertBRElement(WithTransaction::Yes,
   9737                      BRElementType::PaddingForEmptyLastLine,
   9738                      pointToInsertPaddingBR);
   9739  if (MOZ_UNLIKELY(insertPaddingBRResultOrError.isErr())) {
   9740    NS_WARNING(
   9741        "EditorBase::InsertBRElement(WithTransaction::Yes, "
   9742        "BRElementType::PaddingForEmptyLastLine) failed");
   9743    return insertPaddingBRResultOrError.propagateErr();
   9744  }
   9745  return CreateLineBreakResult(insertPaddingBRResultOrError.unwrap());
   9746 }
   9747 
   9748 Result<EditorDOMPoint, nsresult> HTMLEditor::RemoveAlignFromDescendants(
   9749    Element& aElement, const nsAString& aAlignType, EditTarget aEditTarget) {
   9750  MOZ_ASSERT(IsEditActionDataAvailable());
   9751  MOZ_ASSERT(!aElement.IsHTMLElement(nsGkAtoms::table));
   9752 
   9753  const bool useCSS = IsCSSEnabled();
   9754 
   9755  EditorDOMPoint pointToPutCaret;
   9756 
   9757  // Let's remove all alignment hints in the children of aNode; it can
   9758  // be an ALIGN attribute (in case we just remove it) or a CENTER
   9759  // element (here we have to remove the container and keep its
   9760  // children). We break on tables and don't look at their children.
   9761  nsCOMPtr<nsIContent> nextSibling;
   9762  for (nsIContent* content =
   9763           aEditTarget == EditTarget::NodeAndDescendantsExceptTable
   9764               ? &aElement
   9765               : aElement.GetFirstChild();
   9766       content; content = nextSibling) {
   9767    // Get the next sibling before removing content from the DOM tree.
   9768    // XXX If next sibling is removed from the parent and/or inserted to
   9769    //     different parent, we will behave unexpectedly.  I think that
   9770    //     we should create child list and handle it with checking whether
   9771    //     it's still a child of expected parent.
   9772    nextSibling = aEditTarget == EditTarget::NodeAndDescendantsExceptTable
   9773                      ? nullptr
   9774                      : content->GetNextSibling();
   9775 
   9776    if (content->IsHTMLElement(nsGkAtoms::center)) {
   9777      OwningNonNull<Element> centerElement = *content->AsElement();
   9778      {
   9779        Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   9780            RemoveAlignFromDescendants(centerElement, aAlignType,
   9781                                       EditTarget::OnlyDescendantsExceptTable);
   9782        if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   9783          NS_WARNING(
   9784              "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
   9785              "OnlyDescendantsExceptTable) failed");
   9786          return pointToPutCaretOrError;
   9787        }
   9788        if (pointToPutCaretOrError.inspect().IsSet()) {
   9789          pointToPutCaret = pointToPutCaretOrError.unwrap();
   9790        }
   9791      }
   9792 
   9793      // We may have to insert a `<br>` element before first child of the
   9794      // `<center>` element because it should be first element of a hard line
   9795      // even after removing the `<center>` element.
   9796      {
   9797        Result<CreateElementResult, nsresult>
   9798            maybeInsertBRElementBeforeFirstChildResult =
   9799                EnsureHardLineBeginsWithFirstChildOf(centerElement);
   9800        if (MOZ_UNLIKELY(maybeInsertBRElementBeforeFirstChildResult.isErr())) {
   9801          NS_WARNING(
   9802              "HTMLEditor::EnsureHardLineBeginsWithFirstChildOf() failed");
   9803          return maybeInsertBRElementBeforeFirstChildResult.propagateErr();
   9804        }
   9805        CreateElementResult unwrappedResult =
   9806            maybeInsertBRElementBeforeFirstChildResult.unwrap();
   9807        if (unwrappedResult.HasCaretPointSuggestion()) {
   9808          pointToPutCaret = unwrappedResult.UnwrapCaretPoint();
   9809        }
   9810      }
   9811 
   9812      // We may have to insert a `<br>` element after last child of the
   9813      // `<center>` element because it should be last element of a hard line
   9814      // even after removing the `<center>` element.
   9815      {
   9816        Result<CreateElementResult, nsresult>
   9817            maybeInsertBRElementAfterLastChildResult =
   9818                EnsureHardLineEndsWithLastChildOf(centerElement);
   9819        if (MOZ_UNLIKELY(maybeInsertBRElementAfterLastChildResult.isErr())) {
   9820          NS_WARNING("HTMLEditor::EnsureHardLineEndsWithLastChildOf() failed");
   9821          return maybeInsertBRElementAfterLastChildResult.propagateErr();
   9822        }
   9823        CreateElementResult unwrappedResult =
   9824            maybeInsertBRElementAfterLastChildResult.unwrap();
   9825        if (unwrappedResult.HasCaretPointSuggestion()) {
   9826          pointToPutCaret = unwrappedResult.UnwrapCaretPoint();
   9827        }
   9828      }
   9829 
   9830      {
   9831        Result<EditorDOMPoint, nsresult> unwrapCenterElementResult =
   9832            RemoveContainerWithTransaction(centerElement);
   9833        if (MOZ_UNLIKELY(unwrapCenterElementResult.isErr())) {
   9834          NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
   9835          return unwrapCenterElementResult;
   9836        }
   9837        if (unwrapCenterElementResult.inspect().IsSet()) {
   9838          pointToPutCaret = unwrapCenterElementResult.unwrap();
   9839        }
   9840      }
   9841      continue;
   9842    }
   9843 
   9844    if (!HTMLEditUtils::IsBlockElement(*content,
   9845                                       BlockInlineCheck::UseHTMLDefaultStyle) &&
   9846        !content->IsHTMLElement(nsGkAtoms::hr)) {
   9847      continue;
   9848    }
   9849 
   9850    const OwningNonNull<Element> blockOrHRElement = *content->AsElement();
   9851    if (HTMLEditUtils::IsAlignAttrSupported(blockOrHRElement)) {
   9852      nsresult rv =
   9853          RemoveAttributeWithTransaction(blockOrHRElement, *nsGkAtoms::align);
   9854      if (NS_FAILED(rv)) {
   9855        NS_WARNING(
   9856            "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::align) "
   9857            "failed");
   9858        return Err(rv);
   9859      }
   9860    }
   9861    if (useCSS) {
   9862      if (blockOrHRElement->IsAnyOfHTMLElements(nsGkAtoms::table,
   9863                                                nsGkAtoms::hr)) {
   9864        nsresult rv = SetAttributeOrEquivalent(
   9865            blockOrHRElement, nsGkAtoms::align, aAlignType, false);
   9866        if (NS_WARN_IF(Destroyed())) {
   9867          return Err(NS_ERROR_EDITOR_DESTROYED);
   9868        }
   9869        if (NS_FAILED(rv)) {
   9870          NS_WARNING(
   9871              "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
   9872          return Err(rv);
   9873        }
   9874      } else {
   9875        nsStyledElement* styledBlockOrHRElement =
   9876            nsStyledElement::FromNode(blockOrHRElement);
   9877        if (NS_WARN_IF(!styledBlockOrHRElement)) {
   9878          return Err(NS_ERROR_FAILURE);
   9879        }
   9880        // MOZ_KnownLive(*styledBlockOrHRElement): It's `blockOrHRElement
   9881        // which is OwningNonNull.
   9882        nsAutoString dummyCssValue;
   9883        Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   9884            CSSEditUtils::RemoveCSSInlineStyleWithTransaction(
   9885                *this, MOZ_KnownLive(*styledBlockOrHRElement),
   9886                nsGkAtoms::textAlign, dummyCssValue);
   9887        if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
   9888          NS_WARNING(
   9889              "CSSEditUtils::RemoveCSSInlineStyleWithTransaction(nsGkAtoms::"
   9890              "textAlign) failed");
   9891          return pointToPutCaretOrError;
   9892        }
   9893        if (pointToPutCaretOrError.inspect().IsSet()) {
   9894          pointToPutCaret = pointToPutCaretOrError.unwrap();
   9895        }
   9896      }
   9897    }
   9898    if (!blockOrHRElement->IsHTMLElement(nsGkAtoms::table)) {
   9899      // unless this is a table, look at children
   9900      Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
   9901          RemoveAlignFromDescendants(blockOrHRElement, aAlignType,
   9902                                     EditTarget::OnlyDescendantsExceptTable);
   9903      if (pointToPutCaretOrError.isErr()) {
   9904        NS_WARNING(
   9905            "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
   9906            "OnlyDescendantsExceptTable) failed");
   9907        return pointToPutCaretOrError;
   9908      }
   9909      if (pointToPutCaretOrError.inspect().IsSet()) {
   9910        pointToPutCaret = pointToPutCaretOrError.unwrap();
   9911      }
   9912    }
   9913  }
   9914  return pointToPutCaret;
   9915 }
   9916 
   9917 Result<CreateElementResult, nsresult>
   9918 HTMLEditor::EnsureHardLineBeginsWithFirstChildOf(
   9919    Element& aRemovingContainerElement) {
   9920  MOZ_ASSERT(IsEditActionDataAvailable());
   9921 
   9922  nsIContent* firstEditableChild = HTMLEditUtils::GetFirstChild(
   9923      aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
   9924  if (!firstEditableChild) {
   9925    return CreateElementResult::NotHandled();
   9926  }
   9927 
   9928  if (HTMLEditUtils::IsBlockElement(
   9929          *firstEditableChild, BlockInlineCheck::UseComputedDisplayStyle) ||
   9930      firstEditableChild->IsHTMLElement(nsGkAtoms::br)) {
   9931    return CreateElementResult::NotHandled();
   9932  }
   9933 
   9934  nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousSibling(
   9935      aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
   9936  if (!previousEditableContent) {
   9937    return CreateElementResult::NotHandled();
   9938  }
   9939 
   9940  if (HTMLEditUtils::IsBlockElement(
   9941          *previousEditableContent,
   9942          BlockInlineCheck::UseComputedDisplayStyle) ||
   9943      previousEditableContent->IsHTMLElement(nsGkAtoms::br)) {
   9944    return CreateElementResult::NotHandled();
   9945  }
   9946 
   9947  Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError =
   9948      InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement,
   9949                      EditorDOMPoint(&aRemovingContainerElement, 0u));
   9950  if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) {
   9951    NS_WARNING(
   9952        "HTMLEditor::InsertLineBreak(WithTransaction::Yes, "
   9953        "LineBreakType::BRElement) failed");
   9954    return insertBRElementResultOrError.propagateErr();
   9955  }
   9956  CreateLineBreakResult insertBRElementResult =
   9957      insertBRElementResultOrError.unwrap();
   9958  return CreateElementResult(insertBRElementResult->BRElementRef(),
   9959                             insertBRElementResult.UnwrapCaretPoint());
   9960 }
   9961 
   9962 Result<CreateElementResult, nsresult>
   9963 HTMLEditor::EnsureHardLineEndsWithLastChildOf(
   9964    Element& aRemovingContainerElement) {
   9965  MOZ_ASSERT(IsEditActionDataAvailable());
   9966 
   9967  nsIContent* firstEditableContent = HTMLEditUtils::GetLastChild(
   9968      aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
   9969  if (!firstEditableContent) {
   9970    return CreateElementResult::NotHandled();
   9971  }
   9972 
   9973  if (HTMLEditUtils::IsBlockElement(
   9974          *firstEditableContent, BlockInlineCheck::UseComputedDisplayStyle) ||
   9975      firstEditableContent->IsHTMLElement(nsGkAtoms::br)) {
   9976    return CreateElementResult::NotHandled();
   9977  }
   9978 
   9979  nsIContent* nextEditableContent = HTMLEditUtils::GetPreviousSibling(
   9980      aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode});
   9981  if (!nextEditableContent) {
   9982    return CreateElementResult::NotHandled();
   9983  }
   9984 
   9985  if (HTMLEditUtils::IsBlockElement(
   9986          *nextEditableContent, BlockInlineCheck::UseComputedDisplayStyle) ||
   9987      nextEditableContent->IsHTMLElement(nsGkAtoms::br)) {
   9988    return CreateElementResult::NotHandled();
   9989  }
   9990 
   9991  Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError =
   9992      InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement,
   9993                      EditorDOMPoint::AtEndOf(aRemovingContainerElement));
   9994  if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) {
   9995    NS_WARNING(
   9996        "HTMLEditor::InsertLineBreak(WithTransaction::Yes, "
   9997        "LineBreakType::BRElement) failed");
   9998    return insertBRElementResultOrError.propagateErr();
   9999  }
  10000  CreateLineBreakResult insertBRElementResult =
  10001      insertBRElementResultOrError.unwrap();
  10002  return CreateElementResult(insertBRElementResult->BRElementRef(),
  10003                             insertBRElementResult.UnwrapCaretPoint());
  10004 }
  10005 
  10006 Result<EditorDOMPoint, nsresult> HTMLEditor::SetBlockElementAlign(
  10007    Element& aBlockOrHRElement, const nsAString& aAlignType,
  10008    EditTarget aEditTarget) {
  10009  MOZ_ASSERT(IsEditActionDataAvailable());
  10010  MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
  10011                 aBlockOrHRElement, BlockInlineCheck::UseHTMLDefaultStyle) ||
  10012             aBlockOrHRElement.IsHTMLElement(nsGkAtoms::hr));
  10013  MOZ_ASSERT(IsCSSEnabled() ||
  10014             HTMLEditUtils::IsAlignAttrSupported(aBlockOrHRElement));
  10015 
  10016  EditorDOMPoint pointToPutCaret;
  10017  if (!aBlockOrHRElement.IsHTMLElement(nsGkAtoms::table)) {
  10018    Result<EditorDOMPoint, nsresult> pointToPutCaretOrError =
  10019        RemoveAlignFromDescendants(aBlockOrHRElement, aAlignType, aEditTarget);
  10020    if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
  10021      NS_WARNING("HTMLEditor::RemoveAlignFromDescendants() failed");
  10022      return pointToPutCaretOrError;
  10023    }
  10024    if (pointToPutCaretOrError.inspect().IsSet()) {
  10025      pointToPutCaret = pointToPutCaretOrError.unwrap();
  10026    }
  10027  }
  10028  nsresult rv = SetAttributeOrEquivalent(&aBlockOrHRElement, nsGkAtoms::align,
  10029                                         aAlignType, false);
  10030  if (NS_WARN_IF(Destroyed())) {
  10031    return Err(NS_ERROR_EDITOR_DESTROYED);
  10032  }
  10033  if (NS_FAILED(rv)) {
  10034    NS_WARNING("HTMLEditor::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
  10035    return Err(rv);
  10036  }
  10037  return pointToPutCaret;
  10038 }
  10039 
  10040 Result<EditorDOMPoint, nsresult> HTMLEditor::ChangeMarginStart(
  10041    Element& aElement, ChangeMargin aChangeMargin,
  10042    const Element& aEditingHost) {
  10043  MOZ_ASSERT(IsEditActionDataAvailable());
  10044 
  10045  nsStaticAtom& marginProperty = MarginPropertyAtomForIndent(aElement);
  10046  if (NS_WARN_IF(Destroyed())) {
  10047    return Err(NS_ERROR_EDITOR_DESTROYED);
  10048  }
  10049  nsAutoString value;
  10050  DebugOnly<nsresult> rvIgnored =
  10051      CSSEditUtils::GetSpecifiedProperty(aElement, marginProperty, value);
  10052  if (NS_WARN_IF(Destroyed())) {
  10053    return Err(NS_ERROR_EDITOR_DESTROYED);
  10054  }
  10055  NS_WARNING_ASSERTION(
  10056      NS_SUCCEEDED(rvIgnored),
  10057      "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
  10058  float f;
  10059  RefPtr<nsAtom> unit;
  10060  CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit));
  10061  if (!f) {
  10062    unit = nsGkAtoms::px;
  10063  }
  10064  int8_t multiplier = aChangeMargin == ChangeMargin::Increase ? 1 : -1;
  10065  if (nsGkAtoms::in == unit) {
  10066    f += NS_EDITOR_INDENT_INCREMENT_IN * multiplier;
  10067  } else if (nsGkAtoms::cm == unit) {
  10068    f += NS_EDITOR_INDENT_INCREMENT_CM * multiplier;
  10069  } else if (nsGkAtoms::mm == unit) {
  10070    f += NS_EDITOR_INDENT_INCREMENT_MM * multiplier;
  10071  } else if (nsGkAtoms::pt == unit) {
  10072    f += NS_EDITOR_INDENT_INCREMENT_PT * multiplier;
  10073  } else if (nsGkAtoms::pc == unit) {
  10074    f += NS_EDITOR_INDENT_INCREMENT_PC * multiplier;
  10075  } else if (nsGkAtoms::em == unit) {
  10076    f += NS_EDITOR_INDENT_INCREMENT_EM * multiplier;
  10077  } else if (nsGkAtoms::ex == unit) {
  10078    f += NS_EDITOR_INDENT_INCREMENT_EX * multiplier;
  10079  } else if (nsGkAtoms::px == unit) {
  10080    f += NS_EDITOR_INDENT_INCREMENT_PX * multiplier;
  10081  } else if (nsGkAtoms::percentage == unit) {
  10082    f += NS_EDITOR_INDENT_INCREMENT_PERCENT * multiplier;
  10083  }
  10084 
  10085  if (0 < f) {
  10086    if (nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement)) {
  10087      nsAutoString newValue;
  10088      newValue.AppendFloat(f);
  10089      newValue.Append(nsDependentAtomString(unit));
  10090      // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
  10091      // be guaranteed by caller because of MOZ_CAN_RUN_SCRIPT method.
  10092      // MOZ_KnownLive(merginProperty): It's nsStaticAtom.
  10093      nsresult rv = CSSEditUtils::SetCSSPropertyWithTransaction(
  10094          *this, MOZ_KnownLive(*styledElement), MOZ_KnownLive(marginProperty),
  10095          newValue);
  10096      if (rv == NS_ERROR_EDITOR_DESTROYED) {
  10097        NS_WARNING(
  10098            "CSSEditUtils::SetCSSPropertyWithTransaction() destroyed the "
  10099            "editor");
  10100        return Err(NS_ERROR_EDITOR_DESTROYED);
  10101      }
  10102      NS_WARNING_ASSERTION(
  10103          NS_SUCCEEDED(rv),
  10104          "CSSEditUtils::SetCSSPropertyWithTransaction() failed, but ignored");
  10105    }
  10106    return EditorDOMPoint();
  10107  }
  10108 
  10109  if (nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement)) {
  10110    // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
  10111    // be guaranteed by caller because of MOZ_CAN_RUN_SCRIPT method.
  10112    // MOZ_KnownLive(merginProperty): It's nsStaticAtom.
  10113    nsresult rv = CSSEditUtils::RemoveCSSPropertyWithTransaction(
  10114        *this, MOZ_KnownLive(*styledElement), MOZ_KnownLive(marginProperty),
  10115        value);
  10116    if (rv == NS_ERROR_EDITOR_DESTROYED) {
  10117      NS_WARNING(
  10118          "CSSEditUtils::RemoveCSSPropertyWithTransaction() destroyed the "
  10119          "editor");
  10120      return Err(NS_ERROR_EDITOR_DESTROYED);
  10121    }
  10122    NS_WARNING_ASSERTION(
  10123        NS_SUCCEEDED(rv),
  10124        "CSSEditUtils::RemoveCSSPropertyWithTransaction() failed, but ignored");
  10125  }
  10126 
  10127  // Remove unnecessary divs
  10128  if (!aElement.IsHTMLElement(nsGkAtoms::div) ||
  10129      HTMLEditUtils::ElementHasAttribute(aElement)) {
  10130    return EditorDOMPoint();
  10131  }
  10132  // Don't touch editing host nor node which is outside of it.
  10133  if (&aElement == &aEditingHost ||
  10134      !aElement.IsInclusiveDescendantOf(&aEditingHost)) {
  10135    return EditorDOMPoint();
  10136  }
  10137 
  10138  Result<EditorDOMPoint, nsresult> unwrapDivElementResult =
  10139      RemoveContainerWithTransaction(aElement);
  10140  NS_WARNING_ASSERTION(unwrapDivElementResult.isOk(),
  10141                       "HTMLEditor::RemoveContainerWithTransaction() failed");
  10142  return unwrapDivElementResult;
  10143 }
  10144 
  10145 Result<EditActionResult, nsresult>
  10146 HTMLEditor::SetSelectionToAbsoluteAsSubAction(const Element& aEditingHost) {
  10147  AutoPlaceholderBatch treatAsOneTransaction(
  10148      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
  10149  IgnoredErrorResult ignoredError;
  10150  AutoEditSubActionNotifier startToHandleEditSubAction(
  10151      *this, EditSubAction::eSetPositionToAbsolute, nsIEditor::eNext,
  10152      ignoredError);
  10153  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
  10154    return Err(ignoredError.StealNSResult());
  10155  }
  10156  NS_WARNING_ASSERTION(
  10157      !ignoredError.Failed(),
  10158      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
  10159 
  10160  {
  10161    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
  10162    if (MOZ_UNLIKELY(result.isErr())) {
  10163      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
  10164      return result;
  10165    }
  10166    if (result.inspect().Canceled()) {
  10167      return result;
  10168    }
  10169  }
  10170 
  10171  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
  10172  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
  10173    return Err(NS_ERROR_EDITOR_DESTROYED);
  10174  }
  10175  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
  10176                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
  10177                       "failed, but ignored");
  10178 
  10179  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
  10180    nsresult rv = EnsureCaretNotAfterInvisibleBRElement(aEditingHost);
  10181    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
  10182      return Err(NS_ERROR_EDITOR_DESTROYED);
  10183    }
  10184    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
  10185                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
  10186                         "failed, but ignored");
  10187    if (NS_SUCCEEDED(rv)) {
  10188      nsresult rv = PrepareInlineStylesForCaret();
  10189      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
  10190        return Err(NS_ERROR_EDITOR_DESTROYED);
  10191      }
  10192      NS_WARNING_ASSERTION(
  10193          NS_SUCCEEDED(rv),
  10194          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
  10195    }
  10196  }
  10197 
  10198  auto EnsureCaretInElementIfCollapsedOutside =
  10199      [&](Element& aElement) MOZ_CAN_RUN_SCRIPT {
  10200        if (!SelectionRef().IsCollapsed() || !SelectionRef().RangeCount()) {
  10201          return NS_OK;
  10202        }
  10203        const auto firstRangeStartPoint =
  10204            GetFirstSelectionStartPoint<EditorRawDOMPoint>();
  10205        if (MOZ_UNLIKELY(!firstRangeStartPoint.IsSet())) {
  10206          return NS_OK;
  10207        }
  10208        const Result<EditorRawDOMPoint, nsresult> pointToPutCaretOrError =
  10209            HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
  10210                EditorRawDOMPoint>(aElement, firstRangeStartPoint);
  10211        if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
  10212          NS_WARNING(
  10213              "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() "
  10214              "failed, but ignored");
  10215          return NS_OK;
  10216        }
  10217        if (!pointToPutCaretOrError.inspect().IsSet()) {
  10218          return NS_OK;
  10219        }
  10220        nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
  10221        if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
  10222          NS_WARNING("EditorBase::CollapseSelectionTo() failed");
  10223          return NS_ERROR_EDITOR_DESTROYED;
  10224        }
  10225        NS_WARNING_ASSERTION(
  10226            NS_SUCCEEDED(rv),
  10227            "EditorBase::CollapseSelectionTo() failed, but ignored");
  10228        return NS_OK;
  10229      };
  10230 
  10231  const RefPtr<Element> focusElement = GetSelectionContainerElement();
  10232  if (focusElement && HTMLEditUtils::IsImageElement(*focusElement)) {
  10233    nsresult rv = EnsureCaretInElementIfCollapsedOutside(*focusElement);
  10234    if (NS_FAILED(rv)) {
  10235      NS_WARNING("EnsureCaretInElementIfCollapsedOutside() failed");
  10236      return Err(rv);
  10237    }
  10238    return EditActionResult::HandledResult();
  10239  }
  10240 
  10241  // XXX Why do we do this only when there is only one selection range?
  10242  if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) {
  10243    Result<EditorRawDOMRange, nsresult> extendedRange =
  10244        GetRangeExtendedToHardLineEdgesForBlockEditAction(
  10245            SelectionRef().GetRangeAt(0u), aEditingHost);
  10246    if (MOZ_UNLIKELY(extendedRange.isErr())) {
  10247      NS_WARNING(
  10248          "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() "
  10249          "failed");
  10250      return extendedRange.propagateErr();
  10251    }
  10252    // Note that end point may be prior to start point.  So, we
  10253    // cannot use Selection::SetStartAndEndInLimit() here.
  10254    IgnoredErrorResult error;
  10255    SelectionRef().SetBaseAndExtentInLimiter(
  10256        extendedRange.inspect().StartRef().ToRawRangeBoundary(),
  10257        extendedRange.inspect().EndRef().ToRawRangeBoundary(), error);
  10258    if (NS_WARN_IF(Destroyed())) {
  10259      return Err(NS_ERROR_EDITOR_DESTROYED);
  10260    }
  10261    if (MOZ_UNLIKELY(error.Failed())) {
  10262      NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
  10263      return Err(error.StealNSResult());
  10264    }
  10265  }
  10266 
  10267  RefPtr<Element> divElement;
  10268  rv = MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
  10269      address_of(divElement), aEditingHost);
  10270  // MoveSelectedContentsToDivElementToMakeItAbsolutePosition() may restore
  10271  // selection with AutoSelectionRestorer.  Therefore, the editor might have
  10272  // already been destroyed now.
  10273  if (NS_WARN_IF(Destroyed())) {
  10274    return Err(NS_ERROR_EDITOR_DESTROYED);
  10275  }
  10276  if (NS_FAILED(rv)) {
  10277    NS_WARNING(
  10278        "HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition()"
  10279        " failed");
  10280    return Err(rv);
  10281  }
  10282 
  10283  if (IsSelectionRangeContainerNotContent()) {
  10284    NS_WARNING("Mutation event listener might have changed the selection");
  10285    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
  10286  }
  10287 
  10288  if (SelectionRef().IsCollapsed()) {
  10289    const auto caretPosition =
  10290        EditorBase::GetFirstSelectionStartPoint<EditorDOMPoint>();
  10291    Result<CreateLineBreakResult, nsresult>
  10292        insertPaddingBRElementResultOrError =
  10293            InsertPaddingBRElementIfInEmptyBlock(caretPosition, eNoStrip);
  10294    if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) {
  10295      NS_WARNING(
  10296          "HTMLEditor::InsertPaddingBRElementIfInEmptyBlock(eNoStrip) failed");
  10297      return insertPaddingBRElementResultOrError.propagateErr();
  10298    }
  10299    nsresult rv =
  10300        insertPaddingBRElementResultOrError.unwrap().SuggestCaretPointTo(
  10301            *this, {SuggestCaret::OnlyIfHasSuggestion,
  10302                    SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
  10303                    SuggestCaret::AndIgnoreTrivialError});
  10304    if (NS_FAILED(rv)) {
  10305      NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
  10306      return Err(rv);
  10307    }
  10308    NS_WARNING_ASSERTION(
  10309        rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
  10310        "CaretPoint::SuggestCaretPointTo() failed, but ignored");
  10311  }
  10312 
  10313  if (!divElement) {
  10314    return EditActionResult::HandledResult();
  10315  }
  10316 
  10317  rv = SetPositionToAbsoluteOrStatic(*divElement, true);
  10318  if (NS_WARN_IF(Destroyed())) {
  10319    return Err(NS_ERROR_EDITOR_DESTROYED);
  10320  }
  10321  if (NS_FAILED(rv)) {
  10322    NS_WARNING("HTMLEditor::SetPositionToAbsoluteOrStatic() failed");
  10323    return Err(rv);
  10324  }
  10325 
  10326  rv = EnsureCaretInElementIfCollapsedOutside(*divElement);
  10327  if (NS_FAILED(rv)) {
  10328    NS_WARNING("EnsureCaretInElementIfCollapsedOutside() failed");
  10329    return Err(rv);
  10330  }
  10331  return EditActionResult::HandledResult();
  10332 }
  10333 
  10334 nsresult HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
  10335    RefPtr<Element>* aTargetElement, const Element& aEditingHost) {
  10336  MOZ_ASSERT(IsEditActionDataAvailable());
  10337  MOZ_ASSERT(aTargetElement);
  10338 
  10339  AutoSelectionRestorer restoreSelectionLater(this);
  10340 
  10341  EditorDOMPoint pointToPutCaret;
  10342 
  10343  // Use these ranges to construct a list of nodes to act on.
  10344  AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
  10345  {
  10346    AutoClonedSelectionRangeArray extendedSelectionRanges(SelectionRef());
  10347    extendedSelectionRanges.ExtendRangesToWrapLines(
  10348        EditSubAction::eSetPositionToAbsolute,
  10349        BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
  10350    Result<EditorDOMPoint, nsresult> splitResult =
  10351        extendedSelectionRanges
  10352            .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
  10353                *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
  10354    if (MOZ_UNLIKELY(splitResult.isErr())) {
  10355      NS_WARNING(
  10356          "AutoClonedRangeArray::"
  10357          "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() "
  10358          "failed");
  10359      return splitResult.unwrapErr();
  10360    }
  10361    if (splitResult.inspect().IsSet()) {
  10362      pointToPutCaret = splitResult.unwrap();
  10363    }
  10364    nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
  10365        *this, arrayOfContents, EditSubAction::eSetPositionToAbsolute,
  10366        AutoClonedRangeArray::CollectNonEditableNodes::Yes);
  10367    if (NS_FAILED(rv)) {
  10368      NS_WARNING(
  10369          "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::"
  10370          "eSetPositionToAbsolute, CollectNonEditableNodes::Yes) failed");
  10371      return rv;
  10372    }
  10373  }
  10374 
  10375  Result<EditorDOMPoint, nsresult> splitAtBRElementsResult =
  10376      MaybeSplitElementsAtEveryBRElement(arrayOfContents,
  10377                                         EditSubAction::eSetPositionToAbsolute);
  10378  if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) {
  10379    NS_WARNING(
  10380        "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::"
  10381        "eSetPositionToAbsolute) failed");
  10382    return splitAtBRElementsResult.inspectErr();
  10383  }
  10384  if (splitAtBRElementsResult.inspect().IsSet()) {
  10385    pointToPutCaret = splitAtBRElementsResult.unwrap();
  10386  }
  10387 
  10388  if (AllowsTransactionsToChangeSelection() &&
  10389      pointToPutCaret.IsSetAndValid()) {
  10390    nsresult rv = CollapseSelectionTo(pointToPutCaret);
  10391    if (NS_FAILED(rv)) {
  10392      NS_WARNING("EditorBase::CollapseSelectionTo() failed");
  10393      return rv;
  10394    }
  10395  }
  10396 
  10397  // If there is no visible and editable nodes in the edit targets, make an
  10398  // empty block.
  10399  // XXX Isn't this odd if there are only non-editable visible nodes?
  10400  if (HTMLEditUtils::IsEmptyOneHardLine(
  10401          arrayOfContents, BlockInlineCheck::UseHTMLDefaultStyle)) {
  10402    const auto atCaret =
  10403        EditorBase::GetFirstSelectionStartPoint<EditorDOMPoint>();
  10404    if (NS_WARN_IF(!atCaret.IsSet())) {
  10405      return NS_ERROR_FAILURE;
  10406    }
  10407 
  10408    // Make sure we can put a block here.
  10409    Result<CreateElementResult, nsresult> createNewDivElementResult =
  10410        InsertElementWithSplittingAncestorsWithTransaction(
  10411            *nsGkAtoms::div, atCaret, BRElementNextToSplitPoint::Keep,
  10412            aEditingHost);
  10413    if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
  10414      NS_WARNING(
  10415          "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
  10416          "nsGkAtoms::div) failed");
  10417      return createNewDivElementResult.unwrapErr();
  10418    }
  10419    CreateElementResult unwrappedCreateNewDivElementResult =
  10420        createNewDivElementResult.unwrap();
  10421    // We'll update selection after deleting the content nodes and nobody
  10422    // refers selection until then.  Therefore, we don't need to update
  10423    // selection here.
  10424    unwrappedCreateNewDivElementResult.IgnoreCaretPointSuggestion();
  10425    RefPtr<Element> newDivElement =
  10426        unwrappedCreateNewDivElementResult.UnwrapNewNode();
  10427    MOZ_ASSERT(newDivElement);
  10428    // Delete anything that was in the list of nodes
  10429    // XXX We don't need to remove items from the array.
  10430    for (OwningNonNull<nsIContent>& curNode : arrayOfContents) {
  10431      // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
  10432      nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*curNode));
  10433      if (NS_FAILED(rv)) {
  10434        NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
  10435        return rv;
  10436      }
  10437    }
  10438    // Don't restore the selection
  10439    restoreSelectionLater.Abort();
  10440    nsresult rv = CollapseSelectionToStartOf(*newDivElement);
  10441    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
  10442                         "EditorBase::CollapseSelectionToStartOf() failed");
  10443    *aTargetElement = std::move(newDivElement);
  10444    return rv;
  10445  }
  10446 
  10447  // `<div>` element to be positioned absolutely.  This may have already
  10448  // existed or newly created by this method.
  10449  RefPtr<Element> targetDivElement;
  10450  // Newly created list element for moving selected list item elements into
  10451  // targetDivElement.  I.e., this is created in the `<div>` element.
  10452  RefPtr<Element> createdListElement;
  10453  // If we handle a parent list item element, this is set to it.  In such case,
  10454  // we should handle its children again.
  10455  RefPtr<Element> handledListItemElement;
  10456  for (size_t i = 0; i < arrayOfContents.Length(); i++) {
  10457    const OwningNonNull<nsIContent>& content = arrayOfContents[i];
  10458 
  10459    // Here's where we actually figure out what to do.
  10460    EditorDOMPoint atContent(content);
  10461    if (NS_WARN_IF(!atContent.IsInContentNode())) {
  10462      return NS_ERROR_FAILURE;  // XXX not continue??
  10463    }
  10464 
  10465    // Ignore all non-editable nodes.  Leave them be.
  10466    if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
  10467      continue;
  10468    }
  10469 
  10470    // If current node is a child of a list element, we need another list
  10471    // element in absolute-positioned `<div>` element to avoid non-selected
  10472    // list items are moved into the `<div>` element.
  10473    if (HTMLEditUtils::IsListElement(*atContent.ContainerAs<nsIContent>())) {
  10474      // If we cannot move current node to created list element, we need a
  10475      // list element in the target `<div>` element for the destination.
  10476      // Therefore, duplicate same list element into the target `<div>`
  10477      // element.
  10478      nsIContent* previousEditableContent =
  10479          createdListElement
  10480              ? HTMLEditUtils::GetPreviousSibling(
  10481                    content, {WalkTreeOption::IgnoreNonEditableNode})
  10482              : nullptr;
  10483      if (!createdListElement ||
  10484          (previousEditableContent &&
  10485           previousEditableContent != createdListElement)) {
  10486        nsAtom* ULOrOLOrDLTagName =
  10487            atContent.GetContainer()->NodeInfo()->NameAtom();
  10488        if (targetDivElement) {
  10489          // XXX Do we need to split the container? Since we'll append new
  10490          //     element at end of the <div> element.
  10491          Result<SplitNodeResult, nsresult> splitNodeResult =
  10492              MaybeSplitAncestorsForInsertWithTransaction(
  10493                  MOZ_KnownLive(*ULOrOLOrDLTagName), atContent, aEditingHost);
  10494          if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
  10495            NS_WARNING(
  10496                "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() "
  10497                "failed");
  10498            return splitNodeResult.unwrapErr();
  10499          }
  10500          // We'll update selection after creating a list element below.
  10501          // Therefore, we don't need to touch selection here.
  10502          splitNodeResult.inspect().IgnoreCaretPointSuggestion();
  10503        } else {
  10504          // If we've not had a target <div> element yet, let's insert a <div>
  10505          // element with splitting the ancestors.
  10506          Result<CreateElementResult, nsresult> createNewDivElementResult =
  10507              InsertElementWithSplittingAncestorsWithTransaction(
  10508                  *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
  10509                  aEditingHost);
  10510          if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
  10511            NS_WARNING(
  10512                "HTMLEditor::"
  10513                "InsertElementWithSplittingAncestorsWithTransaction(nsGkAtoms::"
  10514                "div) failed");
  10515            return createNewDivElementResult.unwrapErr();
  10516          }
  10517          // We'll update selection after creating a list element below.
  10518          // Therefor, we don't need to touch selection here.
  10519          createNewDivElementResult.inspect().IgnoreCaretPointSuggestion();
  10520          MOZ_ASSERT(createNewDivElementResult.inspect().GetNewNode());
  10521          targetDivElement = createNewDivElementResult.unwrap().UnwrapNewNode();
  10522        }
  10523        Result<CreateElementResult, nsresult> createNewListElementResult =
  10524            CreateAndInsertElement(WithTransaction::Yes,
  10525                                   MOZ_KnownLive(*ULOrOLOrDLTagName),
  10526                                   EditorDOMPoint::AtEndOf(targetDivElement));
  10527        if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
  10528          NS_WARNING(
  10529              "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
  10530              "failed");
  10531          return createNewListElementResult.unwrapErr();
  10532        }
  10533        nsresult rv = createNewListElementResult.inspect().SuggestCaretPointTo(
  10534            *this, {SuggestCaret::OnlyIfHasSuggestion,
  10535                    SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
  10536                    SuggestCaret::AndIgnoreTrivialError});
  10537        if (NS_FAILED(rv)) {
  10538          NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
  10539          return Err(rv);
  10540        }
  10541        NS_WARNING_ASSERTION(
  10542            rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
  10543            "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
  10544        createdListElement =
  10545            createNewListElementResult.unwrap().UnwrapNewNode();
  10546        MOZ_ASSERT(createdListElement);
  10547      }
  10548      // Move current node (maybe, assumed as a list item element) into the
  10549      // new list element in the target `<div>` element to be positioned
  10550      // absolutely.
  10551      // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
  10552      Result<MoveNodeResult, nsresult> moveNodeResult =
  10553          MoveNodeToEndWithTransaction(MOZ_KnownLive(content),
  10554                                       *createdListElement);
  10555      if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
  10556        NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
  10557        return moveNodeResult.propagateErr();
  10558      }
  10559      nsresult rv = moveNodeResult.inspect().SuggestCaretPointTo(
  10560          *this, {SuggestCaret::OnlyIfHasSuggestion,
  10561                  SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
  10562                  SuggestCaret::AndIgnoreTrivialError});
  10563      if (NS_FAILED(rv)) {
  10564        NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
  10565        return Err(rv);
  10566      }
  10567      NS_WARNING_ASSERTION(
  10568          rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
  10569          "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
  10570      continue;
  10571    }
  10572 
  10573    // If contents in a list item element is selected, we should move current
  10574    // node into the target `<div>` element with the list item element itself
  10575    // because we want to keep indent level of the contents.
  10576    if (RefPtr<Element> listItemElement =
  10577            HTMLEditUtils::GetClosestInclusiveAncestorListItemElement(
  10578                content, &aEditingHost)) {
  10579      if (handledListItemElement == listItemElement) {
  10580        // Current node has already been moved into the `<div>` element.
  10581        continue;
  10582      }
  10583      // If we cannot move the list item element into created list element,
  10584      // we need another list element in the target `<div>` element.
  10585      nsIContent* previousEditableContent =
  10586          createdListElement
  10587              ? HTMLEditUtils::GetPreviousSibling(
  10588                    *listItemElement, {WalkTreeOption::IgnoreNonEditableNode})
  10589              : nullptr;
  10590      if (!createdListElement ||
  10591          (previousEditableContent &&
  10592           previousEditableContent != createdListElement)) {
  10593        EditorDOMPoint atListItem(listItemElement);
  10594        if (NS_WARN_IF(!atListItem.IsSet())) {
  10595          return NS_ERROR_FAILURE;
  10596        }
  10597        // XXX If content is the listItemElement and not in a list element,
  10598        //     we duplicate wrong element into the target `<div>` element.
  10599        nsAtom* containerName =
  10600            atListItem.GetContainer()->NodeInfo()->NameAtom();
  10601        if (targetDivElement) {
  10602          // XXX Do we need to split the container? Since we'll append new
  10603          //     element at end of the <div> element.
  10604          Result<SplitNodeResult, nsresult> splitNodeResult =
  10605              MaybeSplitAncestorsForInsertWithTransaction(
  10606                  MOZ_KnownLive(*containerName), atListItem, aEditingHost);
  10607          if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
  10608            NS_WARNING(
  10609                "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() "
  10610                "failed");
  10611            return splitNodeResult.unwrapErr();
  10612          }
  10613          // We'll update selection after creating a list element below.
  10614          // Therefore, we don't need to touch selection here.
  10615          splitNodeResult.inspect().IgnoreCaretPointSuggestion();
  10616        } else {
  10617          // If we've not had a target <div> element yet, let's insert a <div>
  10618          // element with splitting the ancestors.
  10619          Result<CreateElementResult, nsresult> createNewDivElementResult =
  10620              InsertElementWithSplittingAncestorsWithTransaction(
  10621                  *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
  10622                  aEditingHost);
  10623          if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
  10624            NS_WARNING(
  10625                "HTMLEditor::"
  10626                "InsertElementWithSplittingAncestorsWithTransaction("
  10627                "nsGkAtoms::div) failed");
  10628            return createNewDivElementResult.unwrapErr();
  10629          }
  10630          // We'll update selection after creating a list element below.
  10631          // Therefore, we don't need to touch selection here.
  10632          createNewDivElementResult.inspect().IgnoreCaretPointSuggestion();
  10633          MOZ_ASSERT(createNewDivElementResult.inspect().GetNewNode());
  10634          targetDivElement = createNewDivElementResult.unwrap().UnwrapNewNode();
  10635        }
  10636        // XXX So, createdListElement may be set to a non-list element.
  10637        Result<CreateElementResult, nsresult> createNewListElementResult =
  10638            CreateAndInsertElement(WithTransaction::Yes,
  10639                                   MOZ_KnownLive(*containerName),
  10640                                   EditorDOMPoint::AtEndOf(targetDivElement));
  10641        if (MOZ_UNLIKELY(createNewListElementResult.isErr())) {
  10642          NS_WARNING(
  10643              "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) "
  10644              "failed");
  10645          return createNewListElementResult.unwrapErr();
  10646        }
  10647        nsresult rv = createNewListElementResult.inspect().SuggestCaretPointTo(
  10648            *this, {SuggestCaret::OnlyIfHasSuggestion,
  10649                    SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
  10650                    SuggestCaret::AndIgnoreTrivialError});
  10651        if (NS_FAILED(rv)) {
  10652          NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
  10653          return Err(rv);
  10654        }
  10655        NS_WARNING_ASSERTION(
  10656            rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
  10657            "CreateElementResult::SuggestCaretPointTo() failed, but ignored");
  10658        createdListElement =
  10659            createNewListElementResult.unwrap().UnwrapNewNode();
  10660        MOZ_ASSERT(createdListElement);
  10661      }
  10662      // Move current list item element into the createdListElement (could be
  10663      // non-list element due to the above bug) in a candidate `<div>` element
  10664      // to be positioned absolutely.
  10665      Result<MoveNodeResult, nsresult> moveListItemElementResult =
  10666          MoveNodeToEndWithTransaction(*listItemElement, *createdListElement);
  10667      if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) {
  10668        NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
  10669        return moveListItemElementResult.unwrapErr();
  10670      }
  10671      nsresult rv = moveListItemElementResult.inspect().SuggestCaretPointTo(
  10672          *this, {SuggestCaret::OnlyIfHasSuggestion,
  10673                  SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
  10674                  SuggestCaret::AndIgnoreTrivialError});
  10675      if (NS_FAILED(rv)) {
  10676        NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
  10677        return Err(rv);
  10678      }
  10679      NS_WARNING_ASSERTION(
  10680          rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
  10681          "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
  10682      handledListItemElement = std::move(listItemElement);
  10683      continue;
  10684    }
  10685 
  10686    if (!targetDivElement) {
  10687      // If we meet a `<div>` element, use it as the absolute-position
  10688      // container.
  10689      // XXX This looks odd.  If there are 2 or more `<div>` elements are
  10690      //     selected, first found `<div>` element will have all other
  10691      //     selected nodes.
  10692      if (content->IsHTMLElement(nsGkAtoms::div)) {
  10693        targetDivElement = content->AsElement();
  10694        MOZ_ASSERT(!createdListElement);
  10695        MOZ_ASSERT(!handledListItemElement);
  10696        continue;
  10697      }
  10698      // Otherwise, create new `<div>` element to be positioned absolutely
  10699      // and to contain all selected nodes.
  10700      Result<CreateElementResult, nsresult> createNewDivElementResult =
  10701          InsertElementWithSplittingAncestorsWithTransaction(
  10702              *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep,
  10703              aEditingHost);
  10704      if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) {
  10705        NS_WARNING(
  10706            "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction("
  10707            "nsGkAtoms::div) failed");
  10708        return createNewDivElementResult.unwrapErr();
  10709      }
  10710      nsresult rv = createNewDivElementResult.inspect().SuggestCaretPointTo(
  10711          *this, {SuggestCaret::OnlyIfHasSuggestion,
  10712                  SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
  10713      if (NS_FAILED(rv)) {
  10714        NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
  10715        return rv;
  10716      }
  10717      MOZ_ASSERT(createNewDivElementResult.inspect().GetNewNode());
  10718      targetDivElement = createNewDivElementResult.unwrap().UnwrapNewNode();
  10719    }
  10720 
  10721    const OwningNonNull<nsIContent> lastContent = [&]() {
  10722      nsIContent* lastContent = content;
  10723      for (; i + 1 < arrayOfContents.Length(); i++) {
  10724        const OwningNonNull<nsIContent>& nextContent = arrayOfContents[i + 1];
  10725        if (lastContent->GetNextSibling() == nextContent ||
  10726            HTMLEditUtils::IsListElement(*nextContent) ||
  10727            HTMLEditUtils::IsListItemElement(*nextContent) ||
  10728            !EditorUtils::IsEditableContent(content, EditorType::HTML)) {
  10729          break;
  10730        }
  10731        lastContent = nextContent;
  10732      }
  10733      return OwningNonNull<nsIContent>(*lastContent);
  10734    }();
  10735 
  10736    // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
  10737    Result<MoveNodeResult, nsresult> moveNodeResult =
  10738        MoveSiblingsToEndWithTransaction(MOZ_KnownLive(content), lastContent,
  10739                                         *targetDivElement);
  10740    if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
  10741      NS_WARNING("HTMLEditor::MoveSiblingsToEndWithTransaction() failed");
  10742      return moveNodeResult.unwrapErr();
  10743    }
  10744    nsresult rv = moveNodeResult.inspect().SuggestCaretPointTo(
  10745        *this, {SuggestCaret::OnlyIfHasSuggestion,
  10746                SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
  10747                SuggestCaret::AndIgnoreTrivialError});
  10748    if (NS_FAILED(rv)) {
  10749      NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed");
  10750      return rv;
  10751    }
  10752    NS_WARNING_ASSERTION(
  10753        rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
  10754        "MoveNodeResult::SuggestCaretPointTo() failed, but ignored");
  10755    // Forget createdListElement, if any
  10756    createdListElement = nullptr;
  10757  }
  10758  *aTargetElement = std::move(targetDivElement);
  10759  return NS_OK;
  10760 }
  10761 
  10762 Result<EditActionResult, nsresult>
  10763 HTMLEditor::SetSelectionToStaticAsSubAction() {
  10764  MOZ_ASSERT(IsEditActionDataAvailable());
  10765 
  10766  AutoPlaceholderBatch treatAsOneTransaction(
  10767      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
  10768  IgnoredErrorResult ignoredError;
  10769  AutoEditSubActionNotifier startToHandleEditSubAction(
  10770      *this, EditSubAction::eSetPositionToStatic, nsIEditor::eNext,
  10771      ignoredError);
  10772  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
  10773    return Err(ignoredError.StealNSResult());
  10774  }
  10775  NS_WARNING_ASSERTION(
  10776      !ignoredError.Failed(),
  10777      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
  10778 
  10779  {
  10780    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
  10781    if (MOZ_UNLIKELY(result.isErr())) {
  10782      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
  10783      return result;
  10784    }
  10785    if (result.inspect().Canceled()) {
  10786      return result;
  10787    }
  10788  }
  10789 
  10790  const RefPtr<Element> editingHost =
  10791      ComputeEditingHost(LimitInBodyElement::No);
  10792  if (NS_WARN_IF(!editingHost)) {
  10793    return Err(NS_ERROR_FAILURE);
  10794  }
  10795 
  10796  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
  10797  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
  10798    return Err(NS_ERROR_EDITOR_DESTROYED);
  10799  }
  10800  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
  10801                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
  10802                       "failed, but ignored");
  10803 
  10804  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
  10805    nsresult rv = EnsureCaretNotAfterInvisibleBRElement(*editingHost);
  10806    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
  10807      return Err(NS_ERROR_EDITOR_DESTROYED);
  10808    }
  10809    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
  10810                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
  10811                         "failed, but ignored");
  10812    if (NS_SUCCEEDED(rv)) {
  10813      nsresult rv = PrepareInlineStylesForCaret();
  10814      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
  10815        return Err(NS_ERROR_EDITOR_DESTROYED);
  10816      }
  10817      NS_WARNING_ASSERTION(
  10818          NS_SUCCEEDED(rv),
  10819          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
  10820    }
  10821  }
  10822 
  10823  RefPtr<Element> element = GetAbsolutelyPositionedSelectionContainer();
  10824  if (!element) {
  10825    if (NS_WARN_IF(Destroyed())) {
  10826      return Err(NS_ERROR_EDITOR_DESTROYED);
  10827    }
  10828    NS_WARNING(
  10829        "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() returned "
  10830        "nullptr");
  10831    return Err(NS_ERROR_FAILURE);
  10832  }
  10833 
  10834  {
  10835    AutoSelectionRestorer restoreSelectionLater(this);
  10836 
  10837    nsresult rv = SetPositionToAbsoluteOrStatic(*element, false);
  10838    if (NS_WARN_IF(Destroyed())) {
  10839      return Err(NS_ERROR_EDITOR_DESTROYED);
  10840    }
  10841    if (NS_FAILED(rv)) {
  10842      NS_WARNING("HTMLEditor::SetPositionToAbsoluteOrStatic() failed");
  10843      return Err(rv);
  10844    }
  10845  }
  10846 
  10847  // Restoring Selection might cause destroying the HTML editor.
  10848  if (MOZ_UNLIKELY(Destroyed())) {
  10849    NS_WARNING("Destroying AutoSelectionRestorer caused destroying the editor");
  10850    return Err(NS_ERROR_EDITOR_DESTROYED);
  10851  }
  10852  return EditActionResult::HandledResult();
  10853 }
  10854 
  10855 Result<EditActionResult, nsresult> HTMLEditor::AddZIndexAsSubAction(
  10856    int32_t aChange) {
  10857  MOZ_ASSERT(IsEditActionDataAvailable());
  10858 
  10859  AutoPlaceholderBatch treatAsOneTransaction(
  10860      *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
  10861  IgnoredErrorResult ignoredError;
  10862  AutoEditSubActionNotifier startToHandleEditSubAction(
  10863      *this,
  10864      aChange < 0 ? EditSubAction::eDecreaseZIndex
  10865                  : EditSubAction::eIncreaseZIndex,
  10866      nsIEditor::eNext, ignoredError);
  10867  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
  10868    return Err(ignoredError.StealNSResult());
  10869  }
  10870  NS_WARNING_ASSERTION(
  10871      !ignoredError.Failed(),
  10872      "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
  10873 
  10874  {
  10875    Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
  10876    if (MOZ_UNLIKELY(result.isErr())) {
  10877      NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
  10878      return result;
  10879    }
  10880    if (result.inspect().Canceled()) {
  10881      return result;
  10882    }
  10883  }
  10884 
  10885  const RefPtr<Element> editingHost =
  10886      ComputeEditingHost(LimitInBodyElement::No);
  10887  if (NS_WARN_IF(!editingHost)) {
  10888    return Err(NS_ERROR_FAILURE);
  10889  }
  10890 
  10891  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
  10892  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
  10893    return Err(NS_ERROR_EDITOR_DESTROYED);
  10894  }
  10895  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
  10896                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
  10897                       "failed, but ignored");
  10898 
  10899  if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
  10900    nsresult rv = EnsureCaretNotAfterInvisibleBRElement(*editingHost);
  10901    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
  10902      return Err(NS_ERROR_EDITOR_DESTROYED);
  10903    }
  10904    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
  10905                         "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
  10906                         "failed, but ignored");
  10907    if (NS_SUCCEEDED(rv)) {
  10908      nsresult rv = PrepareInlineStylesForCaret();
  10909      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
  10910        return Err(NS_ERROR_EDITOR_DESTROYED);
  10911      }
  10912      NS_WARNING_ASSERTION(
  10913          NS_SUCCEEDED(rv),
  10914          "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
  10915    }
  10916  }
  10917 
  10918  RefPtr<Element> absolutelyPositionedElement =
  10919      GetAbsolutelyPositionedSelectionContainer();
  10920  if (!absolutelyPositionedElement) {
  10921    if (NS_WARN_IF(Destroyed())) {
  10922      return Err(NS_ERROR_EDITOR_DESTROYED);
  10923    }
  10924    NS_WARNING(
  10925        "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() returned "
  10926        "nullptr");
  10927    return Err(NS_ERROR_FAILURE);
  10928  }
  10929 
  10930  nsStyledElement* absolutelyPositionedStyledElement =
  10931      nsStyledElement::FromNode(absolutelyPositionedElement);
  10932  if (NS_WARN_IF(!absolutelyPositionedStyledElement)) {
  10933    return Err(NS_ERROR_FAILURE);
  10934  }
  10935 
  10936  {
  10937    AutoSelectionRestorer restoreSelectionLater(this);
  10938 
  10939    // MOZ_KnownLive(*absolutelyPositionedStyledElement): It's
  10940    // absolutelyPositionedElement whose type is RefPtr.
  10941    Result<int32_t, nsresult> result = AddZIndexWithTransaction(
  10942        MOZ_KnownLive(*absolutelyPositionedStyledElement), aChange);
  10943    if (MOZ_UNLIKELY(result.isErr())) {
  10944      NS_WARNING("HTMLEditor::AddZIndexWithTransaction() failed");
  10945      return result.propagateErr();
  10946    }
  10947  }
  10948 
  10949  // Restoring Selection might cause destroying the HTML editor.
  10950  if (MOZ_UNLIKELY(Destroyed())) {
  10951    NS_WARNING("Destroying AutoSelectionRestorer caused destroying the editor");
  10952    return Err(NS_ERROR_EDITOR_DESTROYED);
  10953  }
  10954 
  10955  return EditActionResult::HandledResult();
  10956 }
  10957 
  10958 }  // namespace mozilla