tor-browser

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

WhiteSpaceVisibilityKeeper.cpp (120716B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "WhiteSpaceVisibilityKeeper.h"
      7 
      8 #include "EditorDOMPoint.h"
      9 #include "EditorUtils.h"
     10 #include "ErrorList.h"
     11 #include "HTMLEditHelpers.h"  // for MoveNodeResult, SplitNodeResult
     12 #include "HTMLEditor.h"
     13 #include "HTMLEditorNestedClasses.h"  // for AutoMoveOneLineHandler
     14 #include "HTMLEditUtils.h"
     15 #include "SelectionState.h"
     16 
     17 #include "mozilla/Assertions.h"
     18 #include "mozilla/SelectionState.h"
     19 #include "mozilla/OwningNonNull.h"
     20 #include "mozilla/StaticPrefs_editor.h"  // for StaticPrefs::editor_*
     21 #include "mozilla/dom/AncestorIterator.h"
     22 
     23 #include "nsCRT.h"
     24 #include "nsContentUtils.h"
     25 #include "nsDebug.h"
     26 #include "nsError.h"
     27 #include "nsIContent.h"
     28 #include "nsIContentInlines.h"
     29 #include "nsString.h"
     30 
     31 namespace mozilla {
     32 
     33 using namespace dom;
     34 
     35 using LeafNodeType = HTMLEditUtils::LeafNodeType;
     36 using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
     37 
     38 Result<EditorDOMPoint, nsresult>
     39 WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
     40    HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToSplit,
     41    const Element& aSplittingBlockElement) {
     42  if (NS_WARN_IF(!aPointToSplit.IsInContentNodeAndValidInComposedDoc()) ||
     43      NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(aSplittingBlockElement)) ||
     44      NS_WARN_IF(!EditorUtils::IsEditableContent(
     45          *aPointToSplit.ContainerAs<nsIContent>(), EditorType::HTML))) {
     46    return Err(NS_ERROR_FAILURE);
     47  }
     48 
     49  // The container of aPointToSplit may be not splittable, e.g., selection
     50  // may be collapsed **in** a `<br>` element or a comment node.  So, look
     51  // for splittable point with climbing the tree up.
     52  EditorDOMPoint pointToSplit(aPointToSplit);
     53  for (nsIContent* content : aPointToSplit.ContainerAs<nsIContent>()
     54                                 ->InclusiveAncestorsOfType<nsIContent>()) {
     55    if (content == &aSplittingBlockElement) {
     56      break;
     57    }
     58    if (HTMLEditUtils::IsSplittableNode(*content)) {
     59      break;
     60    }
     61    pointToSplit.Set(content);
     62  }
     63 
     64  // NOTE: Chrome does not normalize white-spaces at splitting `Text` when
     65  // inserting a paragraph at least when the surrounding white-spaces being or
     66  // end with an NBSP.
     67  Result<EditorDOMPoint, nsresult> pointToSplitOrError =
     68      WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt(
     69          aHTMLEditor, pointToSplit,
     70          {NormalizeOption::StopIfFollowingWhiteSpacesStartsWithNBSP,
     71           NormalizeOption::StopIfPrecedingWhiteSpacesEndsWithNBP});
     72  if (MOZ_UNLIKELY(pointToSplitOrError.isErr())) {
     73    NS_WARNING(
     74        "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt() failed");
     75    return pointToSplitOrError.propagateErr();
     76  }
     77  pointToSplit = pointToSplitOrError.unwrap();
     78 
     79  if (NS_WARN_IF(!pointToSplit.IsInContentNode()) ||
     80      NS_WARN_IF(
     81          !pointToSplit.ContainerAs<nsIContent>()->IsInclusiveDescendantOf(
     82              &aSplittingBlockElement)) ||
     83      NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(aSplittingBlockElement)) ||
     84      NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(
     85          *pointToSplit.ContainerAs<nsIContent>()))) {
     86    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
     87  }
     88 
     89  return pointToSplit;
     90 }
     91 
     92 // static
     93 Result<MoveNodeResult, nsresult> WhiteSpaceVisibilityKeeper::
     94    MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
     95        HTMLEditor& aHTMLEditor, Element& aLeftBlockElement,
     96        Element& aRightBlockElement, const EditorDOMPoint& aAtRightBlockChild,
     97        const Maybe<nsAtom*>& aListElementTagName,
     98        const HTMLBRElement* aPrecedingInvisibleBRElement,
     99        const Element& aEditingHost) {
    100  MOZ_ASSERT(
    101      EditorUtils::IsDescendantOf(aLeftBlockElement, aRightBlockElement));
    102  MOZ_ASSERT(&aRightBlockElement == aAtRightBlockChild.GetContainer());
    103 
    104  OwningNonNull<Element> rightBlockElement = aRightBlockElement;
    105  EditorDOMPoint afterRightBlockChild = aAtRightBlockChild.NextPoint();
    106  {
    107    AutoTrackDOMPoint trackAfterRightBlockChild(aHTMLEditor.RangeUpdaterRef(),
    108                                                &afterRightBlockChild);
    109    // First, delete invisible white-spaces at start of the right block and
    110    // normalize the leading visible white-spaces.
    111    nsresult rv = WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter(
    112        aHTMLEditor, afterRightBlockChild);
    113    if (NS_FAILED(rv)) {
    114      NS_WARNING(
    115          "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter() "
    116          "failed");
    117      return Err(rv);
    118    }
    119    // Next, delete invisible white-spaces at end of the left block and
    120    // normalize the trailing visible white-spaces.
    121    rv = WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore(
    122        aHTMLEditor, EditorDOMPoint::AtEndOf(aLeftBlockElement));
    123    if (NS_FAILED(rv)) {
    124      NS_WARNING(
    125          "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore() "
    126          "failed");
    127      return Err(rv);
    128    }
    129    trackAfterRightBlockChild.FlushAndStopTracking();
    130    if (NS_WARN_IF(afterRightBlockChild.GetContainer() !=
    131                   &aRightBlockElement)) {
    132      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
    133    }
    134  }
    135  // Finally, make sure that we won't create new invisible white-spaces.
    136  {
    137    AutoTrackDOMPoint trackAfterRightBlockChild(aHTMLEditor.RangeUpdaterRef(),
    138                                                &afterRightBlockChild);
    139    Result<EditorDOMPoint, nsresult> atFirstVisibleThingOrError =
    140        WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter(
    141            aHTMLEditor, afterRightBlockChild,
    142            {NormalizeOption::StopIfFollowingWhiteSpacesStartsWithNBSP});
    143    if (MOZ_UNLIKELY(atFirstVisibleThingOrError.isErr())) {
    144      NS_WARNING(
    145          "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() failed");
    146      return atFirstVisibleThingOrError.propagateErr();
    147    }
    148    Result<EditorDOMPoint, nsresult> afterLastVisibleThingOrError =
    149        WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore(
    150            aHTMLEditor, EditorDOMPoint::AtEndOf(aLeftBlockElement), {});
    151    if (MOZ_UNLIKELY(afterLastVisibleThingOrError.isErr())) {
    152      NS_WARNING(
    153          "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() failed");
    154      return afterLastVisibleThingOrError.propagateErr();
    155    }
    156  }
    157 
    158  // XXX And afterRightBlockChild.GetContainerAs<Element>() always returns
    159  //     an element pointer so that probably here should not use
    160  //     accessors of EditorDOMPoint, should use DOM API directly instead.
    161  if (afterRightBlockChild.GetContainerAs<Element>()) {
    162    rightBlockElement = *afterRightBlockChild.ContainerAs<Element>();
    163  } else if (NS_WARN_IF(
    164                 !afterRightBlockChild.GetContainerParentAs<Element>())) {
    165    return Err(NS_ERROR_UNEXPECTED);
    166  } else {
    167    rightBlockElement = *afterRightBlockChild.GetContainerParentAs<Element>();
    168  }
    169 
    170  auto atStartOfRightText = [&]() MOZ_NEVER_INLINE_DEBUG -> EditorDOMPoint {
    171    const WSRunScanner scanner({}, EditorRawDOMPoint(&aRightBlockElement, 0u));
    172    for (EditorRawDOMPointInText atFirstChar =
    173             scanner.GetInclusiveNextCharPoint<EditorRawDOMPointInText>(
    174                 EditorRawDOMPoint(&aRightBlockElement, 0u));
    175         atFirstChar.IsSet();
    176         atFirstChar =
    177             scanner.GetInclusiveNextCharPoint<EditorRawDOMPointInText>(
    178                 atFirstChar.AfterContainer<EditorRawDOMPoint>())) {
    179      if (atFirstChar.IsContainerEmpty()) {
    180        continue;  // Ignore empty text node.
    181      }
    182      if (atFirstChar.IsCharASCIISpaceOrNBSP() &&
    183          HTMLEditUtils::IsSimplyEditableNode(
    184              *atFirstChar.ContainerAs<Text>())) {
    185        return atFirstChar.To<EditorDOMPoint>();
    186      }
    187      break;
    188    }
    189    return EditorDOMPoint();
    190  }();
    191  AutoTrackDOMPoint trackStartOfRightText(aHTMLEditor.RangeUpdaterRef(),
    192                                          &atStartOfRightText);
    193 
    194  // Do br adjustment.
    195  // XXX Why don't we delete the <br> first? If so, we can skip to track the
    196  // MoveNodeResult at last.
    197  const RefPtr<HTMLBRElement> invisibleBRElementAtEndOfLeftBlockElement =
    198      WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
    199          {WSRunScanner::Option::OnlyEditableNodes},
    200          EditorDOMPoint::AtEndOf(aLeftBlockElement));
    201  NS_ASSERTION(
    202      aPrecedingInvisibleBRElement == invisibleBRElementAtEndOfLeftBlockElement,
    203      "The preceding invisible BR element computation was different");
    204  auto moveContentResult = [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
    205      -> Result<MoveNodeResult, nsresult> {
    206    // NOTE: Keep syncing with CanMergeLeftAndRightBlockElements() of
    207    //       AutoInclusiveAncestorBlockElementsJoiner.
    208    if (NS_WARN_IF(aListElementTagName.isSome())) {
    209      // Since 2002, here was the following comment:
    210      // > The idea here is to take all children in rightListElement that are
    211      // > past offset, and pull them into leftlistElement.
    212      // However, this has never been performed because we are here only when
    213      // neither left list nor right list is a descendant of the other but
    214      // in such case, getting a list item in the right list node almost
    215      // always failed since a variable for offset of
    216      // rightListElement->GetChildAt() was not initialized.  So, it might be
    217      // a bug, but we should keep this traditional behavior for now.  If you
    218      // find when we get here, please remove this comment if we don't need to
    219      // do it.  Otherwise, please move children of the right list node to the
    220      // end of the left list node.
    221 
    222      // XXX Although, we do nothing here, but for keeping traditional
    223      //     behavior, we should mark as handled.
    224      return MoveNodeResult::HandledResult(
    225          EditorDOMPoint::AtEndOf(aLeftBlockElement));
    226    }
    227 
    228    AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
    229    // XXX Why do we ignore the result of AutoMoveOneLineHandler::Run()?
    230    NS_ASSERTION(rightBlockElement == afterRightBlockChild.GetContainer(),
    231                 "The relation is not guaranteed but assumed");
    232 #ifdef DEBUG
    233    Result<bool, nsresult> firstLineHasContent =
    234        HTMLEditor::AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
    235            EditorDOMPoint(rightBlockElement, afterRightBlockChild.Offset()),
    236            aEditingHost);
    237 #endif  // #ifdef DEBUG
    238    HTMLEditor::AutoMoveOneLineHandler lineMoverToEndOfLeftBlock(
    239        aLeftBlockElement);
    240    nsresult rv = lineMoverToEndOfLeftBlock.Prepare(
    241        aHTMLEditor,
    242        EditorDOMPoint(rightBlockElement, afterRightBlockChild.Offset()),
    243        aEditingHost);
    244    if (NS_FAILED(rv)) {
    245      NS_WARNING("AutoMoveOneLineHandler::Prepare() failed");
    246      return Err(rv);
    247    }
    248    MoveNodeResult moveResult = MoveNodeResult::IgnoredResult(
    249        EditorDOMPoint::AtEndOf(aLeftBlockElement));
    250    AutoTrackDOMMoveNodeResult trackMoveResult(aHTMLEditor.RangeUpdaterRef(),
    251                                               &moveResult);
    252    Result<MoveNodeResult, nsresult> moveFirstLineResult =
    253        lineMoverToEndOfLeftBlock.Run(aHTMLEditor, aEditingHost);
    254    if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) {
    255      NS_WARNING("AutoMoveOneLineHandler::Run() failed");
    256      return moveFirstLineResult.propagateErr();
    257    }
    258    trackMoveResult.FlushAndStopTracking();
    259 
    260 #ifdef DEBUG
    261    MOZ_ASSERT(!firstLineHasContent.isErr());
    262    if (firstLineHasContent.inspect()) {
    263      NS_ASSERTION(moveFirstLineResult.inspect().Handled(),
    264                   "Failed to consider whether moving or not something");
    265    } else {
    266      NS_ASSERTION(moveFirstLineResult.inspect().Ignored(),
    267                   "Failed to consider whether moving or not something");
    268    }
    269 #endif  // #ifdef DEBUG
    270 
    271    moveResult |= moveFirstLineResult.unwrap();
    272    // Now, all children of rightBlockElement were moved to leftBlockElement.
    273    // So, afterRightBlockChild is now invalid.
    274    afterRightBlockChild.Clear();
    275 
    276    return std::move(moveResult);
    277  }();
    278  if (MOZ_UNLIKELY(moveContentResult.isErr())) {
    279    return moveContentResult;
    280  }
    281 
    282  MoveNodeResult unwrappedMoveContentResult = moveContentResult.unwrap();
    283 
    284  trackStartOfRightText.FlushAndStopTracking();
    285  if (atStartOfRightText.IsInTextNode() &&
    286      atStartOfRightText.IsSetAndValidInComposedDoc() &&
    287      atStartOfRightText.IsMiddleOfContainer()) {
    288    AutoTrackDOMMoveNodeResult trackMoveContentResult(
    289        aHTMLEditor.RangeUpdaterRef(), &unwrappedMoveContentResult);
    290    Result<EditorDOMPoint, nsresult> startOfRightTextOrError =
    291        WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt(
    292            aHTMLEditor, atStartOfRightText.AsInText());
    293    if (MOZ_UNLIKELY(startOfRightTextOrError.isErr())) {
    294      NS_WARNING("WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt() failed");
    295      return startOfRightTextOrError.propagateErr();
    296    }
    297  }
    298 
    299  if (!invisibleBRElementAtEndOfLeftBlockElement ||
    300      !invisibleBRElementAtEndOfLeftBlockElement->IsInComposedDoc()) {
    301    return std::move(unwrappedMoveContentResult);
    302  }
    303 
    304  {
    305    AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
    306    AutoTrackDOMMoveNodeResult trackMoveContentResult(
    307        aHTMLEditor.RangeUpdaterRef(), &unwrappedMoveContentResult);
    308    nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(
    309        *invisibleBRElementAtEndOfLeftBlockElement);
    310    if (NS_FAILED(rv)) {
    311      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed, but ignored");
    312      unwrappedMoveContentResult.IgnoreCaretPointSuggestion();
    313      return Err(rv);
    314    }
    315  }
    316  return std::move(unwrappedMoveContentResult);
    317 }
    318 
    319 // static
    320 Result<MoveNodeResult, nsresult> WhiteSpaceVisibilityKeeper::
    321    MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
    322        HTMLEditor& aHTMLEditor, Element& aLeftBlockElement,
    323        Element& aRightBlockElement, const EditorDOMPoint& aAtLeftBlockChild,
    324        nsIContent& aLeftContentInBlock,
    325        const Maybe<nsAtom*>& aListElementTagName,
    326        const HTMLBRElement* aPrecedingInvisibleBRElement,
    327        const Element& aEditingHost) {
    328  MOZ_ASSERT(
    329      EditorUtils::IsDescendantOf(aRightBlockElement, aLeftBlockElement));
    330  MOZ_ASSERT(
    331      &aLeftBlockElement == &aLeftContentInBlock ||
    332      EditorUtils::IsDescendantOf(aLeftContentInBlock, aLeftBlockElement));
    333  MOZ_ASSERT(&aLeftBlockElement == aAtLeftBlockChild.GetContainer());
    334 
    335  OwningNonNull<Element> originalLeftBlockElement = aLeftBlockElement;
    336  OwningNonNull<Element> leftBlockElement = aLeftBlockElement;
    337  EditorDOMPoint atLeftBlockChild(aAtLeftBlockChild);
    338  // First, delete invisible white-spaces before the right block.
    339  {
    340    AutoTrackDOMPoint tracker(aHTMLEditor.RangeUpdaterRef(), &atLeftBlockChild);
    341    nsresult rv =
    342        WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore(
    343            aHTMLEditor, EditorDOMPoint(&aRightBlockElement));
    344    if (NS_FAILED(rv)) {
    345      NS_WARNING(
    346          "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore() "
    347          "failed");
    348      return Err(rv);
    349    }
    350    // Next, delete invisible white-spaces at start of the right block.
    351    rv = WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter(
    352        aHTMLEditor, EditorDOMPoint(&aRightBlockElement, 0u));
    353    if (NS_FAILED(rv)) {
    354      NS_WARNING(
    355          "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter() "
    356          "failed");
    357      return Err(rv);
    358    }
    359    tracker.FlushAndStopTracking();
    360    if (NS_WARN_IF(!atLeftBlockChild.IsInContentNodeAndValidInComposedDoc())) {
    361      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
    362    }
    363  }
    364  // Finally, make sure that we won't create new invisible white-spaces.
    365  AutoTrackDOMPoint tracker(aHTMLEditor.RangeUpdaterRef(), &atLeftBlockChild);
    366  Result<EditorDOMPoint, nsresult> afterLastVisibleThingOrError =
    367      WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter(
    368          aHTMLEditor, EditorDOMPoint(&aRightBlockElement, 0u),
    369          {NormalizeOption::StopIfPrecedingWhiteSpacesEndsWithNBP});
    370  if (MOZ_UNLIKELY(afterLastVisibleThingOrError.isErr())) {
    371    NS_WARNING(
    372        "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() failed");
    373    return afterLastVisibleThingOrError.propagateErr();
    374  }
    375  Result<EditorDOMPoint, nsresult> atFirstVisibleThingOrError =
    376      WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore(
    377          aHTMLEditor, atLeftBlockChild, {});
    378  if (MOZ_UNLIKELY(atFirstVisibleThingOrError.isErr())) {
    379    NS_WARNING(
    380        "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() failed");
    381    return atFirstVisibleThingOrError.propagateErr();
    382  }
    383  tracker.FlushAndStopTracking();
    384  if (NS_WARN_IF(!atLeftBlockChild.IsInContentNodeAndValidInComposedDoc())) {
    385    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
    386  }
    387 
    388  // XXX atLeftBlockChild.GetContainerAs<Element>() should always return
    389  //     an element pointer so that probably here should not use
    390  //     accessors of EditorDOMPoint, should use DOM API directly instead.
    391  if (Element* nearestAncestor =
    392          atLeftBlockChild.GetContainerOrContainerParentElement()) {
    393    leftBlockElement = *nearestAncestor;
    394  } else {
    395    return Err(NS_ERROR_UNEXPECTED);
    396  }
    397 
    398  auto atStartOfRightText = [&]() MOZ_NEVER_INLINE_DEBUG -> EditorDOMPoint {
    399    const WSRunScanner scanner({}, EditorRawDOMPoint(&aRightBlockElement, 0u));
    400    for (EditorRawDOMPointInText atFirstChar =
    401             scanner.GetInclusiveNextCharPoint<EditorRawDOMPointInText>(
    402                 EditorRawDOMPoint(&aRightBlockElement, 0u));
    403         atFirstChar.IsSet();
    404         atFirstChar =
    405             scanner.GetInclusiveNextCharPoint<EditorRawDOMPointInText>(
    406                 atFirstChar.AfterContainer<EditorRawDOMPoint>())) {
    407      if (atFirstChar.IsContainerEmpty()) {
    408        continue;  // Ignore empty text node.
    409      }
    410      if (atFirstChar.IsCharASCIISpaceOrNBSP() &&
    411          HTMLEditUtils::IsSimplyEditableNode(
    412              *atFirstChar.ContainerAs<Text>())) {
    413        return atFirstChar.To<EditorDOMPoint>();
    414      }
    415      break;
    416    }
    417    return EditorDOMPoint();
    418  }();
    419  AutoTrackDOMPoint trackStartOfRightText(aHTMLEditor.RangeUpdaterRef(),
    420                                          &atStartOfRightText);
    421 
    422  // Do br adjustment.
    423  // XXX Why don't we delete the <br> first? If so, we can skip to track the
    424  // MoveNodeResult at last.
    425  const RefPtr<HTMLBRElement> invisibleBRElementBeforeLeftBlockElement =
    426      WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
    427          {WSRunScanner::Option::OnlyEditableNodes}, atLeftBlockChild);
    428  NS_ASSERTION(
    429      aPrecedingInvisibleBRElement == invisibleBRElementBeforeLeftBlockElement,
    430      "The preceding invisible BR element computation was different");
    431  auto moveContentResult = [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
    432      -> Result<MoveNodeResult, nsresult> {
    433    // NOTE: Keep syncing with CanMergeLeftAndRightBlockElements() of
    434    //       AutoInclusiveAncestorBlockElementsJoiner.
    435    if (aListElementTagName.isSome()) {
    436      // XXX Why do we ignore the error from MoveChildrenWithTransaction()?
    437      MOZ_ASSERT(originalLeftBlockElement == atLeftBlockChild.GetContainer(),
    438                 "This is not guaranteed, but assumed");
    439 #ifdef DEBUG
    440      Result<bool, nsresult> rightBlockHasContent =
    441          aHTMLEditor.CanMoveChildren(aRightBlockElement, aLeftBlockElement);
    442 #endif  // #ifdef DEBUG
    443      MoveNodeResult moveResult = MoveNodeResult::IgnoredResult(EditorDOMPoint(
    444          atLeftBlockChild.GetContainer(), atLeftBlockChild.Offset()));
    445      AutoTrackDOMMoveNodeResult trackMoveResult(aHTMLEditor.RangeUpdaterRef(),
    446                                                 &moveResult);
    447      // TODO: Stop using HTMLEditor::PreserveWhiteSpaceStyle::No due to no
    448      // tests.
    449      AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
    450      Result<MoveNodeResult, nsresult> moveChildrenResult =
    451          aHTMLEditor.MoveChildrenWithTransaction(
    452              aRightBlockElement, moveResult.NextInsertionPointRef(),
    453              HTMLEditor::PreserveWhiteSpaceStyle::No,
    454              HTMLEditor::RemoveIfCommentNode::Yes);
    455      if (MOZ_UNLIKELY(moveChildrenResult.isErr())) {
    456        if (NS_WARN_IF(moveChildrenResult.inspectErr() ==
    457                       NS_ERROR_EDITOR_DESTROYED)) {
    458          return moveChildrenResult;
    459        }
    460        NS_WARNING(
    461            "HTMLEditor::MoveChildrenWithTransaction() failed, but ignored");
    462      } else {
    463 #ifdef DEBUG
    464        MOZ_ASSERT(!rightBlockHasContent.isErr());
    465        if (rightBlockHasContent.inspect()) {
    466          NS_ASSERTION(moveChildrenResult.inspect().Handled(),
    467                       "Failed to consider whether moving or not children");
    468        } else {
    469          NS_ASSERTION(moveChildrenResult.inspect().Ignored(),
    470                       "Failed to consider whether moving or not children");
    471        }
    472 #endif  // #ifdef DEBUG
    473        trackMoveResult.FlushAndStopTracking();
    474        moveResult |= moveChildrenResult.unwrap();
    475      }
    476      // atLeftBlockChild was moved to rightListElement.  So, it's invalid now.
    477      atLeftBlockChild.Clear();
    478 
    479      return std::move(moveResult);
    480    }
    481 
    482    // Left block is a parent of right block, and the parent of the previous
    483    // visible content.  Right block is a child and contains the contents we
    484    // want to move.
    485    EditorDOMPoint pointToMoveFirstLineContent;
    486    if (&aLeftContentInBlock == leftBlockElement) {
    487      // We are working with valid HTML, aLeftContentInBlock is a block
    488      // element, and is therefore allowed to contain aRightBlockElement. This
    489      // is the simple case, we will simply move the content in
    490      // aRightBlockElement out of its block.
    491      pointToMoveFirstLineContent = atLeftBlockChild;
    492      MOZ_ASSERT(pointToMoveFirstLineContent.GetContainer() ==
    493                 &aLeftBlockElement);
    494    } else {
    495      if (NS_WARN_IF(!aLeftContentInBlock.IsInComposedDoc())) {
    496        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
    497      }
    498      // We try to work as well as possible with HTML that's already invalid.
    499      // Although "right block" is a block, and a block must not be contained
    500      // in inline elements, reality is that broken documents do exist.  The
    501      // DIRECT parent of "left NODE" might be an inline element.  Previous
    502      // versions of this code skipped inline parents until the first block
    503      // parent was found (and used "left block" as the destination).
    504      // However, in some situations this strategy moves the content to an
    505      // unexpected position.  (see bug 200416) The new idea is to make the
    506      // moving content a sibling, next to the previous visible content.
    507      pointToMoveFirstLineContent.SetAfter(&aLeftContentInBlock);
    508      if (NS_WARN_IF(!pointToMoveFirstLineContent.IsInContentNode())) {
    509        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
    510      }
    511    }
    512 
    513    MOZ_ASSERT(pointToMoveFirstLineContent.IsSetAndValid());
    514 
    515    // Because we don't want the moving content to receive the style of the
    516    // previous content, we split the previous content's style.
    517 
    518 #ifdef DEBUG
    519    Result<bool, nsresult> firstLineHasContent =
    520        HTMLEditor::AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
    521            EditorDOMPoint(&aRightBlockElement, 0u), aEditingHost);
    522 #endif  // #ifdef DEBUG
    523 
    524    if (&aLeftContentInBlock != &aEditingHost) {
    525      Result<SplitNodeResult, nsresult> splitNodeResult =
    526          aHTMLEditor.SplitAncestorStyledInlineElementsAt(
    527              pointToMoveFirstLineContent, EditorInlineStyle::RemoveAllStyles(),
    528              HTMLEditor::SplitAtEdges::eDoNotCreateEmptyContainer);
    529      if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
    530        NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
    531        return splitNodeResult.propagateErr();
    532      }
    533      SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap();
    534      nsresult rv = unwrappedSplitNodeResult.SuggestCaretPointTo(
    535          aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
    536                        SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
    537      if (NS_FAILED(rv)) {
    538        NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
    539        return Err(rv);
    540      }
    541      if (!unwrappedSplitNodeResult.DidSplit()) {
    542        // If nothing was split, we should move the first line content to
    543        // after the parent inline elements.
    544        for (EditorDOMPoint parentPoint = pointToMoveFirstLineContent;
    545             pointToMoveFirstLineContent.IsEndOfContainer() &&
    546             pointToMoveFirstLineContent.IsInContentNode();
    547             pointToMoveFirstLineContent = EditorDOMPoint::After(
    548                 *pointToMoveFirstLineContent.ContainerAs<nsIContent>())) {
    549          if (pointToMoveFirstLineContent.GetContainer() ==
    550                  &aLeftBlockElement ||
    551              NS_WARN_IF(pointToMoveFirstLineContent.GetContainer() ==
    552                         &aEditingHost)) {
    553            break;
    554          }
    555        }
    556        if (NS_WARN_IF(!pointToMoveFirstLineContent.IsInContentNode())) {
    557          return Err(NS_ERROR_FAILURE);
    558        }
    559      } else if (unwrappedSplitNodeResult.Handled()) {
    560        // If se split something, we should move the first line contents
    561        // before the right elements.
    562        if (nsIContent* nextContentAtSplitPoint =
    563                unwrappedSplitNodeResult.GetNextContent()) {
    564          pointToMoveFirstLineContent.Set(nextContentAtSplitPoint);
    565          if (NS_WARN_IF(!pointToMoveFirstLineContent.IsInContentNode())) {
    566            return Err(NS_ERROR_FAILURE);
    567          }
    568        } else {
    569          pointToMoveFirstLineContent =
    570              unwrappedSplitNodeResult.AtSplitPoint<EditorDOMPoint>();
    571          if (NS_WARN_IF(!pointToMoveFirstLineContent.IsInContentNode())) {
    572            return Err(NS_ERROR_FAILURE);
    573          }
    574        }
    575      }
    576      MOZ_DIAGNOSTIC_ASSERT(pointToMoveFirstLineContent.IsSetAndValid());
    577    }
    578 
    579    MoveNodeResult moveResult =
    580        MoveNodeResult::IgnoredResult(pointToMoveFirstLineContent);
    581    HTMLEditor::AutoMoveOneLineHandler lineMoverToPoint(
    582        pointToMoveFirstLineContent);
    583    nsresult rv = lineMoverToPoint.Prepare(
    584        aHTMLEditor, EditorDOMPoint(&aRightBlockElement, 0u), aEditingHost);
    585    if (NS_FAILED(rv)) {
    586      NS_WARNING("AutoMoveOneLineHandler::Prepare() failed");
    587      return Err(rv);
    588    }
    589    AutoTrackDOMMoveNodeResult trackMoveResult(aHTMLEditor.RangeUpdaterRef(),
    590                                               &moveResult);
    591    AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
    592    Result<MoveNodeResult, nsresult> moveFirstLineResult =
    593        lineMoverToPoint.Run(aHTMLEditor, aEditingHost);
    594    if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) {
    595      NS_WARNING("AutoMoveOneLineHandler::Run() failed");
    596      return moveFirstLineResult.propagateErr();
    597    }
    598 
    599 #ifdef DEBUG
    600    MOZ_ASSERT(!firstLineHasContent.isErr());
    601    if (firstLineHasContent.inspect()) {
    602      NS_ASSERTION(moveFirstLineResult.inspect().Handled(),
    603                   "Failed to consider whether moving or not something");
    604    } else {
    605      NS_ASSERTION(moveFirstLineResult.inspect().Ignored(),
    606                   "Failed to consider whether moving or not something");
    607    }
    608 #endif  // #ifdef DEBUG
    609 
    610    trackMoveResult.FlushAndStopTracking();
    611    moveResult |= moveFirstLineResult.unwrap();
    612    return std::move(moveResult);
    613  }();
    614  if (MOZ_UNLIKELY(moveContentResult.isErr())) {
    615    return moveContentResult;
    616  }
    617 
    618  MoveNodeResult unwrappedMoveContentResult = moveContentResult.unwrap();
    619 
    620  trackStartOfRightText.FlushAndStopTracking();
    621  if (atStartOfRightText.IsInTextNode() &&
    622      atStartOfRightText.IsSetAndValidInComposedDoc() &&
    623      atStartOfRightText.IsMiddleOfContainer()) {
    624    AutoTrackDOMMoveNodeResult trackMoveContentResult(
    625        aHTMLEditor.RangeUpdaterRef(), &unwrappedMoveContentResult);
    626    Result<EditorDOMPoint, nsresult> startOfRightTextOrError =
    627        WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt(
    628            aHTMLEditor, atStartOfRightText.AsInText());
    629    if (MOZ_UNLIKELY(startOfRightTextOrError.isErr())) {
    630      NS_WARNING("WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt() failed");
    631      return startOfRightTextOrError.propagateErr();
    632    }
    633  }
    634 
    635  if (!invisibleBRElementBeforeLeftBlockElement ||
    636      !invisibleBRElementBeforeLeftBlockElement->IsInComposedDoc()) {
    637    return std::move(unwrappedMoveContentResult);
    638  }
    639 
    640  {
    641    AutoTrackDOMMoveNodeResult trackMoveContentResult(
    642        aHTMLEditor.RangeUpdaterRef(), &unwrappedMoveContentResult);
    643    AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
    644    nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(
    645        *invisibleBRElementBeforeLeftBlockElement);
    646    if (NS_FAILED(rv)) {
    647      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed, but ignored");
    648      unwrappedMoveContentResult.IgnoreCaretPointSuggestion();
    649      return Err(rv);
    650    }
    651  }
    652  return std::move(unwrappedMoveContentResult);
    653 }
    654 
    655 // static
    656 Result<MoveNodeResult, nsresult> WhiteSpaceVisibilityKeeper::
    657    MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
    658        HTMLEditor& aHTMLEditor, Element& aLeftBlockElement,
    659        Element& aRightBlockElement, const Maybe<nsAtom*>& aListElementTagName,
    660        const HTMLBRElement* aPrecedingInvisibleBRElement,
    661        const Element& aEditingHost) {
    662  MOZ_ASSERT(
    663      !EditorUtils::IsDescendantOf(aLeftBlockElement, aRightBlockElement));
    664  MOZ_ASSERT(
    665      !EditorUtils::IsDescendantOf(aRightBlockElement, aLeftBlockElement));
    666 
    667  // First, delete invisible white-spaces at end of the left block
    668  nsresult rv = WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore(
    669      aHTMLEditor, EditorDOMPoint::AtEndOf(aLeftBlockElement));
    670  if (NS_FAILED(rv)) {
    671    NS_WARNING(
    672        "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore() "
    673        "failed");
    674    return Err(rv);
    675  }
    676  // Next, delete invisible white-spaces at start of the right block and
    677  // normalize the leading visible white-spaces.
    678  rv = WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter(
    679      aHTMLEditor, EditorDOMPoint(&aRightBlockElement, 0u));
    680  if (NS_FAILED(rv)) {
    681    NS_WARNING(
    682        "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter() "
    683        "failed");
    684    return Err(rv);
    685  }
    686  // Finally, make sure to that we won't create new invisible white-spaces.
    687  Result<EditorDOMPoint, nsresult> atFirstVisibleThingOrError =
    688      WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter(
    689          aHTMLEditor, EditorDOMPoint(&aRightBlockElement, 0u),
    690          {NormalizeOption::StopIfFollowingWhiteSpacesStartsWithNBSP});
    691  if (MOZ_UNLIKELY(atFirstVisibleThingOrError.isErr())) {
    692    NS_WARNING(
    693        "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() failed");
    694    return atFirstVisibleThingOrError.propagateErr();
    695  }
    696  Result<EditorDOMPoint, nsresult> afterLastVisibleThingOrError =
    697      WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore(
    698          aHTMLEditor, EditorDOMPoint::AtEndOf(aLeftBlockElement), {});
    699  if (MOZ_UNLIKELY(afterLastVisibleThingOrError.isErr())) {
    700    NS_WARNING(
    701        "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore() failed");
    702    return afterLastVisibleThingOrError.propagateErr();
    703  }
    704  auto atStartOfRightText = [&]() MOZ_NEVER_INLINE_DEBUG -> EditorDOMPoint {
    705    const WSRunScanner scanner({}, EditorRawDOMPoint(&aRightBlockElement, 0u));
    706    for (EditorRawDOMPointInText atFirstChar =
    707             scanner.GetInclusiveNextCharPoint<EditorRawDOMPointInText>(
    708                 EditorRawDOMPoint(&aRightBlockElement, 0u));
    709         atFirstChar.IsSet();
    710         atFirstChar =
    711             scanner.GetInclusiveNextCharPoint<EditorRawDOMPointInText>(
    712                 atFirstChar.AfterContainer<EditorRawDOMPoint>())) {
    713      if (atFirstChar.IsContainerEmpty()) {
    714        continue;  // Ignore empty text node.
    715      }
    716      if (atFirstChar.IsCharASCIISpaceOrNBSP() &&
    717          HTMLEditUtils::IsSimplyEditableNode(
    718              *atFirstChar.ContainerAs<Text>())) {
    719        return atFirstChar.To<EditorDOMPoint>();
    720      }
    721      break;
    722    }
    723    return EditorDOMPoint();
    724  }();
    725  AutoTrackDOMPoint trackStartOfRightText(aHTMLEditor.RangeUpdaterRef(),
    726                                          &atStartOfRightText);
    727  // Do br adjustment.
    728  // XXX Why don't we delete the <br> first? If so, we can skip to track the
    729  // MoveNodeResult at last.
    730  const RefPtr<HTMLBRElement> invisibleBRElementAtEndOfLeftBlockElement =
    731      WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
    732          {WSRunScanner::Option::OnlyEditableNodes},
    733          EditorDOMPoint::AtEndOf(aLeftBlockElement));
    734  NS_ASSERTION(
    735      aPrecedingInvisibleBRElement == invisibleBRElementAtEndOfLeftBlockElement,
    736      "The preceding invisible BR element computation was different");
    737  auto moveContentResult = [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT
    738      -> Result<MoveNodeResult, nsresult> {
    739    if (aListElementTagName.isSome() ||
    740        // TODO: We should stop merging entire blocks even if they have same
    741        // white-space style because Chrome behave so.  However, it's risky to
    742        // change our behavior in the major cases so that we should do it in
    743        // a bug to manage only the change.
    744        (aLeftBlockElement.NodeInfo()->NameAtom() ==
    745             aRightBlockElement.NodeInfo()->NameAtom() &&
    746         EditorUtils::GetComputedWhiteSpaceStyles(aLeftBlockElement) ==
    747             EditorUtils::GetComputedWhiteSpaceStyles(aRightBlockElement))) {
    748      MoveNodeResult moveResult = MoveNodeResult::IgnoredResult(
    749          EditorDOMPoint::AtEndOf(aLeftBlockElement));
    750      AutoTrackDOMMoveNodeResult trackMoveResult(aHTMLEditor.RangeUpdaterRef(),
    751                                                 &moveResult);
    752      AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
    753      // Nodes are same type.  merge them.
    754      EditorDOMPoint atFirstChildOfRightNode;
    755      nsresult rv = aHTMLEditor.JoinNearestEditableNodesWithTransaction(
    756          aLeftBlockElement, aRightBlockElement, &atFirstChildOfRightNode);
    757      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    758        return Err(NS_ERROR_EDITOR_DESTROYED);
    759      }
    760      NS_WARNING_ASSERTION(
    761          NS_SUCCEEDED(rv),
    762          "HTMLEditor::JoinNearestEditableNodesWithTransaction()"
    763          " failed, but ignored");
    764      if (aListElementTagName.isSome() && atFirstChildOfRightNode.IsSet()) {
    765        Result<CreateElementResult, nsresult> convertListTypeResult =
    766            aHTMLEditor.ChangeListElementType(
    767                // XXX Shouldn't be aLeftBlockElement here?
    768                aRightBlockElement, MOZ_KnownLive(*aListElementTagName.ref()),
    769                *nsGkAtoms::li);
    770        if (MOZ_UNLIKELY(convertListTypeResult.isErr())) {
    771          if (NS_WARN_IF(convertListTypeResult.inspectErr() ==
    772                         NS_ERROR_EDITOR_DESTROYED)) {
    773            return Err(NS_ERROR_EDITOR_DESTROYED);
    774          }
    775          NS_WARNING("HTMLEditor::ChangeListElementType() failed, but ignored");
    776        } else {
    777          // There is AutoTransactionConserveSelection above, therefore, we
    778          // don't need to update selection here.
    779          convertListTypeResult.inspect().IgnoreCaretPointSuggestion();
    780        }
    781      }
    782      trackMoveResult.FlushAndStopTracking();
    783      moveResult |= MoveNodeResult::HandledResult(
    784          EditorDOMPoint::AtEndOf(aLeftBlockElement));
    785      return std::move(moveResult);
    786    }
    787 
    788 #ifdef DEBUG
    789    Result<bool, nsresult> firstLineHasContent =
    790        HTMLEditor::AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
    791            EditorDOMPoint(&aRightBlockElement, 0u), aEditingHost);
    792 #endif  // #ifdef DEBUG
    793 
    794    MoveNodeResult moveResult = MoveNodeResult::IgnoredResult(
    795        EditorDOMPoint::AtEndOf(aLeftBlockElement));
    796    // Nodes are dissimilar types.
    797    HTMLEditor::AutoMoveOneLineHandler lineMoverToEndOfLeftBlock(
    798        aLeftBlockElement);
    799    nsresult rv = lineMoverToEndOfLeftBlock.Prepare(
    800        aHTMLEditor, EditorDOMPoint(&aRightBlockElement, 0u), aEditingHost);
    801    if (NS_FAILED(rv)) {
    802      NS_WARNING("AutoMoveOneLineHandler::Prepare() failed");
    803      return Err(rv);
    804    }
    805    AutoTrackDOMMoveNodeResult trackMoveResult(aHTMLEditor.RangeUpdaterRef(),
    806                                               &moveResult);
    807    AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
    808    Result<MoveNodeResult, nsresult> moveFirstLineResult =
    809        lineMoverToEndOfLeftBlock.Run(aHTMLEditor, aEditingHost);
    810    if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) {
    811      NS_WARNING("AutoMoveOneLineHandler::Run() failed");
    812      return moveFirstLineResult.propagateErr();
    813    }
    814 
    815 #ifdef DEBUG
    816    MOZ_ASSERT(!firstLineHasContent.isErr());
    817    if (firstLineHasContent.inspect()) {
    818      NS_ASSERTION(moveFirstLineResult.inspect().Handled(),
    819                   "Failed to consider whether moving or not something");
    820    } else {
    821      NS_ASSERTION(moveFirstLineResult.inspect().Ignored(),
    822                   "Failed to consider whether moving or not something");
    823    }
    824 #endif  // #ifdef DEBUG
    825 
    826    trackMoveResult.FlushAndStopTracking();
    827    moveResult |= moveFirstLineResult.unwrap();
    828    return std::move(moveResult);
    829  }();
    830  if (MOZ_UNLIKELY(moveContentResult.isErr())) {
    831    return moveContentResult;
    832  }
    833 
    834  MoveNodeResult unwrappedMoveContentResult = moveContentResult.unwrap();
    835 
    836  trackStartOfRightText.FlushAndStopTracking();
    837  if (atStartOfRightText.IsInTextNode() &&
    838      atStartOfRightText.IsSetAndValidInComposedDoc() &&
    839      atStartOfRightText.IsMiddleOfContainer()) {
    840    AutoTrackDOMMoveNodeResult trackMoveContentResult(
    841        aHTMLEditor.RangeUpdaterRef(), &unwrappedMoveContentResult);
    842    Result<EditorDOMPoint, nsresult> startOfRightTextOrError =
    843        WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt(
    844            aHTMLEditor, atStartOfRightText.AsInText());
    845    if (MOZ_UNLIKELY(startOfRightTextOrError.isErr())) {
    846      NS_WARNING("WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt() failed");
    847      return startOfRightTextOrError.propagateErr();
    848    }
    849  }
    850 
    851  if (!invisibleBRElementAtEndOfLeftBlockElement ||
    852      !invisibleBRElementAtEndOfLeftBlockElement->IsInComposedDoc()) {
    853    unwrappedMoveContentResult.ForceToMarkAsHandled();
    854    return std::move(unwrappedMoveContentResult);
    855  }
    856 
    857  {
    858    AutoTrackDOMMoveNodeResult trackMoveContentResult(
    859        aHTMLEditor.RangeUpdaterRef(), &unwrappedMoveContentResult);
    860    AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
    861    nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(
    862        *invisibleBRElementAtEndOfLeftBlockElement);
    863    // XXX In other top level if blocks, the result of
    864    //     DeleteNodeWithTransaction() is ignored.  Why does only this result
    865    //     is respected?
    866    if (NS_FAILED(rv)) {
    867      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
    868      unwrappedMoveContentResult.IgnoreCaretPointSuggestion();
    869      return Err(rv);
    870    }
    871  }
    872  return std::move(unwrappedMoveContentResult);
    873 }
    874 
    875 // static
    876 Result<EditorDOMPoint, nsresult>
    877 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt(
    878    HTMLEditor& aHTMLEditor, const EditorDOMPointInText& aPoint) {
    879  MOZ_ASSERT(aPoint.IsSet());
    880  MOZ_ASSERT(!aPoint.IsEndOfContainer());
    881 
    882  if (!aPoint.IsCharCollapsibleASCIISpaceOrNBSP()) {
    883    return aPoint.To<EditorDOMPoint>();
    884  }
    885 
    886  const HTMLEditor::ReplaceWhiteSpacesData normalizedWhiteSpaces =
    887      aHTMLEditor.GetNormalizedStringAt(aPoint).GetMinimizedData(
    888          *aPoint.ContainerAs<Text>());
    889  if (!normalizedWhiteSpaces.ReplaceLength()) {
    890    return aPoint.To<EditorDOMPoint>();
    891  }
    892 
    893  const OwningNonNull<Text> textNode = *aPoint.ContainerAs<Text>();
    894  Result<InsertTextResult, nsresult> insertTextResultOrError =
    895      aHTMLEditor.ReplaceTextWithTransaction(textNode, normalizedWhiteSpaces);
    896  if (MOZ_UNLIKELY(insertTextResultOrError.isErr())) {
    897    NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
    898    return insertTextResultOrError.propagateErr();
    899  }
    900  return insertTextResultOrError.unwrap().UnwrapCaretPoint();
    901 }
    902 
    903 // static
    904 Result<EditorDOMPoint, nsresult>
    905 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore(
    906    HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint,
    907    NormalizeOptions aOptions  // NOLINT(performance-unnecessary-value-param)
    908 ) {
    909  MOZ_ASSERT(aPoint.IsSetAndValid());
    910  MOZ_ASSERT_IF(aPoint.IsInTextNode(), !aPoint.IsMiddleOfContainer());
    911  MOZ_ASSERT(
    912      !aOptions.contains(NormalizeOption::HandleOnlyFollowingWhiteSpaces));
    913 
    914  const RefPtr<Element> colsetBlockElement =
    915      aPoint.IsInContentNode() ? HTMLEditUtils::GetInclusiveAncestorElement(
    916                                     *aPoint.ContainerAs<nsIContent>(),
    917                                     HTMLEditUtils::ClosestEditableBlockElement,
    918                                     BlockInlineCheck::UseComputedDisplayStyle)
    919                               : nullptr;
    920  EditorDOMPoint afterLastVisibleThing(aPoint);
    921  AutoTArray<OwningNonNull<nsIContent>, 32> unnecessaryContents;
    922  for (nsIContent* previousContent =
    923           aPoint.IsInTextNode() && aPoint.IsEndOfContainer()
    924               ? aPoint.ContainerAs<Text>()
    925               : HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
    926                     aPoint,
    927                     {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock},
    928                     BlockInlineCheck::UseComputedDisplayStyle,
    929                     colsetBlockElement);
    930       previousContent;
    931       previousContent =
    932           HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
    933               EditorRawDOMPoint(previousContent),
    934               {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock},
    935               BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement)) {
    936    if (!HTMLEditUtils::IsSimplyEditableNode(*previousContent)) {
    937      // XXX Assume non-editable nodes are visible.
    938      break;
    939    }
    940    const RefPtr<Text> precedingTextNode = Text::FromNode(previousContent);
    941    if (!precedingTextNode &&
    942        HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousContent)) {
    943      afterLastVisibleThing.SetAfter(previousContent);
    944      break;
    945    }
    946    if (!precedingTextNode || !precedingTextNode->TextDataLength()) {
    947      // If it's an empty inline element like `<b></b>` or an empty `Text`,
    948      // delete it.
    949      nsIContent* emptyInlineContent =
    950          HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
    951              *previousContent, BlockInlineCheck::UseComputedDisplayStyle);
    952      if (!emptyInlineContent) {
    953        emptyInlineContent = previousContent;
    954      }
    955      unnecessaryContents.AppendElement(*emptyInlineContent);
    956      continue;
    957    }
    958    const auto atLastChar =
    959        EditorRawDOMPointInText::AtLastContentOf(*precedingTextNode);
    960    if (!atLastChar.IsCharCollapsibleASCIISpaceOrNBSP()) {
    961      afterLastVisibleThing.SetAfter(precedingTextNode);
    962      break;
    963    }
    964    if (aOptions.contains(
    965            NormalizeOption::StopIfPrecedingWhiteSpacesEndsWithNBP) &&
    966        atLastChar.IsCharNBSP()) {
    967      afterLastVisibleThing.SetAfter(precedingTextNode);
    968      break;
    969    }
    970    const HTMLEditor::ReplaceWhiteSpacesData replaceData =
    971        aHTMLEditor.GetNormalizedStringAt(atLastChar.AsInText())
    972            .GetMinimizedData(*precedingTextNode);
    973    if (!replaceData.ReplaceLength()) {
    974      afterLastVisibleThing.SetAfter(precedingTextNode);
    975      break;
    976    }
    977    // If the Text node has only invisible white-spaces, delete the node itself.
    978    if (replaceData.ReplaceLength() == precedingTextNode->TextDataLength() &&
    979        replaceData.mNormalizedString.IsEmpty()) {
    980      nsIContent* emptyInlineContent =
    981          HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
    982              *precedingTextNode, BlockInlineCheck::UseComputedDisplayStyle);
    983      if (!emptyInlineContent) {
    984        emptyInlineContent = precedingTextNode;
    985      }
    986      unnecessaryContents.AppendElement(*emptyInlineContent);
    987      continue;
    988    }
    989    Result<InsertTextResult, nsresult> replaceWhiteSpacesResultOrError =
    990        aHTMLEditor.ReplaceTextWithTransaction(*precedingTextNode, replaceData);
    991    if (MOZ_UNLIKELY(replaceWhiteSpacesResultOrError.isErr())) {
    992      NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
    993      return replaceWhiteSpacesResultOrError.propagateErr();
    994    }
    995    InsertTextResult replaceWhiteSpacesResult =
    996        replaceWhiteSpacesResultOrError.unwrap();
    997    replaceWhiteSpacesResult.IgnoreCaretPointSuggestion();
    998    afterLastVisibleThing = replaceWhiteSpacesResult.EndOfInsertedTextRef();
    999  }
   1000 
   1001  AutoTrackDOMPoint trackAfterLastVisibleThing(aHTMLEditor.RangeUpdaterRef(),
   1002                                               &afterLastVisibleThing);
   1003  for (const auto& contentToDelete : unnecessaryContents) {
   1004    if (MOZ_UNLIKELY(!contentToDelete->IsInComposedDoc())) {
   1005      continue;
   1006    }
   1007    nsresult rv =
   1008        aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(contentToDelete));
   1009    if (NS_FAILED(rv)) {
   1010      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   1011      return Err(rv);
   1012    }
   1013  }
   1014  trackAfterLastVisibleThing.FlushAndStopTracking();
   1015  if (NS_WARN_IF(
   1016          !afterLastVisibleThing.IsInContentNodeAndValidInComposedDoc())) {
   1017    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1018  }
   1019  return std::move(afterLastVisibleThing);
   1020 }
   1021 
   1022 // static
   1023 Result<EditorDOMPoint, nsresult>
   1024 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter(
   1025    HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint,
   1026    NormalizeOptions aOptions  // NOLINT(performance-unnecessary-value-param)
   1027 ) {
   1028  MOZ_ASSERT(aPoint.IsSetAndValid());
   1029  MOZ_ASSERT_IF(aPoint.IsInTextNode(), !aPoint.IsMiddleOfContainer());
   1030  MOZ_ASSERT(
   1031      !aOptions.contains(NormalizeOption::HandleOnlyPrecedingWhiteSpaces));
   1032 
   1033  const RefPtr<Element> colsetBlockElement =
   1034      aPoint.IsInContentNode() ? HTMLEditUtils::GetInclusiveAncestorElement(
   1035                                     *aPoint.ContainerAs<nsIContent>(),
   1036                                     HTMLEditUtils::ClosestEditableBlockElement,
   1037                                     BlockInlineCheck::UseComputedDisplayStyle)
   1038                               : nullptr;
   1039  EditorDOMPoint atFirstVisibleThing(aPoint);
   1040  AutoTArray<OwningNonNull<nsIContent>, 32> unnecessaryContents;
   1041  for (nsIContent* nextContent =
   1042           aPoint.IsInTextNode() && aPoint.IsStartOfContainer()
   1043               ? aPoint.ContainerAs<Text>()
   1044               : HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
   1045                     aPoint,
   1046                     {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock},
   1047                     BlockInlineCheck::UseComputedDisplayStyle,
   1048                     colsetBlockElement);
   1049       nextContent;
   1050       nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
   1051           EditorRawDOMPoint::After(*nextContent),
   1052           {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock},
   1053           BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement)) {
   1054    if (!HTMLEditUtils::IsSimplyEditableNode(*nextContent)) {
   1055      // XXX Assume non-editable nodes are visible.
   1056      break;
   1057    }
   1058    const RefPtr<Text> followingTextNode = Text::FromNode(nextContent);
   1059    if (!followingTextNode &&
   1060        HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*nextContent)) {
   1061      atFirstVisibleThing.Set(nextContent);
   1062      break;
   1063    }
   1064    if (!followingTextNode || !followingTextNode->TextDataLength()) {
   1065      // If it's an empty inline element like `<b></b>` or an empty `Text`,
   1066      // delete it.
   1067      nsIContent* emptyInlineContent =
   1068          HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
   1069              *nextContent, BlockInlineCheck::UseComputedDisplayStyle);
   1070      if (!emptyInlineContent) {
   1071        emptyInlineContent = nextContent;
   1072      }
   1073      unnecessaryContents.AppendElement(*emptyInlineContent);
   1074      continue;
   1075    }
   1076    const auto atFirstChar = EditorRawDOMPointInText(followingTextNode, 0u);
   1077    if (!atFirstChar.IsCharCollapsibleASCIISpaceOrNBSP()) {
   1078      atFirstVisibleThing.Set(followingTextNode);
   1079      break;
   1080    }
   1081    if (aOptions.contains(
   1082            NormalizeOption::StopIfPrecedingWhiteSpacesEndsWithNBP) &&
   1083        atFirstChar.IsCharNBSP()) {
   1084      atFirstVisibleThing.Set(followingTextNode);
   1085      break;
   1086    }
   1087    const HTMLEditor::ReplaceWhiteSpacesData replaceData =
   1088        aHTMLEditor.GetNormalizedStringAt(atFirstChar.AsInText())
   1089            .GetMinimizedData(*followingTextNode);
   1090    if (!replaceData.ReplaceLength()) {
   1091      atFirstVisibleThing.Set(followingTextNode);
   1092      break;
   1093    }
   1094    // If the Text node has only invisible white-spaces, delete the node itself.
   1095    if (replaceData.ReplaceLength() == followingTextNode->TextDataLength() &&
   1096        replaceData.mNormalizedString.IsEmpty()) {
   1097      nsIContent* emptyInlineContent =
   1098          HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
   1099              *followingTextNode, BlockInlineCheck::UseComputedDisplayStyle);
   1100      if (!emptyInlineContent) {
   1101        emptyInlineContent = followingTextNode;
   1102      }
   1103      unnecessaryContents.AppendElement(*emptyInlineContent);
   1104      continue;
   1105    }
   1106    Result<InsertTextResult, nsresult> replaceWhiteSpacesResultOrError =
   1107        aHTMLEditor.ReplaceTextWithTransaction(*followingTextNode, replaceData);
   1108    if (MOZ_UNLIKELY(replaceWhiteSpacesResultOrError.isErr())) {
   1109      NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
   1110      return replaceWhiteSpacesResultOrError.propagateErr();
   1111    }
   1112    replaceWhiteSpacesResultOrError.unwrap().IgnoreCaretPointSuggestion();
   1113    atFirstVisibleThing.Set(followingTextNode, 0u);
   1114    break;
   1115  }
   1116 
   1117  AutoTrackDOMPoint trackAtFirstVisibleThing(aHTMLEditor.RangeUpdaterRef(),
   1118                                             &atFirstVisibleThing);
   1119  for (const auto& contentToDelete : unnecessaryContents) {
   1120    if (MOZ_UNLIKELY(!contentToDelete->IsInComposedDoc())) {
   1121      continue;
   1122    }
   1123    nsresult rv =
   1124        aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(contentToDelete));
   1125    if (NS_FAILED(rv)) {
   1126      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   1127      return Err(rv);
   1128    }
   1129  }
   1130  trackAtFirstVisibleThing.FlushAndStopTracking();
   1131  if (NS_WARN_IF(!atFirstVisibleThing.IsInContentNodeAndValidInComposedDoc())) {
   1132    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1133  }
   1134  return std::move(atFirstVisibleThing);
   1135 }
   1136 
   1137 // static
   1138 Result<EditorDOMPoint, nsresult>
   1139 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt(
   1140    HTMLEditor& aHTMLEditor, const EditorDOMPointInText& aPointToSplit,
   1141    NormalizeOptions aOptions  // NOLINT(performance-unnecessary-value-param)
   1142 ) {
   1143  MOZ_ASSERT(aPointToSplit.IsSetAndValid());
   1144 
   1145  if (EditorUtils::IsWhiteSpacePreformatted(
   1146          *aPointToSplit.ContainerAs<Text>())) {
   1147    return aPointToSplit.To<EditorDOMPoint>();
   1148  }
   1149 
   1150  const OwningNonNull<Text> textNode = *aPointToSplit.ContainerAs<Text>();
   1151  if (!textNode->TextDataLength()) {
   1152    // Delete if it's an empty `Text` node and removable.
   1153    if (!HTMLEditUtils::IsRemovableNode(*textNode)) {
   1154      // It's logically odd to call this for non-editable `Text`, but it may
   1155      // happen if surrounding white-space sequence contains empty non-editable
   1156      // `Text`.  In that case, the caller needs to normalize its preceding
   1157      // `Text` nodes too.
   1158      return EditorDOMPoint();
   1159    }
   1160    const nsCOMPtr<nsINode> parentNode = textNode->GetParentNode();
   1161    const nsCOMPtr<nsIContent> nextSibling = textNode->GetNextSibling();
   1162    nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(textNode);
   1163    if (NS_FAILED(rv)) {
   1164      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   1165      return Err(rv);
   1166    }
   1167    if (NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parentNode)) {
   1168      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1169    }
   1170    return nextSibling ? EditorDOMPoint(nextSibling)
   1171                       : EditorDOMPoint::AtEndOf(*parentNode);
   1172  }
   1173 
   1174  const HTMLEditor::ReplaceWhiteSpacesData replacePrecedingWhiteSpacesData =
   1175      aPointToSplit.IsStartOfContainer() ||
   1176              aOptions.contains(
   1177                  NormalizeOption::HandleOnlyFollowingWhiteSpaces) ||
   1178              (aOptions.contains(
   1179                   NormalizeOption::StopIfPrecedingWhiteSpacesEndsWithNBP) &&
   1180               aPointToSplit.IsPreviousCharNBSP())
   1181          ? HTMLEditor::ReplaceWhiteSpacesData()
   1182          : aHTMLEditor.GetPrecedingNormalizedStringToSplitAt(aPointToSplit);
   1183  const HTMLEditor::ReplaceWhiteSpacesData replaceFollowingWhiteSpaceData =
   1184      aPointToSplit.IsEndOfContainer() ||
   1185              aOptions.contains(
   1186                  NormalizeOption::HandleOnlyPrecedingWhiteSpaces) ||
   1187              (aOptions.contains(
   1188                   NormalizeOption::StopIfFollowingWhiteSpacesStartsWithNBSP) &&
   1189               aPointToSplit.IsCharNBSP())
   1190          ? HTMLEditor::ReplaceWhiteSpacesData()
   1191          : aHTMLEditor.GetFollowingNormalizedStringToSplitAt(aPointToSplit);
   1192  const HTMLEditor::ReplaceWhiteSpacesData replaceWhiteSpacesData =
   1193      (replacePrecedingWhiteSpacesData + replaceFollowingWhiteSpaceData)
   1194          .GetMinimizedData(*textNode);
   1195  if (!replaceWhiteSpacesData.ReplaceLength()) {
   1196    return aPointToSplit.To<EditorDOMPoint>();
   1197  }
   1198  if (replaceWhiteSpacesData.mNormalizedString.IsEmpty() &&
   1199      replaceWhiteSpacesData.ReplaceLength() == textNode->TextDataLength()) {
   1200    // If there is only invisible white-spaces, mNormalizedString is empty
   1201    // string but replace length is same the the `Text` length. In this case, we
   1202    // should delete the `Text` to avoid empty `Text` to stay in the DOM tree.
   1203    const nsCOMPtr<nsINode> parentNode = textNode->GetParentNode();
   1204    const nsCOMPtr<nsIContent> nextSibling = textNode->GetNextSibling();
   1205    nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(textNode);
   1206    if (NS_FAILED(rv)) {
   1207      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   1208      return Err(rv);
   1209    }
   1210    if (NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parentNode)) {
   1211      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1212    }
   1213    return nextSibling ? EditorDOMPoint(nextSibling)
   1214                       : EditorDOMPoint::AtEndOf(*parentNode);
   1215  }
   1216  Result<InsertTextResult, nsresult> replaceWhiteSpacesResultOrError =
   1217      aHTMLEditor.ReplaceTextWithTransaction(textNode, replaceWhiteSpacesData);
   1218  if (MOZ_UNLIKELY(replaceWhiteSpacesResultOrError.isErr())) {
   1219    NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
   1220    return replaceWhiteSpacesResultOrError.propagateErr();
   1221  }
   1222  replaceWhiteSpacesResultOrError.unwrap().IgnoreCaretPointSuggestion();
   1223  const uint32_t offsetToSplit =
   1224      aPointToSplit.Offset() - replacePrecedingWhiteSpacesData.ReplaceLength() +
   1225      replacePrecedingWhiteSpacesData.mNormalizedString.Length();
   1226  if (NS_WARN_IF(textNode->TextDataLength() < offsetToSplit)) {
   1227    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1228  }
   1229  return EditorDOMPoint(textNode, offsetToSplit);
   1230 }
   1231 
   1232 // static
   1233 Result<EditorDOMPoint, nsresult>
   1234 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt(
   1235    HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToSplit,
   1236    NormalizeOptions aOptions  // NOLINT(performance-unnecessary-value-param)
   1237 ) {
   1238  MOZ_ASSERT(aPointToSplit.IsSet());
   1239 
   1240  // If the insertion point is not in composed doc, we're probably initializing
   1241  // an element which will be inserted.  In such case, the caller should own the
   1242  // responsibility for normalizing the white-spaces.
   1243  if (!aPointToSplit.IsInComposedDoc()) {
   1244    return aPointToSplit;
   1245  }
   1246 
   1247  EditorDOMPoint pointToSplit(aPointToSplit);
   1248  {
   1249    AutoTrackDOMPoint trackPointToSplit(aHTMLEditor.RangeUpdaterRef(),
   1250                                        &pointToSplit);
   1251    Result<EditorDOMPoint, nsresult> pointToSplitOrError =
   1252        WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces(aHTMLEditor,
   1253                                                                 pointToSplit);
   1254    if (MOZ_UNLIKELY(pointToSplitOrError.isErr())) {
   1255      NS_WARNING(
   1256          "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces() failed");
   1257      return pointToSplitOrError.propagateErr();
   1258    }
   1259  }
   1260 
   1261  if (NS_WARN_IF(!pointToSplit.IsInContentNode())) {
   1262    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1263  }
   1264 
   1265  if (pointToSplit.IsInTextNode()) {
   1266    Result<EditorDOMPoint, nsresult> pointToSplitOrError =
   1267        WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt(
   1268            aHTMLEditor, pointToSplit.AsInText(), aOptions);
   1269    if (MOZ_UNLIKELY(pointToSplitOrError.isErr())) {
   1270      NS_WARNING(
   1271          "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt() "
   1272          "failed");
   1273      return pointToSplitOrError.propagateErr();
   1274    }
   1275    pointToSplit = pointToSplitOrError.unwrap().To<EditorDOMPoint>();
   1276    if (NS_WARN_IF(!pointToSplit.IsInContentNode())) {
   1277      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1278    }
   1279    // If we normalize white-spaces in middle of the `Text`, we don't need to
   1280    // touch surrounding `Text` nodes.
   1281    if (pointToSplit.IsMiddleOfContainer()) {
   1282      return pointToSplit;
   1283    }
   1284  }
   1285 
   1286  // Preceding and/or following white-space sequence may be across multiple
   1287  // `Text` nodes.  Then, they may become unexpectedly visible without
   1288  // normalizing the white-spaces.  Therefore, we need to list up all possible
   1289  // `Text` nodes first. Then, normalize them unless the `Text` is not
   1290  const RefPtr<Element> closestBlockElement =
   1291      HTMLEditUtils::GetInclusiveAncestorElement(
   1292          *pointToSplit.ContainerAs<nsIContent>(),
   1293          HTMLEditUtils::ClosestBlockElement,
   1294          BlockInlineCheck::UseComputedDisplayStyle);
   1295  AutoTArray<OwningNonNull<Text>, 3> precedingTextNodes, followingTextNodes;
   1296  if (!pointToSplit.IsInTextNode() || pointToSplit.IsStartOfContainer()) {
   1297    for (nsCOMPtr<nsIContent> previousContent =
   1298             HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1299                 pointToSplit, {LeafNodeType::LeafNodeOrChildBlock},
   1300                 BlockInlineCheck::UseComputedDisplayStyle,
   1301                 closestBlockElement);
   1302         previousContent;
   1303         previousContent =
   1304             HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1305                 *previousContent, {LeafNodeType::LeafNodeOrChildBlock},
   1306                 BlockInlineCheck::UseComputedDisplayStyle,
   1307                 closestBlockElement)) {
   1308      if (auto* const textNode = Text::FromNode(previousContent)) {
   1309        if (!HTMLEditUtils::IsSimplyEditableNode(*textNode) &&
   1310            textNode->TextDataLength()) {
   1311          break;
   1312        }
   1313        if (aOptions.contains(
   1314                NormalizeOption::StopIfPrecedingWhiteSpacesEndsWithNBP) &&
   1315            textNode->DataBuffer().SafeLastChar() == HTMLEditUtils::kNBSP) {
   1316          break;
   1317        }
   1318        precedingTextNodes.AppendElement(*textNode);
   1319        if (textNode->TextIsOnlyWhitespace()) {
   1320          // white-space only `Text` will be removed, so, we need to check
   1321          // preceding one too.
   1322          continue;
   1323        }
   1324        break;
   1325      }
   1326      if (auto* const element = Element::FromNode(previousContent)) {
   1327        if (HTMLEditUtils::IsBlockElement(
   1328                *element, BlockInlineCheck::UseComputedDisplayStyle) ||
   1329            !HTMLEditUtils::IsContainerNode(*element) ||
   1330            HTMLEditUtils::IsReplacedElement(*element)) {
   1331          break;
   1332        }
   1333        // Ignore invisible inline elements
   1334      }
   1335    }
   1336  }
   1337  if (!pointToSplit.IsInTextNode() || pointToSplit.IsEndOfContainer()) {
   1338    for (nsCOMPtr<nsIContent> nextContent =
   1339             HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
   1340                 pointToSplit, {LeafNodeType::LeafNodeOrChildBlock},
   1341                 BlockInlineCheck::UseComputedDisplayStyle,
   1342                 closestBlockElement);
   1343         nextContent;
   1344         nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
   1345             *nextContent, {LeafNodeType::LeafNodeOrChildBlock},
   1346             BlockInlineCheck::UseComputedDisplayStyle, closestBlockElement)) {
   1347      if (auto* const textNode = Text::FromNode(nextContent)) {
   1348        if (!HTMLEditUtils::IsSimplyEditableNode(*textNode) &&
   1349            textNode->TextDataLength()) {
   1350          break;
   1351        }
   1352        if (aOptions.contains(
   1353                NormalizeOption::StopIfFollowingWhiteSpacesStartsWithNBSP) &&
   1354            textNode->DataBuffer().SafeFirstChar() == HTMLEditUtils::kNBSP) {
   1355          break;
   1356        }
   1357        followingTextNodes.AppendElement(*textNode);
   1358        if (textNode->TextIsOnlyWhitespace() &&
   1359            EditorUtils::IsWhiteSpacePreformatted(*textNode)) {
   1360          // white-space only `Text` will be removed, so, we need to check next
   1361          // one too.
   1362          continue;
   1363        }
   1364        break;
   1365      }
   1366      if (auto* const element = Element::FromNode(nextContent)) {
   1367        if (HTMLEditUtils::IsBlockElement(
   1368                *element, BlockInlineCheck::UseComputedDisplayStyle) ||
   1369            !HTMLEditUtils::IsContainerNode(*element) ||
   1370            HTMLEditUtils::IsReplacedElement(*element)) {
   1371          break;
   1372        }
   1373        // Ignore invisible inline elements
   1374      }
   1375    }
   1376  }
   1377  AutoTrackDOMPoint trackPointToSplit(aHTMLEditor.RangeUpdaterRef(),
   1378                                      &pointToSplit);
   1379  for (const auto& textNode : precedingTextNodes) {
   1380    Result<EditorDOMPoint, nsresult> normalizeWhiteSpacesResultOrError =
   1381        WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt(
   1382            aHTMLEditor, EditorDOMPointInText::AtEndOf(textNode), aOptions);
   1383    if (MOZ_UNLIKELY(normalizeWhiteSpacesResultOrError.isErr())) {
   1384      NS_WARNING(
   1385          "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt() "
   1386          "failed");
   1387      return normalizeWhiteSpacesResultOrError.propagateErr();
   1388    }
   1389    if (normalizeWhiteSpacesResultOrError.inspect().IsInTextNode() &&
   1390        !normalizeWhiteSpacesResultOrError.inspect().IsStartOfContainer()) {
   1391      // The white-space sequence started from middle of this node, so, we need
   1392      // to do this for the preceding nodes.
   1393      break;
   1394    }
   1395  }
   1396  for (const auto& textNode : followingTextNodes) {
   1397    Result<EditorDOMPoint, nsresult> normalizeWhiteSpacesResultOrError =
   1398        WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt(
   1399            aHTMLEditor, EditorDOMPointInText(textNode, 0u), aOptions);
   1400    if (MOZ_UNLIKELY(normalizeWhiteSpacesResultOrError.isErr())) {
   1401      NS_WARNING(
   1402          "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt() "
   1403          "failed");
   1404      return normalizeWhiteSpacesResultOrError.propagateErr();
   1405    }
   1406    if (normalizeWhiteSpacesResultOrError.inspect().IsInTextNode() &&
   1407        !normalizeWhiteSpacesResultOrError.inspect().IsEndOfContainer()) {
   1408      // The white-space sequence ended in middle of this node, so, we need
   1409      // to do this for the following nodes.
   1410      break;
   1411    }
   1412  }
   1413  trackPointToSplit.FlushAndStopTracking();
   1414  if (NS_WARN_IF(!pointToSplit.IsInContentNode())) {
   1415    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1416  }
   1417  return std::move(pointToSplit);
   1418 }
   1419 
   1420 Result<EditorDOMRange, nsresult>
   1421 WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToJoin(
   1422    HTMLEditor& aHTMLEditor, const EditorDOMRange& aRangeToDelete) {
   1423  MOZ_ASSERT(!aRangeToDelete.Collapsed());
   1424 
   1425  // Special case if the range for deleting text in same `Text`.  In the case,
   1426  // we need to normalize the white-space sequence which may be joined after
   1427  // deletion.
   1428  if (aRangeToDelete.StartRef().IsInTextNode() &&
   1429      aRangeToDelete.InSameContainer()) {
   1430    const RefPtr<Text> textNode = aRangeToDelete.StartRef().ContainerAs<Text>();
   1431    Result<EditorDOMRange, nsresult> rangeToDeleteOrError =
   1432        WhiteSpaceVisibilityKeeper::
   1433            NormalizeSurroundingWhiteSpacesToDeleteCharacters(
   1434                aHTMLEditor, *textNode, aRangeToDelete.StartRef().Offset(),
   1435                aRangeToDelete.EndRef().Offset() -
   1436                    aRangeToDelete.StartRef().Offset());
   1437    NS_WARNING_ASSERTION(
   1438        rangeToDeleteOrError.isOk(),
   1439        "WhiteSpaceVisibilityKeeper::"
   1440        "NormalizeSurroundingWhiteSpacesToDeleteCharacters() failed");
   1441    return rangeToDeleteOrError;
   1442  }
   1443 
   1444  EditorDOMRange rangeToDelete(aRangeToDelete);
   1445  // First, delete all invisible white-spaces around the end boundary.
   1446  // The end boundary may be middle of invisible white-spaces.  If so,
   1447  // NormalizeWhiteSpacesToSplitTextNodeAt() won't work well for this.
   1448  {
   1449    AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(),
   1450                                         &rangeToDelete);
   1451    const WSScanResult nextThing =
   1452        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   1453            {}, rangeToDelete.StartRef());
   1454    if (nextThing.ReachedLineBoundary()) {
   1455      nsresult rv =
   1456          WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore(
   1457              aHTMLEditor, nextThing.PointAtReachedContent<EditorDOMPoint>());
   1458      if (NS_FAILED(rv)) {
   1459        NS_WARNING(
   1460            "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore() "
   1461            "failed");
   1462        return Err(rv);
   1463      }
   1464    } else {
   1465      Result<EditorDOMPoint, nsresult>
   1466          deleteInvisibleLeadingWhiteSpaceResultOrError =
   1467              WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces(
   1468                  aHTMLEditor, rangeToDelete.EndRef());
   1469      if (MOZ_UNLIKELY(deleteInvisibleLeadingWhiteSpaceResultOrError.isErr())) {
   1470        NS_WARNING(
   1471            "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces() "
   1472            "failed");
   1473        return deleteInvisibleLeadingWhiteSpaceResultOrError.propagateErr();
   1474      }
   1475    }
   1476    trackRangeToDelete.FlushAndStopTracking();
   1477    if (NS_WARN_IF(!rangeToDelete.IsPositionedAndValidInComposedDoc())) {
   1478      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1479    }
   1480  }
   1481 
   1482  // Then, normalize white-spaces after the end boundary.
   1483  if (rangeToDelete.EndRef().IsInTextNode() &&
   1484      rangeToDelete.EndRef().IsMiddleOfContainer()) {
   1485    Result<EditorDOMPoint, nsresult> pointToSplitOrError =
   1486        WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt(
   1487            aHTMLEditor, rangeToDelete.EndRef().AsInText(),
   1488            {NormalizeOption::HandleOnlyFollowingWhiteSpaces});
   1489    if (MOZ_UNLIKELY(pointToSplitOrError.isErr())) {
   1490      NS_WARNING(
   1491          "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt("
   1492          ") failed");
   1493      return pointToSplitOrError.propagateErr();
   1494    }
   1495    EditorDOMPoint pointToSplit = pointToSplitOrError.unwrap();
   1496    if (pointToSplit.IsSet() && pointToSplit != rangeToDelete.EndRef()) {
   1497      MOZ_ASSERT(rangeToDelete.StartRef().EqualsOrIsBefore(pointToSplit));
   1498      rangeToDelete.SetEnd(std::move(pointToSplit));
   1499    }
   1500  } else {
   1501    AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(),
   1502                                         &rangeToDelete);
   1503    Result<EditorDOMPoint, nsresult> atFirstVisibleThingOrError =
   1504        WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter(
   1505            aHTMLEditor, rangeToDelete.EndRef(), {});
   1506    if (MOZ_UNLIKELY(atFirstVisibleThingOrError.isErr())) {
   1507      NS_WARNING(
   1508          "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() failed");
   1509      return atFirstVisibleThingOrError.propagateErr();
   1510    }
   1511    trackRangeToDelete.FlushAndStopTracking();
   1512    if (NS_WARN_IF(!rangeToDelete.IsPositionedAndValidInComposedDoc())) {
   1513      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1514    }
   1515  }
   1516 
   1517  // If cleaning up the white-spaces around the end boundary made the range
   1518  // collapsed, the range was in invisible white-spaces.  So, in the case, we
   1519  // don't need to do nothing.
   1520  if (MOZ_UNLIKELY(rangeToDelete.Collapsed())) {
   1521    return rangeToDelete;
   1522  }
   1523 
   1524  // Next, delete the invisible white-spaces around the start boundary.
   1525  {
   1526    AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(),
   1527                                         &rangeToDelete);
   1528    Result<EditorDOMPoint, nsresult>
   1529        deleteInvisibleTrailingWhiteSpaceResultOrError =
   1530            WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces(
   1531                aHTMLEditor, rangeToDelete.StartRef());
   1532    if (MOZ_UNLIKELY(deleteInvisibleTrailingWhiteSpaceResultOrError.isErr())) {
   1533      NS_WARNING(
   1534          "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces() failed");
   1535      return deleteInvisibleTrailingWhiteSpaceResultOrError.propagateErr();
   1536    }
   1537    trackRangeToDelete.FlushAndStopTracking();
   1538    if (NS_WARN_IF(!rangeToDelete.IsPositionedAndValidInComposedDoc())) {
   1539      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1540    }
   1541  }
   1542 
   1543  // Finally, normalize white-spaces before the start boundary only when
   1544  // the start boundary is middle of a `Text` node.  This is compatible with
   1545  // the other browsers.
   1546  if (rangeToDelete.StartRef().IsInTextNode() &&
   1547      rangeToDelete.StartRef().IsMiddleOfContainer()) {
   1548    AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(),
   1549                                         &rangeToDelete);
   1550    Result<EditorDOMPoint, nsresult> afterLastVisibleThingOrError =
   1551        WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt(
   1552            aHTMLEditor, rangeToDelete.StartRef().AsInText(),
   1553            {NormalizeOption::HandleOnlyPrecedingWhiteSpaces});
   1554    if (MOZ_UNLIKELY(afterLastVisibleThingOrError.isErr())) {
   1555      NS_WARNING(
   1556          "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt() "
   1557          "failed");
   1558      return afterLastVisibleThingOrError.propagateErr();
   1559    }
   1560    trackRangeToDelete.FlushAndStopTracking();
   1561    EditorDOMPoint pointToSplit = afterLastVisibleThingOrError.unwrap();
   1562    if (pointToSplit.IsSet() && pointToSplit != rangeToDelete.StartRef()) {
   1563      MOZ_ASSERT(pointToSplit.EqualsOrIsBefore(rangeToDelete.EndRef()));
   1564      rangeToDelete.SetStart(std::move(pointToSplit));
   1565    }
   1566  }
   1567  return rangeToDelete;
   1568 }
   1569 
   1570 Result<EditorDOMRange, nsresult>
   1571 WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToDeleteCharacters(
   1572    HTMLEditor& aHTMLEditor, Text& aTextNode, uint32_t aOffset,
   1573    uint32_t aLength) {
   1574  MOZ_ASSERT(aOffset <= aTextNode.TextDataLength());
   1575  MOZ_ASSERT(aOffset + aLength <= aTextNode.TextDataLength());
   1576 
   1577  const HTMLEditor::ReplaceWhiteSpacesData normalizedWhiteSpacesData =
   1578      aHTMLEditor.GetSurroundingNormalizedStringToDelete(aTextNode, aOffset,
   1579                                                         aLength);
   1580  EditorDOMRange rangeToDelete(EditorDOMPoint(&aTextNode, aOffset),
   1581                               EditorDOMPoint(&aTextNode, aOffset + aLength));
   1582  if (!normalizedWhiteSpacesData.ReplaceLength()) {
   1583    return rangeToDelete;
   1584  }
   1585  // mNewOffsetAfterReplace is set to aOffset after applying replacing the
   1586  // range.
   1587  MOZ_ASSERT(normalizedWhiteSpacesData.mNewOffsetAfterReplace != UINT32_MAX);
   1588  MOZ_ASSERT(normalizedWhiteSpacesData.mNewOffsetAfterReplace >=
   1589             normalizedWhiteSpacesData.mReplaceStartOffset);
   1590  MOZ_ASSERT(normalizedWhiteSpacesData.mNewOffsetAfterReplace <=
   1591             normalizedWhiteSpacesData.mReplaceEndOffset);
   1592 #ifdef DEBUG
   1593  {
   1594    const HTMLEditor::ReplaceWhiteSpacesData
   1595        normalizedPrecedingWhiteSpacesData =
   1596            normalizedWhiteSpacesData.PreviousDataOfNewOffset(aOffset);
   1597    const HTMLEditor::ReplaceWhiteSpacesData
   1598        normalizedFollowingWhiteSpacesData =
   1599            normalizedWhiteSpacesData.NextDataOfNewOffset(aOffset + aLength);
   1600    MOZ_ASSERT(normalizedPrecedingWhiteSpacesData.ReplaceLength() + aLength +
   1601                   normalizedFollowingWhiteSpacesData.ReplaceLength() ==
   1602               normalizedWhiteSpacesData.ReplaceLength());
   1603    MOZ_ASSERT(
   1604        normalizedPrecedingWhiteSpacesData.mNormalizedString.Length() +
   1605            normalizedFollowingWhiteSpacesData.mNormalizedString.Length() ==
   1606        normalizedWhiteSpacesData.mNormalizedString.Length());
   1607  }
   1608 #endif
   1609  const HTMLEditor::ReplaceWhiteSpacesData normalizedPrecedingWhiteSpacesData =
   1610      normalizedWhiteSpacesData.PreviousDataOfNewOffset(aOffset)
   1611          .GetMinimizedData(aTextNode);
   1612  const HTMLEditor::ReplaceWhiteSpacesData normalizedFollowingWhiteSpacesData =
   1613      normalizedWhiteSpacesData.NextDataOfNewOffset(aOffset + aLength)
   1614          .GetMinimizedData(aTextNode);
   1615  if (normalizedFollowingWhiteSpacesData.ReplaceLength()) {
   1616    AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(),
   1617                                         &rangeToDelete);
   1618    Result<InsertTextResult, nsresult>
   1619        replaceFollowingWhiteSpacesResultOrError =
   1620            aHTMLEditor.ReplaceTextWithTransaction(
   1621                aTextNode, normalizedFollowingWhiteSpacesData);
   1622    if (MOZ_UNLIKELY(replaceFollowingWhiteSpacesResultOrError.isErr())) {
   1623      NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
   1624      return replaceFollowingWhiteSpacesResultOrError.propagateErr();
   1625    }
   1626    trackRangeToDelete.FlushAndStopTracking();
   1627    if (NS_WARN_IF(!rangeToDelete.IsPositioned())) {
   1628      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1629    }
   1630  }
   1631  if (normalizedPrecedingWhiteSpacesData.ReplaceLength()) {
   1632    AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(),
   1633                                         &rangeToDelete);
   1634    Result<InsertTextResult, nsresult>
   1635        replacePrecedingWhiteSpacesResultOrError =
   1636            aHTMLEditor.ReplaceTextWithTransaction(
   1637                aTextNode, normalizedPrecedingWhiteSpacesData);
   1638    if (MOZ_UNLIKELY(replacePrecedingWhiteSpacesResultOrError.isErr())) {
   1639      NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
   1640      return replacePrecedingWhiteSpacesResultOrError.propagateErr();
   1641    }
   1642    trackRangeToDelete.FlushAndStopTracking();
   1643    if (NS_WARN_IF(!rangeToDelete.IsPositioned())) {
   1644      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1645    }
   1646  }
   1647  return std::move(rangeToDelete);
   1648 }
   1649 
   1650 // static
   1651 Result<CreateLineBreakResult, nsresult>
   1652 WhiteSpaceVisibilityKeeper::InsertLineBreak(
   1653    LineBreakType aLineBreakType, HTMLEditor& aHTMLEditor,
   1654    const EditorDOMPoint& aPointToInsert) {
   1655  if (MOZ_UNLIKELY(NS_WARN_IF(!aPointToInsert.IsSet()))) {
   1656    return Err(NS_ERROR_INVALID_ARG);
   1657  }
   1658 
   1659  EditorDOMPoint pointToInsert(aPointToInsert);
   1660  // Chrome does not normalize preceding white-spaces at least when it ends
   1661  // with an NBSP.
   1662  Result<EditorDOMPoint, nsresult>
   1663      normalizeSurroundingWhiteSpacesResultOrError =
   1664          WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt(
   1665              aHTMLEditor, aPointToInsert,
   1666              {NormalizeOption::StopIfPrecedingWhiteSpacesEndsWithNBP});
   1667  if (MOZ_UNLIKELY(normalizeSurroundingWhiteSpacesResultOrError.isErr())) {
   1668    NS_WARNING(
   1669        "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt() failed");
   1670    return normalizeSurroundingWhiteSpacesResultOrError.propagateErr();
   1671  }
   1672  pointToInsert = normalizeSurroundingWhiteSpacesResultOrError.unwrap();
   1673  if (NS_WARN_IF(!pointToInsert.IsSet())) {
   1674    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1675  }
   1676 
   1677  Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError =
   1678      aHTMLEditor.InsertLineBreak(WithTransaction::Yes, aLineBreakType,
   1679                                  pointToInsert);
   1680  NS_WARNING_ASSERTION(insertBRElementResultOrError.isOk(),
   1681                       "HTMLEditor::InsertLineBreak(WithTransaction::Yes, "
   1682                       "aLineBreakType, eNone) failed");
   1683  return insertBRElementResultOrError;
   1684 }
   1685 
   1686 nsresult WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter(
   1687    HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint) {
   1688  MOZ_ASSERT(aPoint.IsInContentNode());
   1689 
   1690  const RefPtr<Element> colsetBlockElement =
   1691      HTMLEditUtils::GetInclusiveAncestorElement(
   1692          *aPoint.ContainerAs<nsIContent>(),
   1693          HTMLEditUtils::ClosestEditableBlockElement,
   1694          BlockInlineCheck::UseComputedDisplayStyle);
   1695  EditorDOMPoint atFirstInvisibleWhiteSpace;
   1696  AutoTArray<OwningNonNull<nsIContent>, 32> unnecessaryContents;
   1697  for (nsIContent* nextContent =
   1698           HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
   1699               aPoint,
   1700               {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock,
   1701                HTMLEditUtils::LeafNodeType::TreatCommentAsLeafNode},
   1702               BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement);
   1703       nextContent;
   1704       nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
   1705           EditorRawDOMPoint::After(*nextContent),
   1706           {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock,
   1707            HTMLEditUtils::LeafNodeType::TreatCommentAsLeafNode},
   1708           BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement)) {
   1709    if (!HTMLEditUtils::IsSimplyEditableNode(*nextContent)) {
   1710      // XXX Assume non-editable nodes are visible.
   1711      break;
   1712    }
   1713    const RefPtr<Text> followingTextNode = Text::FromNode(nextContent);
   1714    if (!followingTextNode &&
   1715        HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*nextContent)) {
   1716      break;
   1717    }
   1718    if (!followingTextNode || !followingTextNode->TextDataLength()) {
   1719      // If it's an empty inline element like `<b></b>` or an empty `Text`,
   1720      // delete it.
   1721      nsIContent* emptyInlineContent =
   1722          HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
   1723              *nextContent, BlockInlineCheck::UseComputedDisplayStyle);
   1724      if (!emptyInlineContent) {
   1725        emptyInlineContent = nextContent;
   1726      }
   1727      unnecessaryContents.AppendElement(*emptyInlineContent);
   1728      continue;
   1729    }
   1730    const EditorRawDOMPointInText atFirstChar(followingTextNode, 0u);
   1731    if (!atFirstChar.IsCharCollapsibleASCIISpace()) {
   1732      break;
   1733    }
   1734    // If the preceding Text is collapsed and invisible, we should delete it
   1735    // and keep deleting preceding invisible white-spaces.
   1736    if (!HTMLEditUtils::IsVisibleTextNode(*followingTextNode)) {
   1737      nsIContent* emptyInlineContent =
   1738          HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
   1739              *followingTextNode, BlockInlineCheck::UseComputedDisplayStyle);
   1740      if (!emptyInlineContent) {
   1741        emptyInlineContent = followingTextNode;
   1742      }
   1743      unnecessaryContents.AppendElement(*emptyInlineContent);
   1744      continue;
   1745    }
   1746    Result<EditorDOMPoint, nsresult> startOfTextOrError =
   1747        WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces(
   1748            aHTMLEditor, EditorDOMPoint(followingTextNode, 0u));
   1749    if (MOZ_UNLIKELY(startOfTextOrError.isErr())) {
   1750      NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
   1751      return startOfTextOrError.unwrapErr();
   1752    }
   1753    break;
   1754  }
   1755 
   1756  for (const auto& contentToDelete : unnecessaryContents) {
   1757    if (MOZ_UNLIKELY(!contentToDelete->IsInComposedDoc())) {
   1758      continue;
   1759    }
   1760    nsresult rv =
   1761        aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(contentToDelete));
   1762    if (NS_FAILED(rv)) {
   1763      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   1764      return rv;
   1765    }
   1766  }
   1767  return NS_OK;
   1768 }
   1769 
   1770 nsresult WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore(
   1771    HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint) {
   1772  MOZ_ASSERT(aPoint.IsInContentNode());
   1773 
   1774  const RefPtr<Element> colsetBlockElement =
   1775      HTMLEditUtils::GetInclusiveAncestorElement(
   1776          *aPoint.ContainerAs<nsIContent>(),
   1777          HTMLEditUtils::ClosestEditableBlockElement,
   1778          BlockInlineCheck::UseComputedDisplayStyle);
   1779  EditorDOMPoint atFirstInvisibleWhiteSpace;
   1780  AutoTArray<OwningNonNull<nsIContent>, 32> unnecessaryContents;
   1781  for (nsIContent* previousContent =
   1782           HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1783               aPoint,
   1784               {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock,
   1785                HTMLEditUtils::LeafNodeType::TreatCommentAsLeafNode},
   1786               BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement);
   1787       previousContent;
   1788       previousContent =
   1789           HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1790               EditorRawDOMPoint(previousContent),
   1791               {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock,
   1792                HTMLEditUtils::LeafNodeType::TreatCommentAsLeafNode},
   1793               BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement)) {
   1794    if (!HTMLEditUtils::IsSimplyEditableNode(*previousContent)) {
   1795      // XXX Assume non-editable nodes are visible.
   1796      break;
   1797    }
   1798    const RefPtr<Text> precedingTextNode = Text::FromNode(previousContent);
   1799    if (!precedingTextNode &&
   1800        HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousContent)) {
   1801      break;
   1802    }
   1803    if (!precedingTextNode || !precedingTextNode->TextDataLength()) {
   1804      // If it's an empty inline element like `<b></b>` or an empty `Text`,
   1805      // delete it.
   1806      nsIContent* emptyInlineContent =
   1807          HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
   1808              *previousContent, BlockInlineCheck::UseComputedDisplayStyle);
   1809      if (!emptyInlineContent) {
   1810        emptyInlineContent = previousContent;
   1811      }
   1812      unnecessaryContents.AppendElement(*emptyInlineContent);
   1813      continue;
   1814    }
   1815    const auto atLastChar =
   1816        EditorRawDOMPointInText::AtLastContentOf(*precedingTextNode);
   1817    if (!atLastChar.IsCharCollapsibleASCIISpace()) {
   1818      break;
   1819    }
   1820    // If the preceding Text is collapsed and invisible, we should delete it
   1821    // and keep deleting preceding invisible white-spaces.
   1822    if (!HTMLEditUtils::IsVisibleTextNode(*precedingTextNode)) {
   1823      nsIContent* emptyInlineContent =
   1824          HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(
   1825              *precedingTextNode, BlockInlineCheck::UseComputedDisplayStyle);
   1826      if (!emptyInlineContent) {
   1827        emptyInlineContent = precedingTextNode;
   1828      }
   1829      unnecessaryContents.AppendElement(*emptyInlineContent);
   1830      continue;
   1831    }
   1832    Result<EditorDOMPoint, nsresult> endOfTextOrResult =
   1833        WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces(
   1834            aHTMLEditor, EditorDOMPoint::AtEndOf(*precedingTextNode));
   1835    if (MOZ_UNLIKELY(endOfTextOrResult.isErr())) {
   1836      NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
   1837      return endOfTextOrResult.unwrapErr();
   1838    }
   1839    break;
   1840  }
   1841 
   1842  for (const auto& contentToDelete : Reversed(unnecessaryContents)) {
   1843    if (MOZ_UNLIKELY(!contentToDelete->IsInComposedDoc())) {
   1844      continue;
   1845    }
   1846    nsresult rv =
   1847        aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(contentToDelete));
   1848    if (NS_FAILED(rv)) {
   1849      NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   1850      return rv;
   1851    }
   1852  }
   1853  return NS_OK;
   1854 }
   1855 
   1856 Result<EditorDOMPoint, nsresult>
   1857 WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces(
   1858    HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint) {
   1859  if (EditorUtils::IsWhiteSpacePreformatted(
   1860          *aPoint.ContainerAs<nsIContent>())) {
   1861    return EditorDOMPoint();
   1862  }
   1863  if (aPoint.IsInTextNode() &&
   1864      // If there is a previous char and it's not a collapsible ASCII
   1865      // white-space, the point is not in the leading white-spaces.
   1866      (!aPoint.IsStartOfContainer() && !aPoint.IsPreviousCharASCIISpace()) &&
   1867      // If it does not points a collapsible ASCII white-space, the point is not
   1868      // in the trailing white-spaces.
   1869      (!aPoint.IsEndOfContainer() && !aPoint.IsCharCollapsibleASCIISpace())) {
   1870    return EditorDOMPoint();
   1871  }
   1872  const Element* const maybeNonEditableClosestBlockElement =
   1873      HTMLEditUtils::GetInclusiveAncestorElement(
   1874          *aPoint.ContainerAs<nsIContent>(), HTMLEditUtils::ClosestBlockElement,
   1875          BlockInlineCheck::UseComputedDisplayStyle);
   1876  if (MOZ_UNLIKELY(!maybeNonEditableClosestBlockElement)) {
   1877    return EditorDOMPoint();  // aPoint is not in a block.
   1878  }
   1879  const TextFragmentData textFragmentDataForLeadingWhiteSpaces(
   1880      {WSRunScanner::Option::OnlyEditableNodes},
   1881      aPoint.IsStartOfContainer() &&
   1882              (aPoint.GetContainer() == maybeNonEditableClosestBlockElement ||
   1883               aPoint.GetContainer()->IsEditingHost())
   1884          ? aPoint
   1885          : aPoint.PreviousPointOrParentPoint<EditorDOMPoint>(),
   1886      maybeNonEditableClosestBlockElement);
   1887  if (NS_WARN_IF(!textFragmentDataForLeadingWhiteSpaces.IsInitialized())) {
   1888    return Err(NS_ERROR_FAILURE);
   1889  }
   1890 
   1891  {
   1892    const EditorDOMRange& leadingWhiteSpaceRange =
   1893        textFragmentDataForLeadingWhiteSpaces
   1894            .InvisibleLeadingWhiteSpaceRangeRef();
   1895    if (leadingWhiteSpaceRange.IsPositioned() &&
   1896        !leadingWhiteSpaceRange.Collapsed()) {
   1897      EditorDOMPoint endOfLeadingWhiteSpaces(leadingWhiteSpaceRange.EndRef());
   1898      AutoTrackDOMPoint trackEndOfLeadingWhiteSpaces(
   1899          aHTMLEditor.RangeUpdaterRef(), &endOfLeadingWhiteSpaces);
   1900      Result<CaretPoint, nsresult> caretPointOrError =
   1901          aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
   1902              leadingWhiteSpaceRange.StartRef(),
   1903              leadingWhiteSpaceRange.EndRef(),
   1904              HTMLEditor::TreatEmptyTextNodes::
   1905                  KeepIfContainerOfRangeBoundaries);
   1906      if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   1907        NS_WARNING(
   1908            "HTMLEditor::DeleteTextAndTextNodesWithTransaction("
   1909            "TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries) failed");
   1910        return caretPointOrError.propagateErr();
   1911      }
   1912      caretPointOrError.unwrap().IgnoreCaretPointSuggestion();
   1913      // If the leading white-spaces were split into multiple text node, we need
   1914      // only the last `Text` node.
   1915      if (!leadingWhiteSpaceRange.InSameContainer() &&
   1916          leadingWhiteSpaceRange.StartRef().IsInTextNode() &&
   1917          leadingWhiteSpaceRange.StartRef()
   1918              .ContainerAs<Text>()
   1919              ->IsInComposedDoc() &&
   1920          leadingWhiteSpaceRange.EndRef().IsInTextNode() &&
   1921          leadingWhiteSpaceRange.EndRef()
   1922              .ContainerAs<Text>()
   1923              ->IsInComposedDoc() &&
   1924          !leadingWhiteSpaceRange.StartRef()
   1925               .ContainerAs<Text>()
   1926               ->TextDataLength()) {
   1927        nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(
   1928            *leadingWhiteSpaceRange.StartRef().ContainerAs<Text>()));
   1929        if (NS_FAILED(rv)) {
   1930          NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
   1931          return Err(rv);
   1932        }
   1933      }
   1934      trackEndOfLeadingWhiteSpaces.FlushAndStopTracking();
   1935      if (NS_WARN_IF(!endOfLeadingWhiteSpaces.IsSetAndValidInComposedDoc())) {
   1936        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1937      }
   1938      return endOfLeadingWhiteSpaces;
   1939    }
   1940  }
   1941 
   1942  const TextFragmentData textFragmentData =
   1943      textFragmentDataForLeadingWhiteSpaces.ScanStartRef() == aPoint
   1944          ? textFragmentDataForLeadingWhiteSpaces
   1945          : TextFragmentData({WSRunScanner::Option::OnlyEditableNodes}, aPoint,
   1946                             maybeNonEditableClosestBlockElement);
   1947  const EditorDOMRange& trailingWhiteSpaceRange =
   1948      textFragmentData.InvisibleTrailingWhiteSpaceRangeRef();
   1949  if (trailingWhiteSpaceRange.IsPositioned() &&
   1950      !trailingWhiteSpaceRange.Collapsed()) {
   1951    EditorDOMPoint startOfTrailingWhiteSpaces(
   1952        trailingWhiteSpaceRange.StartRef());
   1953    AutoTrackDOMPoint trackStartOfTrailingWhiteSpaces(
   1954        aHTMLEditor.RangeUpdaterRef(), &startOfTrailingWhiteSpaces);
   1955    Result<CaretPoint, nsresult> caretPointOrError =
   1956        aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
   1957            trailingWhiteSpaceRange.StartRef(),
   1958            trailingWhiteSpaceRange.EndRef(),
   1959            HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries);
   1960    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   1961      NS_WARNING(
   1962          "HTMLEditor::DeleteTextAndTextNodesWithTransaction("
   1963          "TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries) failed");
   1964      return caretPointOrError.propagateErr();
   1965    }
   1966    caretPointOrError.unwrap().IgnoreCaretPointSuggestion();
   1967    // If the leading white-spaces were split into multiple text node, we need
   1968    // only the last `Text` node.
   1969    if (!trailingWhiteSpaceRange.InSameContainer() &&
   1970        trailingWhiteSpaceRange.StartRef().IsInTextNode() &&
   1971        trailingWhiteSpaceRange.StartRef()
   1972            .ContainerAs<Text>()
   1973            ->IsInComposedDoc() &&
   1974        trailingWhiteSpaceRange.EndRef().IsInTextNode() &&
   1975        trailingWhiteSpaceRange.EndRef()
   1976            .ContainerAs<Text>()
   1977            ->IsInComposedDoc() &&
   1978        !trailingWhiteSpaceRange.EndRef()
   1979             .ContainerAs<Text>()
   1980             ->TextDataLength()) {
   1981      nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(
   1982          MOZ_KnownLive(*trailingWhiteSpaceRange.EndRef().ContainerAs<Text>()));
   1983      if (NS_FAILED(rv)) {
   1984        NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
   1985        return Err(rv);
   1986      }
   1987    }
   1988    trackStartOfTrailingWhiteSpaces.FlushAndStopTracking();
   1989    if (NS_WARN_IF(!startOfTrailingWhiteSpaces.IsSetAndValidInComposedDoc())) {
   1990      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   1991    }
   1992    return startOfTrailingWhiteSpaces;
   1993  }
   1994 
   1995  const auto atCollapsibleASCIISpace =
   1996      [&]() MOZ_NEVER_INLINE_DEBUG -> EditorDOMPointInText {
   1997    const auto point =
   1998        textFragmentData.GetInclusiveNextCharPoint<EditorDOMPointInText>(
   1999            textFragmentData.ScanStartRef(), IgnoreNonEditableNodes::Yes);
   2000    if (point.IsSet() &&
   2001        // XXX Perhaps, we should ignore empty `Text` nodes and keep scanning.
   2002        !point.IsEndOfContainer() && point.IsCharCollapsibleASCIISpace()) {
   2003      return point;
   2004    }
   2005    const auto prevPoint =
   2006        textFragmentData.GetPreviousCharPoint<EditorDOMPointInText>(
   2007            textFragmentData.ScanStartRef(), IgnoreNonEditableNodes::Yes);
   2008    return prevPoint.IsSet() &&
   2009                   // XXX Perhaps, we should ignore empty `Text` nodes and keep
   2010                   // scanning.
   2011                   !prevPoint.IsEndOfContainer() &&
   2012                   prevPoint.IsCharCollapsibleASCIISpace()
   2013               ? prevPoint
   2014               : EditorDOMPointInText();
   2015  }();
   2016  if (!atCollapsibleASCIISpace.IsSet()) {
   2017    return EditorDOMPoint();
   2018  }
   2019  const auto firstCollapsibleASCIISpacePoint =
   2020      textFragmentData
   2021          .GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointInText>(
   2022              atCollapsibleASCIISpace, nsIEditor::eNone,
   2023              IgnoreNonEditableNodes::No);
   2024  const auto endOfCollapsibleASCIISpacePoint =
   2025      textFragmentData
   2026          .GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>(
   2027              atCollapsibleASCIISpace, nsIEditor::eNone,
   2028              IgnoreNonEditableNodes::No);
   2029  if (firstCollapsibleASCIISpacePoint.NextPoint() ==
   2030      endOfCollapsibleASCIISpacePoint) {
   2031    // Only one white-space, so that nothing to do.
   2032    return EditorDOMPoint();
   2033  }
   2034  // Okay, there are some collapsed white-spaces. We should delete them with
   2035  // keeping first one.
   2036  Result<CaretPoint, nsresult> deleteTextResultOrError =
   2037      aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
   2038          firstCollapsibleASCIISpacePoint.NextPoint(),
   2039          endOfCollapsibleASCIISpacePoint,
   2040          HTMLEditor::TreatEmptyTextNodes::Remove);
   2041  if (MOZ_UNLIKELY(deleteTextResultOrError.isErr())) {
   2042    NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
   2043    return deleteTextResultOrError.propagateErr();
   2044  }
   2045  return deleteTextResultOrError.unwrap().UnwrapCaretPoint();
   2046 }
   2047 
   2048 // static
   2049 Result<InsertTextResult, nsresult>
   2050 WhiteSpaceVisibilityKeeper::InsertTextOrInsertOrUpdateCompositionString(
   2051    HTMLEditor& aHTMLEditor, const nsAString& aStringToInsert,
   2052    const EditorDOMRange& aRangeToBeReplaced, InsertTextTo aInsertTextTo,
   2053    InsertTextFor aPurpose) {
   2054  MOZ_ASSERT(aRangeToBeReplaced.StartRef().IsInContentNode());
   2055  MOZ_ASSERT_IF(!EditorBase::InsertingTextForExtantComposition(aPurpose),
   2056                aRangeToBeReplaced.Collapsed());
   2057  if (aStringToInsert.IsEmpty()) {
   2058    MOZ_ASSERT(aRangeToBeReplaced.Collapsed());
   2059    return InsertTextResult();
   2060  }
   2061 
   2062  if (NS_WARN_IF(!aRangeToBeReplaced.StartRef().IsInContentNode())) {
   2063    return Err(NS_ERROR_FAILURE);  // Cannot insert text
   2064  }
   2065 
   2066  EditorDOMPoint pointToInsert = aHTMLEditor.ComputePointToInsertText(
   2067      aRangeToBeReplaced.StartRef(), aInsertTextTo);
   2068  MOZ_ASSERT(pointToInsert.IsInContentNode());
   2069  const bool isWhiteSpaceCollapsible = !EditorUtils::IsWhiteSpacePreformatted(
   2070      *aRangeToBeReplaced.StartRef().ContainerAs<nsIContent>());
   2071 
   2072  // First, delete invisible leading white-spaces and trailing white-spaces if
   2073  // they are there around the replacing range boundaries.  However, don't do
   2074  // that if we're updating existing composition string to avoid the composition
   2075  // transaction is broken by the text change around composition string.
   2076  if (!EditorBase::InsertingTextForExtantComposition(aPurpose) &&
   2077      isWhiteSpaceCollapsible && pointToInsert.IsInContentNode()) {
   2078    AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
   2079                                         &pointToInsert);
   2080    Result<EditorDOMPoint, nsresult>
   2081        deletePointOfInvisibleWhiteSpacesAtStartOrError =
   2082            WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces(
   2083                aHTMLEditor, pointToInsert);
   2084    if (MOZ_UNLIKELY(deletePointOfInvisibleWhiteSpacesAtStartOrError.isErr())) {
   2085      NS_WARNING(
   2086          "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces() failed");
   2087      return deletePointOfInvisibleWhiteSpacesAtStartOrError.propagateErr();
   2088    }
   2089    trackPointToInsert.FlushAndStopTracking();
   2090    const EditorDOMPoint deletePointOfInvisibleWhiteSpacesAtStart =
   2091        deletePointOfInvisibleWhiteSpacesAtStartOrError.unwrap();
   2092    if (NS_WARN_IF(deletePointOfInvisibleWhiteSpacesAtStart.IsSet() &&
   2093                   !pointToInsert.IsSetAndValidInComposedDoc())) {
   2094      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2095    }
   2096    // If we're starting composition, we won't normalizing surrounding
   2097    // white-spaces until end of the composition.  Additionally, at that time,
   2098    // we need to assume all white-spaces of surrounding white-spaces are
   2099    // visible because canceling composition may cause previous white-space
   2100    // invisible temporarily. Therefore, we should normalize surrounding
   2101    // white-spaces to delete invisible white-spaces contained in the sequence.
   2102    // E.g., `NBSP SP SP NBSP`, in this case, one of the SP is invisible.
   2103    if (EditorBase::InsertingTextForStartingComposition(aPurpose) &&
   2104        pointToInsert.IsInTextNode()) {
   2105      const auto whiteSpaceOffset = [&]() -> Maybe<uint32_t> {
   2106        if (!pointToInsert.IsEndOfContainer() &&
   2107            pointToInsert.IsCharCollapsibleASCIISpaceOrNBSP()) {
   2108          return Some(pointToInsert.Offset());
   2109        }
   2110        if (!pointToInsert.IsStartOfContainer() &&
   2111            pointToInsert.IsPreviousCharCollapsibleASCIISpaceOrNBSP()) {
   2112          return Some(pointToInsert.Offset() - 1u);
   2113        }
   2114        return Nothing();
   2115      }();
   2116      if (whiteSpaceOffset.isSome()) {
   2117        Maybe<AutoTrackDOMPoint> trackPointToInsert;
   2118        if (pointToInsert.Offset() != *whiteSpaceOffset) {
   2119          trackPointToInsert.emplace(aHTMLEditor.RangeUpdaterRef(),
   2120                                     &pointToInsert);
   2121        }
   2122        Result<EditorDOMPoint, nsresult> pointToInsertOrError =
   2123            WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt(
   2124                aHTMLEditor,
   2125                EditorDOMPointInText(pointToInsert.ContainerAs<Text>(),
   2126                                     *whiteSpaceOffset));
   2127        if (MOZ_UNLIKELY(pointToInsertOrError.isErr())) {
   2128          NS_WARNING(
   2129              "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt() failed");
   2130          return pointToInsertOrError.propagateErr();
   2131        }
   2132        if (trackPointToInsert.isSome()) {
   2133          trackPointToInsert.reset();
   2134        } else {
   2135          pointToInsert = pointToInsertOrError.unwrap();
   2136        }
   2137        if (NS_WARN_IF(!pointToInsert.IsInContentNodeAndValidInComposedDoc())) {
   2138          return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2139        }
   2140      }
   2141    }
   2142  }
   2143 
   2144  if (NS_WARN_IF(!pointToInsert.IsInContentNode())) {
   2145    return Err(NS_ERROR_FAILURE);
   2146  }
   2147 
   2148  const HTMLEditor::NormalizedStringToInsertText insertTextData =
   2149      [&]() MOZ_NEVER_INLINE_DEBUG {
   2150        if (!isWhiteSpaceCollapsible) {
   2151          return HTMLEditor::NormalizedStringToInsertText(aStringToInsert,
   2152                                                          pointToInsert);
   2153        }
   2154        if (pointToInsert.IsInTextNode() &&
   2155            !EditorBase::InsertingTextForComposition(aPurpose)) {
   2156          // If normalizing the surrounding white-spaces in the `Text`, we
   2157          // should minimize the replacing range to avoid to unnecessary
   2158          // replacement.
   2159          return aHTMLEditor
   2160              .NormalizeWhiteSpacesToInsertText(
   2161                  pointToInsert, aStringToInsert,
   2162                  HTMLEditor::NormalizeSurroundingWhiteSpaces::Yes)
   2163              .GetMinimizedData(*pointToInsert.ContainerAs<Text>());
   2164        }
   2165        return aHTMLEditor.NormalizeWhiteSpacesToInsertText(
   2166            pointToInsert, aStringToInsert,
   2167            // If we're handling composition string, we should not replace
   2168            // surrounding white-spaces to avoid to make
   2169            // CompositionTransaction confused.
   2170            EditorBase::InsertingTextForComposition(aPurpose)
   2171                ? HTMLEditor::NormalizeSurroundingWhiteSpaces::No
   2172                : HTMLEditor::NormalizeSurroundingWhiteSpaces::Yes);
   2173      }();
   2174 
   2175  MOZ_ASSERT_IF(insertTextData.ReplaceLength(), pointToInsert.IsInTextNode());
   2176  Result<InsertTextResult, nsresult> insertOrReplaceTextResultOrError =
   2177      aHTMLEditor.InsertOrReplaceTextWithTransaction(pointToInsert,
   2178                                                     insertTextData);
   2179  if (MOZ_UNLIKELY(insertOrReplaceTextResultOrError.isErr())) {
   2180    NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
   2181    return insertOrReplaceTextResultOrError;
   2182  }
   2183  // If the composition is committed, we should normalize surrounding
   2184  // white-spaces of the commit string.
   2185  if (!EditorBase::InsertingTextForCommittingComposition(aPurpose)) {
   2186    return insertOrReplaceTextResultOrError;
   2187  }
   2188  InsertTextResult insertOrReplaceTextResult =
   2189      insertOrReplaceTextResultOrError.unwrap();
   2190  const EditorDOMPointInText endOfCommitString =
   2191      insertOrReplaceTextResult.EndOfInsertedTextRef().GetAsInText();
   2192  if (!endOfCommitString.IsSet() || endOfCommitString.IsContainerEmpty()) {
   2193    return std::move(insertOrReplaceTextResult);
   2194  }
   2195  if (NS_WARN_IF(endOfCommitString.Offset() <
   2196                 insertTextData.mNormalizedString.Length())) {
   2197    insertOrReplaceTextResult.IgnoreCaretPointSuggestion();
   2198    return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2199  }
   2200  const EditorDOMPointInText startOfCommitString(
   2201      endOfCommitString.ContainerAs<Text>(),
   2202      endOfCommitString.Offset() - insertTextData.mNormalizedString.Length());
   2203  MOZ_ASSERT(insertOrReplaceTextResult.EndOfInsertedTextRef() ==
   2204             insertOrReplaceTextResult.CaretPointRef());
   2205  EditorDOMPoint pointToPutCaret = insertOrReplaceTextResult.UnwrapCaretPoint();
   2206  // First, normalize the trailing white-spaces if there is.  Note that its
   2207  // sequence may start from before the commit string.  In such case, the
   2208  // another call of NormalizeWhiteSpacesAt() won't update the DOM.
   2209  if (endOfCommitString.IsMiddleOfContainer()) {
   2210    nsresult rv = WhiteSpaceVisibilityKeeper::
   2211        NormalizeVisibleWhiteSpacesWithoutDeletingInvisibleWhiteSpaces(
   2212            aHTMLEditor, endOfCommitString.PreviousPoint());
   2213    if (NS_FAILED(rv)) {
   2214      NS_WARNING(
   2215          "WhiteSpaceVisibilityKeeper::"
   2216          "NormalizeVisibleWhiteSpacesWithoutDeletingInvisibleWhiteSpaces() "
   2217          "failed");
   2218      return Err(rv);
   2219    }
   2220    if (NS_WARN_IF(!pointToPutCaret.IsSetAndValidInComposedDoc())) {
   2221      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2222    }
   2223  }
   2224  // Finally, normalize the leading white-spaces if there is and not a part of
   2225  // the trailing white-spaces.
   2226  if (!startOfCommitString.IsStartOfContainer()) {
   2227    nsresult rv = WhiteSpaceVisibilityKeeper::
   2228        NormalizeVisibleWhiteSpacesWithoutDeletingInvisibleWhiteSpaces(
   2229            aHTMLEditor, startOfCommitString.PreviousPoint());
   2230    if (NS_FAILED(rv)) {
   2231      NS_WARNING(
   2232          "WhiteSpaceVisibilityKeeper::"
   2233          "NormalizeVisibleWhiteSpacesWithoutDeletingInvisibleWhiteSpaces() "
   2234          "failed");
   2235      return Err(rv);
   2236    }
   2237    if (NS_WARN_IF(!pointToPutCaret.IsSetAndValidInComposedDoc())) {
   2238      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2239    }
   2240  }
   2241  EditorDOMPoint endOfCommitStringAfterNormalized = pointToPutCaret;
   2242  return InsertTextResult(std::move(endOfCommitStringAfterNormalized),
   2243                          CaretPoint(std::move(pointToPutCaret)));
   2244 }
   2245 
   2246 // static
   2247 nsresult WhiteSpaceVisibilityKeeper::
   2248    NormalizeVisibleWhiteSpacesWithoutDeletingInvisibleWhiteSpaces(
   2249        HTMLEditor& aHTMLEditor, const EditorDOMPointInText& aPoint) {
   2250  MOZ_ASSERT(aPoint.IsSet());
   2251  MOZ_ASSERT(!aPoint.IsEndOfContainer());
   2252 
   2253  if (EditorUtils::IsWhiteSpacePreformatted(*aPoint.ContainerAs<Text>())) {
   2254    return NS_OK;
   2255  }
   2256  Text& textNode = *aPoint.ContainerAs<Text>();
   2257  const bool isNewLinePreformatted =
   2258      EditorUtils::IsNewLinePreformatted(textNode);
   2259  const auto IsCollapsibleChar = [&](char16_t aChar) {
   2260    return aChar == HTMLEditUtils::kNewLine ? !isNewLinePreformatted
   2261                                            : nsCRT::IsAsciiSpace(aChar);
   2262  };
   2263  const auto IsCollapsibleCharOrNBSP = [&](char16_t aChar) {
   2264    return aChar == HTMLEditUtils::kNBSP || IsCollapsibleChar(aChar);
   2265  };
   2266  const auto whiteSpaceOffset = [&]() -> Maybe<uint32_t> {
   2267    if (IsCollapsibleCharOrNBSP(aPoint.Char())) {
   2268      return Some(aPoint.Offset());
   2269    }
   2270    if (!aPoint.IsAtLastContent() &&
   2271        IsCollapsibleCharOrNBSP(aPoint.NextChar())) {
   2272      return Some(aPoint.Offset() + 1u);
   2273    }
   2274    return Nothing();
   2275  }();
   2276  if (whiteSpaceOffset.isNothing()) {
   2277    return NS_OK;
   2278  }
   2279  CharacterDataBuffer::WhitespaceOptions whitespaceOptions{
   2280      CharacterDataBuffer::WhitespaceOption::FormFeedIsSignificant,
   2281      CharacterDataBuffer::WhitespaceOption::TreatNBSPAsCollapsible};
   2282  if (isNewLinePreformatted) {
   2283    whitespaceOptions +=
   2284        CharacterDataBuffer::WhitespaceOption::NewLineIsSignificant;
   2285  }
   2286  const uint32_t firstOffset = [&]() {
   2287    if (!*whiteSpaceOffset) {
   2288      return 0u;
   2289    }
   2290    const uint32_t offset = textNode.DataBuffer().RFindNonWhitespaceChar(
   2291        whitespaceOptions, *whiteSpaceOffset - 1);
   2292    return offset == CharacterDataBuffer::kNotFound ? 0u : offset + 1u;
   2293  }();
   2294  const uint32_t endOffset = [&]() {
   2295    const uint32_t offset = textNode.DataBuffer().FindNonWhitespaceChar(
   2296        whitespaceOptions, *whiteSpaceOffset + 1);
   2297    return offset == CharacterDataBuffer::kNotFound ? textNode.TextDataLength()
   2298                                                    : offset;
   2299  }();
   2300  MOZ_DIAGNOSTIC_ASSERT(firstOffset <= endOffset);
   2301  nsAutoString normalizedString;
   2302  const char16_t precedingChar =
   2303      !firstOffset ? static_cast<char16_t>(0)
   2304                   : textNode.DataBuffer().CharAt(firstOffset - 1u);
   2305  const char16_t followingChar = endOffset == textNode.TextDataLength()
   2306                                     ? static_cast<char16_t>(0)
   2307                                     : textNode.DataBuffer().CharAt(endOffset);
   2308  HTMLEditor::GenerateWhiteSpaceSequence(
   2309      normalizedString, endOffset - firstOffset,
   2310      !firstOffset ? HTMLEditor::CharPointData::InSameTextNode(
   2311                         HTMLEditor::CharPointType::TextEnd)
   2312                   : HTMLEditor::CharPointData::InSameTextNode(
   2313                         precedingChar == HTMLEditUtils::kNewLine
   2314                             ? HTMLEditor::CharPointType::PreformattedLineBreak
   2315                             : HTMLEditor::CharPointType::VisibleChar),
   2316      endOffset == textNode.TextDataLength()
   2317          ? HTMLEditor::CharPointData::InSameTextNode(
   2318                HTMLEditor::CharPointType::TextEnd)
   2319          : HTMLEditor::CharPointData::InSameTextNode(
   2320                followingChar == HTMLEditUtils::kNewLine
   2321                    ? HTMLEditor::CharPointType::PreformattedLineBreak
   2322                    : HTMLEditor::CharPointType::VisibleChar));
   2323  MOZ_ASSERT(normalizedString.Length() == endOffset - firstOffset);
   2324  const OwningNonNull<Text> text(textNode);
   2325  Result<InsertTextResult, nsresult> normalizeWhiteSpaceSequenceResultOrError =
   2326      aHTMLEditor.ReplaceTextWithTransaction(
   2327          text, firstOffset, endOffset - firstOffset, normalizedString);
   2328  if (MOZ_UNLIKELY(normalizeWhiteSpaceSequenceResultOrError.isErr())) {
   2329    NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
   2330    return normalizeWhiteSpaceSequenceResultOrError.unwrapErr();
   2331  }
   2332  normalizeWhiteSpaceSequenceResultOrError.unwrap()
   2333      .IgnoreCaretPointSuggestion();
   2334  return NS_OK;
   2335 }
   2336 
   2337 // static
   2338 Result<CaretPoint, nsresult>
   2339 WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
   2340    HTMLEditor& aHTMLEditor, nsIContent& aContentToDelete,
   2341    const EditorDOMPoint& aCaretPoint, const Element& aEditingHost) {
   2342  EditorDOMPoint atContent(&aContentToDelete);
   2343  if (!atContent.IsSet()) {
   2344    NS_WARNING("Deleting content node was an orphan node");
   2345    return Err(NS_ERROR_FAILURE);
   2346  }
   2347  if (!HTMLEditUtils::IsRemovableNode(aContentToDelete)) {
   2348    NS_WARNING("Deleting content node wasn't removable");
   2349    return Err(NS_ERROR_FAILURE);
   2350  }
   2351  EditorDOMPoint pointToPutCaret(aCaretPoint);
   2352  Maybe<AutoTrackDOMPoint> trackPointToPutCaret;
   2353  if (aCaretPoint.IsSet()) {
   2354    trackPointToPutCaret.emplace(aHTMLEditor.RangeUpdaterRef(),
   2355                                 &pointToPutCaret);
   2356  }
   2357  // If we're removing a block, it may be surrounded by invisible
   2358  // white-spaces.  We should remove them to avoid to make them accidentally
   2359  // visible.
   2360  if (HTMLEditUtils::IsBlockElement(
   2361          aContentToDelete, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
   2362    AutoTrackDOMPoint trackAtContent(aHTMLEditor.RangeUpdaterRef(), &atContent);
   2363    {
   2364      nsresult rv =
   2365          WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore(
   2366              aHTMLEditor, EditorDOMPoint(aContentToDelete.AsElement()));
   2367      if (NS_FAILED(rv)) {
   2368        NS_WARNING(
   2369            "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore()"
   2370            " failed");
   2371        return Err(rv);
   2372      }
   2373      if (NS_WARN_IF(!aContentToDelete.IsInComposedDoc())) {
   2374        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2375      }
   2376      rv = WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter(
   2377          aHTMLEditor, EditorDOMPoint::After(*aContentToDelete.AsElement()));
   2378      if (NS_FAILED(rv)) {
   2379        NS_WARNING(
   2380            "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter() "
   2381            "failed");
   2382        return Err(rv);
   2383      }
   2384      if (NS_WARN_IF(!aContentToDelete.IsInComposedDoc())) {
   2385        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2386      }
   2387      if (trackPointToPutCaret.isSome()) {
   2388        trackPointToPutCaret->Flush(StopTracking::No);
   2389      }
   2390    }
   2391    if (pointToPutCaret.IsInContentNode()) {
   2392      // Additionally, we may put caret into the preceding block (this is the
   2393      // case when caret was in an empty block and type `Backspace`, or when
   2394      // caret is at end of the preceding block and type `Delete`).  In such
   2395      // case, we need to normalize the white-space of the preceding `Text` of
   2396      // the deleting empty block for the compatibility with the other
   2397      // browsers.
   2398      if (pointToPutCaret.IsBefore(EditorRawDOMPoint(&aContentToDelete))) {
   2399        WSScanResult nextThingOfCaretPoint =
   2400            WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   2401                {}, pointToPutCaret);
   2402        Maybe<EditorLineBreak> lineBreak;
   2403        if (nextThingOfCaretPoint.ReachedLineBreak()) {
   2404          lineBreak.emplace(
   2405              nextThingOfCaretPoint.CreateEditorLineBreak<EditorLineBreak>());
   2406          nextThingOfCaretPoint =
   2407              WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   2408                  {}, lineBreak->After<EditorRawDOMPoint>());
   2409        }
   2410        if (nextThingOfCaretPoint.ReachedBlockBoundary()) {
   2411          const EditorDOMPoint atBlockBoundary =
   2412              nextThingOfCaretPoint.ReachedCurrentBlockBoundary()
   2413                  ? EditorDOMPoint::AtEndOf(*nextThingOfCaretPoint.ElementPtr())
   2414                  : EditorDOMPoint(nextThingOfCaretPoint.ElementPtr());
   2415          Result<EditorDOMPoint, nsresult> afterLastVisibleThingOrError =
   2416              WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore(
   2417                  aHTMLEditor, atBlockBoundary, {});
   2418          if (MOZ_UNLIKELY(afterLastVisibleThingOrError.isErr())) {
   2419            NS_WARNING(
   2420                "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore() "
   2421                "failed");
   2422            return afterLastVisibleThingOrError.propagateErr();
   2423          }
   2424          if (NS_WARN_IF(!aContentToDelete.IsInComposedDoc())) {
   2425            return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2426          }
   2427          // If the previous content ends with an invisible line break, let's
   2428          // delete it.
   2429          if (lineBreak.isSome() && lineBreak->IsInComposedDoc()) {
   2430            const WSScanResult prevThing =
   2431                WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   2432                    {}, lineBreak->To<EditorRawDOMPoint>(), &aEditingHost);
   2433            if (!prevThing.ReachedLineBoundary()) {
   2434              Result<EditorDOMPoint, nsresult> pointOrError =
   2435                  aHTMLEditor.DeleteLineBreakWithTransaction(
   2436                      lineBreak.ref(), nsIEditor::eStrip, aEditingHost);
   2437              if (MOZ_UNLIKELY(pointOrError.isErr())) {
   2438                NS_WARNING(
   2439                    "HTMLEditor::DeleteLineBreakWithTransaction() failed");
   2440                return pointOrError.propagateErr();
   2441              }
   2442              trackPointToPutCaret->Flush(StopTracking::No);
   2443            }
   2444          }
   2445        }
   2446      }
   2447      // Similarly, we may put caret into the following block (this is the
   2448      // case when caret was in an empty block and type `Delete`, or when
   2449      // caret is at start of the following block and type `Backspace`).  In
   2450      // such case, we need to normalize the white-space of the following
   2451      // `Text` of the deleting empty block for the compatibility with the
   2452      // other browsers.
   2453      else if (EditorRawDOMPoint::After(aContentToDelete)
   2454                   .EqualsOrIsBefore(pointToPutCaret)) {
   2455        const WSScanResult previousThingOfCaretPoint =
   2456            WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   2457                {}, pointToPutCaret);
   2458        if (previousThingOfCaretPoint.ReachedBlockBoundary()) {
   2459          const EditorDOMPoint atBlockBoundary =
   2460              previousThingOfCaretPoint.ReachedCurrentBlockBoundary()
   2461                  ? EditorDOMPoint(previousThingOfCaretPoint.ElementPtr(), 0u)
   2462                  : EditorDOMPoint(previousThingOfCaretPoint.ElementPtr());
   2463          Result<EditorDOMPoint, nsresult> atFirstVisibleThingOrError =
   2464              WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter(
   2465                  aHTMLEditor, atBlockBoundary, {});
   2466          if (MOZ_UNLIKELY(atFirstVisibleThingOrError.isErr())) {
   2467            NS_WARNING(
   2468                "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() "
   2469                "failed");
   2470            return atFirstVisibleThingOrError.propagateErr();
   2471          }
   2472          if (NS_WARN_IF(!aContentToDelete.IsInComposedDoc())) {
   2473            return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2474          }
   2475        }
   2476      }
   2477    }
   2478    trackAtContent.Flush(StopTracking::Yes);
   2479    if (NS_WARN_IF(!atContent.IsInContentNodeAndValidInComposedDoc())) {
   2480      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2481    }
   2482  }
   2483  // If we're deleting inline content which is not followed by visible
   2484  // content, i.e., the preceding text will become the last Text node, we
   2485  // should normalize the preceding white-spaces for compatibility with the
   2486  // other browsers.
   2487  else {
   2488    const WSScanResult nextThing =
   2489        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   2490            {}, EditorRawDOMPoint::After(aContentToDelete));
   2491    if (nextThing.ReachedLineBoundary()) {
   2492      AutoTrackDOMPoint trackAtContent(aHTMLEditor.RangeUpdaterRef(),
   2493                                       &atContent);
   2494      Result<EditorDOMPoint, nsresult> afterLastVisibleThingOrError =
   2495          WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore(aHTMLEditor,
   2496                                                                 atContent, {});
   2497      if (MOZ_UNLIKELY(afterLastVisibleThingOrError.isErr())) {
   2498        NS_WARNING(
   2499            "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore() "
   2500            "failed");
   2501        return afterLastVisibleThingOrError.propagateErr();
   2502      }
   2503      trackAtContent.Flush(StopTracking::Yes);
   2504      if (NS_WARN_IF(!atContent.IsInContentNodeAndValidInComposedDoc())) {
   2505        return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2506      }
   2507    }
   2508  }
   2509 
   2510  // Finally, we should normalize the following white-spaces for compatibility
   2511  // with the other browsers.
   2512  {
   2513    AutoTrackDOMPoint trackAtContent(aHTMLEditor.RangeUpdaterRef(), &atContent);
   2514    Result<EditorDOMPoint, nsresult> atFirstVisibleThingOrError =
   2515        WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter(
   2516            aHTMLEditor, atContent.NextPoint(), {});
   2517    if (MOZ_UNLIKELY(atFirstVisibleThingOrError.isErr())) {
   2518      NS_WARNING(
   2519          "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore() failed");
   2520      return atFirstVisibleThingOrError.propagateErr();
   2521    }
   2522    trackAtContent.Flush(StopTracking::Yes);
   2523    if (NS_WARN_IF(!atContent.IsInContentNodeAndValidInComposedDoc())) {
   2524      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2525    }
   2526  }
   2527 
   2528  nsCOMPtr<nsIContent> previousEditableSibling =
   2529      HTMLEditUtils::GetPreviousSibling(
   2530          aContentToDelete, {WalkTreeOption::IgnoreNonEditableNode});
   2531  // Delete the node, and join like nodes if appropriate
   2532  nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aContentToDelete);
   2533  if (NS_FAILED(rv)) {
   2534    NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
   2535    return Err(rv);
   2536  }
   2537 
   2538  if (trackPointToPutCaret.isSome()) {
   2539    trackPointToPutCaret->Flush(StopTracking::Yes);
   2540    if (NS_WARN_IF(!pointToPutCaret.IsInContentNode())) {
   2541      return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
   2542    }
   2543  }
   2544 
   2545  // Are they both text nodes?  If so, join them!
   2546  // XXX This may cause odd behavior if there is non-editable nodes
   2547  //     around the atomic content.
   2548  if (!aCaretPoint.IsInTextNode() || !previousEditableSibling ||
   2549      !previousEditableSibling->IsText()) {
   2550    return CaretPoint(std::move(pointToPutCaret));
   2551  }
   2552 
   2553  nsIContent* nextEditableSibling = HTMLEditUtils::GetNextSibling(
   2554      *previousEditableSibling, {WalkTreeOption::IgnoreNonEditableNode});
   2555  if (aCaretPoint.GetContainer() != nextEditableSibling) {
   2556    return CaretPoint(std::move(pointToPutCaret));
   2557  }
   2558 
   2559  Result<JoinNodesResult, nsresult> joinTextNodesResultOrError =
   2560      aHTMLEditor.JoinTextNodesWithNormalizeWhiteSpaces(
   2561          MOZ_KnownLive(*previousEditableSibling->AsText()),
   2562          MOZ_KnownLive(*aCaretPoint.ContainerAs<Text>()));
   2563  if (MOZ_UNLIKELY(joinTextNodesResultOrError.isErr())) {
   2564    NS_WARNING("HTMLEditor::JoinTextNodesWithNormalizeWhiteSpaces() failed");
   2565    return joinTextNodesResultOrError.propagateErr();
   2566  }
   2567  return CaretPoint(
   2568      joinTextNodesResultOrError.unwrap().AtJoinedPoint<EditorDOMPoint>());
   2569 }
   2570 
   2571 // static
   2572 nsresult WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
   2573    HTMLEditor& aHTMLEditor, const EditorDOMRangeInTexts& aRangeToReplace,
   2574    const nsAString& aReplaceString) {
   2575  MOZ_ASSERT(aRangeToReplace.IsPositioned());
   2576  MOZ_ASSERT(aRangeToReplace.StartRef().IsSetAndValid());
   2577  MOZ_ASSERT(aRangeToReplace.EndRef().IsSetAndValid());
   2578  MOZ_ASSERT(aRangeToReplace.StartRef().IsBefore(aRangeToReplace.EndRef()));
   2579 
   2580  {
   2581    Result<InsertTextResult, nsresult> caretPointOrError =
   2582        aHTMLEditor.ReplaceTextWithTransaction(
   2583            MOZ_KnownLive(*aRangeToReplace.StartRef().ContainerAs<Text>()),
   2584            aRangeToReplace.StartRef().Offset(),
   2585            aRangeToReplace.InSameContainer()
   2586                ? aRangeToReplace.EndRef().Offset() -
   2587                      aRangeToReplace.StartRef().Offset()
   2588                : aRangeToReplace.StartRef().ContainerAs<Text>()->TextLength() -
   2589                      aRangeToReplace.StartRef().Offset(),
   2590            aReplaceString);
   2591    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   2592      NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
   2593      return caretPointOrError.unwrapErr();
   2594    }
   2595    // Ignore caret suggestion because there was
   2596    // AutoTransactionsConserveSelection.
   2597    caretPointOrError.unwrap().IgnoreCaretPointSuggestion();
   2598  }
   2599 
   2600  if (aRangeToReplace.InSameContainer()) {
   2601    return NS_OK;
   2602  }
   2603 
   2604  Result<CaretPoint, nsresult> caretPointOrError =
   2605      aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
   2606          EditorDOMPointInText::AtEndOf(
   2607              *aRangeToReplace.StartRef().ContainerAs<Text>()),
   2608          aRangeToReplace.EndRef(),
   2609          HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries);
   2610  if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   2611    NS_WARNING("HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
   2612    return caretPointOrError.unwrapErr();
   2613  }
   2614  // Ignore caret suggestion because there was
   2615  // AutoTransactionsConserveSelection.
   2616  caretPointOrError.unwrap().IgnoreCaretPointSuggestion();
   2617  return NS_OK;
   2618 }
   2619 
   2620 // static
   2621 Result<CaretPoint, nsresult>
   2622 WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
   2623    HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint) {
   2624  MOZ_ASSERT(aPoint.IsSet());
   2625  const TextFragmentData textFragmentData(
   2626      {WSRunScanner::Option::OnlyEditableNodes}, aPoint);
   2627  if (NS_WARN_IF(!textFragmentData.IsInitialized())) {
   2628    return Err(NS_ERROR_FAILURE);
   2629  }
   2630  const EditorDOMRange& leadingWhiteSpaceRange =
   2631      textFragmentData.InvisibleLeadingWhiteSpaceRangeRef();
   2632  // XXX Getting trailing white-space range now must be wrong because
   2633  //     mutation event listener may invalidate it.
   2634  const EditorDOMRange& trailingWhiteSpaceRange =
   2635      textFragmentData.InvisibleTrailingWhiteSpaceRangeRef();
   2636  EditorDOMPoint pointToPutCaret;
   2637  DebugOnly<bool> leadingWhiteSpacesDeleted = false;
   2638  if (leadingWhiteSpaceRange.IsPositioned() &&
   2639      !leadingWhiteSpaceRange.Collapsed()) {
   2640    Result<CaretPoint, nsresult> caretPointOrError =
   2641        aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
   2642            leadingWhiteSpaceRange.StartRef(), leadingWhiteSpaceRange.EndRef(),
   2643            HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries);
   2644    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   2645      NS_WARNING("HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
   2646      return caretPointOrError;
   2647    }
   2648    caretPointOrError.unwrap().MoveCaretPointTo(
   2649        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   2650    leadingWhiteSpacesDeleted = true;
   2651  }
   2652  if (trailingWhiteSpaceRange.IsPositioned() &&
   2653      !trailingWhiteSpaceRange.Collapsed() &&
   2654      leadingWhiteSpaceRange != trailingWhiteSpaceRange) {
   2655    NS_ASSERTION(!leadingWhiteSpacesDeleted,
   2656                 "We're trying to remove trailing white-spaces with maybe "
   2657                 "outdated range");
   2658    AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
   2659                                           &pointToPutCaret);
   2660    Result<CaretPoint, nsresult> caretPointOrError =
   2661        aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
   2662            trailingWhiteSpaceRange.StartRef(),
   2663            trailingWhiteSpaceRange.EndRef(),
   2664            HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries);
   2665    if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
   2666      NS_WARNING("HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
   2667      return caretPointOrError.propagateErr();
   2668    }
   2669    trackPointToPutCaret.FlushAndStopTracking();
   2670    caretPointOrError.unwrap().MoveCaretPointTo(
   2671        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
   2672  }
   2673  return CaretPoint(std::move(pointToPutCaret));
   2674 }
   2675 
   2676 }  // namespace mozilla