tor-browser

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

AutoClonedRangeArray.cpp (68197B)


      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 "AutoClonedRangeArray.h"
      7 
      8 #include "EditAction.h"
      9 #include "EditorDOMPoint.h"   // for EditorDOMPoint, EditorDOMRange, etc
     10 #include "EditorForwards.h"   // for CollectChildrenOptions
     11 #include "HTMLEditUtils.h"    // for HTMLEditUtils
     12 #include "HTMLEditHelpers.h"  // for SplitNodeResult
     13 #include "TextEditor.h"       // for TextEditor
     14 #include "WSRunScanner.h"     // for WSRunScanner
     15 
     16 #include "mozilla/CaretAssociationHint.h"     // for CaretAssociationHint
     17 #include "mozilla/IntegerRange.h"             // for IntegerRange
     18 #include "mozilla/OwningNonNull.h"            // for OwningNonNull
     19 #include "mozilla/PresShell.h"                // for PresShell
     20 #include "mozilla/dom/CharacterDataBuffer.h"  // for CharacterDataBuffer
     21 #include "mozilla/dom/Document.h"             // for dom::Document
     22 #include "mozilla/dom/HTMLBRElement.h"        // for dom HTMLBRElement
     23 #include "mozilla/dom/Selection.h"            // for dom::Selection
     24 #include "mozilla/dom/Text.h"                 // for dom::Text
     25 
     26 #include "gfxFontUtils.h"      // for gfxFontUtils
     27 #include "nsError.h"           // for NS_SUCCESS_* and NS_ERROR_*
     28 #include "nsFrameSelection.h"  // for nsFrameSelection
     29 #include "nsIContent.h"        // for nsIContent
     30 #include "nsINode.h"           // for nsINode
     31 #include "nsRange.h"           // for nsRange
     32 
     33 namespace mozilla {
     34 
     35 using namespace dom;
     36 
     37 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
     38 using ReplaceOrVoidElementOption = HTMLEditUtils::ReplaceOrVoidElementOption;
     39 
     40 /******************************************************************************
     41 * mozilla::AutoClonedRangeArray
     42 *****************************************************************************/
     43 
     44 template AutoClonedRangeArray::AutoClonedRangeArray(
     45    const EditorDOMRange& aRange);
     46 template AutoClonedRangeArray::AutoClonedRangeArray(
     47    const EditorRawDOMRange& aRange);
     48 template AutoClonedRangeArray::AutoClonedRangeArray(
     49    const EditorDOMPoint& aRange);
     50 template AutoClonedRangeArray::AutoClonedRangeArray(
     51    const EditorRawDOMPoint& aRange);
     52 
     53 AutoClonedRangeArray::AutoClonedRangeArray(const AutoClonedRangeArray& aOther)
     54    : mAnchorFocusRange(aOther.mAnchorFocusRange),
     55      mDirection(aOther.mDirection) {
     56  mRanges.SetCapacity(aOther.mRanges.Length());
     57  for (const OwningNonNull<nsRange>& range : aOther.mRanges) {
     58    RefPtr<nsRange> clonedRange = range->CloneRange();
     59    mRanges.AppendElement(std::move(clonedRange));
     60  }
     61  mAnchorFocusRange = aOther.mAnchorFocusRange;
     62 }
     63 
     64 template <typename PointType>
     65 AutoClonedRangeArray::AutoClonedRangeArray(
     66    const EditorDOMRangeBase<PointType>& aRange) {
     67  MOZ_ASSERT(aRange.IsPositionedAndValid());
     68  RefPtr<nsRange> range = aRange.CreateRange(IgnoreErrors());
     69  if (NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned())) {
     70    return;
     71  }
     72  mRanges.AppendElement(*range);
     73  mAnchorFocusRange = std::move(range);
     74 }
     75 
     76 template <typename PT, typename CT>
     77 AutoClonedRangeArray::AutoClonedRangeArray(
     78    const EditorDOMPointBase<PT, CT>& aPoint) {
     79  MOZ_ASSERT(aPoint.IsSetAndValid());
     80  RefPtr<nsRange> range = aPoint.CreateCollapsedRange(IgnoreErrors());
     81  if (NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned())) {
     82    return;
     83  }
     84  mRanges.AppendElement(*range);
     85  mAnchorFocusRange = std::move(range);
     86 }
     87 
     88 AutoClonedRangeArray::AutoClonedRangeArray(const nsRange& aRange) {
     89  MOZ_ASSERT(aRange.IsPositioned());
     90  mRanges.AppendElement(aRange.CloneRange());
     91  mAnchorFocusRange = mRanges[0];
     92 }
     93 
     94 // static
     95 bool AutoClonedRangeArray::IsEditableRange(const dom::AbstractRange& aRange,
     96                                           const Element& aEditingHost) {
     97  // TODO: Perhaps, we should check whether the start/end boundaries are
     98  //       first/last point of non-editable element.
     99  //       See https://github.com/w3c/editing/issues/283#issuecomment-788654850
    100  EditorRawDOMPoint atStart(aRange.StartRef());
    101  if (!atStart.IsInContentNode() || !HTMLEditUtils::IsSimplyEditableNode(
    102                                        *atStart.ContainerAs<nsIContent>())) {
    103    return false;
    104  }
    105 
    106  if (aRange.GetStartContainer() != aRange.GetEndContainer()) {
    107    EditorRawDOMPoint atEnd(aRange.EndRef());
    108    if (!atEnd.IsInContentNode() || !HTMLEditUtils::IsSimplyEditableNode(
    109                                        *atEnd.ContainerAs<nsIContent>())) {
    110      return false;
    111    }
    112 
    113    // Now, both start and end points are editable, but if they are in
    114    // different editing host, we cannot edit the range.
    115    if (atStart.ContainerAs<nsIContent>() != atEnd.ContainerAs<nsIContent>() &&
    116        atStart.ContainerAs<nsIContent>()->GetEditingHost() !=
    117            atEnd.ContainerAs<nsIContent>()->GetEditingHost()) {
    118      return false;
    119    }
    120  }
    121 
    122  // HTMLEditor does not support modifying outside `<body>` element for now.
    123  nsINode* commonAncestor = aRange.GetClosestCommonInclusiveAncestor();
    124  return commonAncestor && commonAncestor->IsContent() &&
    125         commonAncestor->IsInclusiveDescendantOf(&aEditingHost);
    126 }
    127 
    128 void AutoClonedRangeArray::EnsureOnlyEditableRanges(
    129    const Element& aEditingHost) {
    130  for (const size_t index : Reversed(IntegerRange(mRanges.Length()))) {
    131    const OwningNonNull<nsRange>& range = mRanges[index];
    132    if (!AutoClonedRangeArray::IsEditableRange(range, aEditingHost)) {
    133      mRanges.RemoveElementAt(index);
    134      continue;
    135    }
    136    // Special handling for `inert` attribute. If anchor node is inert, the
    137    // range should be treated as not editable.
    138    nsIContent* anchorContent =
    139        mDirection == eDirNext
    140            ? nsIContent::FromNode(range->GetStartContainer())
    141            : nsIContent::FromNode(range->GetEndContainer());
    142    if (anchorContent && HTMLEditUtils::ContentIsInert(*anchorContent)) {
    143      mRanges.RemoveElementAt(index);
    144      continue;
    145    }
    146    // Additionally, if focus node is inert, the range should be collapsed to
    147    // anchor node.
    148    nsIContent* focusContent =
    149        mDirection == eDirNext
    150            ? nsIContent::FromNode(range->GetEndContainer())
    151            : nsIContent::FromNode(range->GetStartContainer());
    152    if (focusContent && focusContent != anchorContent &&
    153        HTMLEditUtils::ContentIsInert(*focusContent)) {
    154      range->Collapse(mDirection == eDirNext);
    155    }
    156  }
    157  mAnchorFocusRange = mRanges.IsEmpty() ? nullptr : mRanges.LastElement().get();
    158 }
    159 
    160 bool AutoClonedRangeArray::AdjustRangesNotInReplacedNorVoidElements(
    161    RangeInReplacedOrVoidElement aRangeInReplacedOrVoidElement,
    162    const dom::Element& aEditingHost) {
    163  bool adjusted = false;
    164  for (const size_t index : Reversed(IntegerRange(mRanges.Length()))) {
    165    const OwningNonNull<nsRange>& range = mRanges[index];
    166    // If the range is in a replaced element or a void element, we should adjust
    167    // the range boundaries outside of the element.
    168    if (Element* const replacedOrVoidElementAtStart =
    169            HTMLEditUtils::GetInclusiveAncestorReplacedOrVoidElement(
    170                *range->StartRef().GetContainer()->AsContent(),
    171                ReplaceOrVoidElementOption::LookForReplacedOrVoidElement)) {
    172      adjusted = true;
    173      if (MOZ_UNLIKELY(!replacedOrVoidElementAtStart->IsInclusiveDescendantOf(
    174              &aEditingHost))) {
    175        mRanges.RemoveElementAt(index);
    176        continue;
    177      }
    178      nsIContent* const commonAncestorContent =
    179          nsIContent::FromNode(range->GetClosestCommonInclusiveAncestor());
    180      if (commonAncestorContent &&
    181          commonAncestorContent->IsInclusiveDescendantOf(
    182              replacedOrVoidElementAtStart)) {
    183        // If the range is completely in a replaced element or a void element,
    184        // let's treat that it's collapsed before the element or just delete the
    185        // range.
    186        if (aRangeInReplacedOrVoidElement ==
    187                RangeInReplacedOrVoidElement::Delete ||
    188            NS_WARN_IF(NS_FAILED(range->CollapseTo(RawRangeBoundary(
    189                replacedOrVoidElementAtStart->GetParentNode(),
    190                replacedOrVoidElementAtStart->GetPreviousSibling())))) ||
    191            MOZ_UNLIKELY(
    192                !AutoClonedRangeArray::IsEditableRange(range, aEditingHost))) {
    193          mRanges.RemoveElementAt(index);
    194          continue;
    195        }
    196        adjusted = true;
    197      } else {
    198        // If the range does not end in the replaced element or the void
    199        // element, let's treat that the range starts after the element.
    200        if (NS_WARN_IF(NS_FAILED(range->SetStartAndEnd(
    201                RawRangeBoundary(replacedOrVoidElementAtStart->GetParentNode(),
    202                                 replacedOrVoidElementAtStart),
    203                range->EndRef()))) ||
    204            MOZ_UNLIKELY(
    205                !AutoClonedRangeArray::IsEditableRange(range, aEditingHost))) {
    206          mRanges.RemoveElementAt(index);
    207          continue;
    208        }
    209      }
    210    }
    211    if (!range->Collapsed() &&
    212        range->GetStartContainer() != range->GetEndContainer()) {
    213      if (Element* const replacedOrVoidElementAtEnd =
    214              HTMLEditUtils::GetInclusiveAncestorReplacedOrVoidElement(
    215                  *range->EndRef().GetContainer()->AsContent(),
    216                  ReplaceOrVoidElementOption::LookForReplacedOrVoidElement)) {
    217        MOZ_ASSERT(
    218            replacedOrVoidElementAtEnd->IsInclusiveDescendantOf(&aEditingHost));
    219        adjusted = true;
    220        // If the range ends in a replaced element or a void element, let's
    221        // treat that the range ends before the element.
    222        if (NS_WARN_IF(NS_FAILED(range->SetStartAndEnd(
    223                range->StartRef(),
    224                RawRangeBoundary(
    225                    replacedOrVoidElementAtEnd->GetParentNode(),
    226                    replacedOrVoidElementAtEnd->GetPreviousSibling())))) ||
    227            MOZ_UNLIKELY(
    228                !AutoClonedRangeArray::IsEditableRange(range, aEditingHost))) {
    229          mRanges.RemoveElementAt(index);
    230          continue;
    231        }
    232      }
    233    }
    234  }
    235  return adjusted;
    236 }
    237 
    238 void AutoClonedRangeArray::EnsureRangesInTextNode(const Text& aTextNode) {
    239  auto GetOffsetInTextNode = [&aTextNode](const nsINode* aNode,
    240                                          uint32_t aOffset) -> uint32_t {
    241    MOZ_DIAGNOSTIC_ASSERT(aNode);
    242    if (aNode == &aTextNode) {
    243      return aOffset;
    244    }
    245    const nsIContent* anonymousDivElement = aTextNode.GetParent();
    246    MOZ_DIAGNOSTIC_ASSERT(anonymousDivElement);
    247    MOZ_DIAGNOSTIC_ASSERT(anonymousDivElement->IsHTMLElement(nsGkAtoms::div));
    248    MOZ_DIAGNOSTIC_ASSERT(anonymousDivElement->GetFirstChild() == &aTextNode);
    249    if (aNode == anonymousDivElement && aOffset == 0u) {
    250      return 0u;  // Point before the text node so that use start of the text.
    251    }
    252    MOZ_DIAGNOSTIC_ASSERT(aNode->IsInclusiveDescendantOf(anonymousDivElement));
    253    // Point after the text node so that use end of the text.
    254    return aTextNode.TextDataLength();
    255  };
    256  for (const OwningNonNull<nsRange>& range : mRanges) {
    257    if (MOZ_LIKELY(range->GetStartContainer() == &aTextNode &&
    258                   range->GetEndContainer() == &aTextNode)) {
    259      continue;
    260    }
    261    range->SetStartAndEnd(
    262        const_cast<Text*>(&aTextNode),
    263        GetOffsetInTextNode(range->GetStartContainer(), range->StartOffset()),
    264        const_cast<Text*>(&aTextNode),
    265        GetOffsetInTextNode(range->GetEndContainer(), range->EndOffset()));
    266  }
    267 
    268  if (MOZ_UNLIKELY(mRanges.Length() >= 2)) {
    269    // For avoiding to handle same things in same range, we should drop and
    270    // merge unnecessary ranges.  Note that the ranges never overlap
    271    // because selection ranges are not allowed it so that we need to check only
    272    // end offset vs start offset of next one.
    273    for (const size_t i : Reversed(IntegerRange(mRanges.Length() - 1u))) {
    274      MOZ_ASSERT(mRanges[i]->EndOffset() < mRanges[i + 1]->StartOffset());
    275      // XXX Should we delete collapsed range unless the index is 0?  Without
    276      //     Selection API, such situation cannot happen so that `TextEditor`
    277      //     may behave unexpectedly.
    278      if (MOZ_UNLIKELY(mRanges[i]->EndOffset() >=
    279                       mRanges[i + 1]->StartOffset())) {
    280        const uint32_t newEndOffset = mRanges[i + 1]->EndOffset();
    281        mRanges.RemoveElementAt(i + 1);
    282        if (MOZ_UNLIKELY(NS_WARN_IF(newEndOffset > mRanges[i]->EndOffset()))) {
    283          // So, this case shouldn't happen.
    284          mRanges[i]->SetStartAndEnd(
    285              const_cast<Text*>(&aTextNode), mRanges[i]->StartOffset(),
    286              const_cast<Text*>(&aTextNode), newEndOffset);
    287        }
    288      }
    289    }
    290  }
    291 }
    292 
    293 Result<bool, nsresult>
    294 AutoClonedRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent(
    295    const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
    296    IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent) {
    297  if (IsCollapsed()) {
    298    return false;
    299  }
    300 
    301  switch (aDirectionAndAmount) {
    302    case nsIEditor::eNext:
    303    case nsIEditor::eNextWord:
    304    case nsIEditor::ePrevious:
    305    case nsIEditor::ePreviousWord:
    306      break;
    307    default:
    308      return false;
    309  }
    310 
    311  bool changed = false;
    312  for (const OwningNonNull<nsRange>& range : mRanges) {
    313    MOZ_ASSERT(!range->IsInAnySelection(),
    314               "Changing range in selection may cause running script");
    315    Result<bool, nsresult> result =
    316        WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
    317            {WSRunScanner::Option::OnlyEditableNodes}, range);
    318    if (result.isErr()) {
    319      NS_WARNING(
    320          "WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() "
    321          "failed");
    322      return Err(result.inspectErr());
    323    }
    324    changed |= result.inspect();
    325  }
    326 
    327  if (mRanges.Length() == 1 && aIfSelectingOnlyOneAtomicContent ==
    328                                   IfSelectingOnlyOneAtomicContent::Collapse) {
    329    MOZ_ASSERT(mRanges[0].get() == mAnchorFocusRange.get());
    330    if (mAnchorFocusRange->GetStartContainer() ==
    331            mAnchorFocusRange->GetEndContainer() &&
    332        mAnchorFocusRange->GetChildAtStartOffset() &&
    333        mAnchorFocusRange->StartRef().GetNextSiblingOfChildAtOffset() ==
    334            mAnchorFocusRange->GetChildAtEndOffset()) {
    335      mAnchorFocusRange->Collapse(aDirectionAndAmount == nsIEditor::eNext ||
    336                                  aDirectionAndAmount == nsIEditor::eNextWord);
    337      changed = true;
    338    }
    339  }
    340 
    341  return changed;
    342 }
    343 
    344 // static
    345 void AutoClonedRangeArray::
    346    UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement(
    347        EditorDOMPoint& aStartPoint, EditorDOMPoint& aEndPoint,
    348        const Element& aEditingHost) {
    349  // FYI: This was moved from
    350  // https://searchfox.org/mozilla-central/rev/3419858c997f422e3e70020a46baae7f0ec6dacc/editor/libeditor/HTMLEditSubActionHandler.cpp#6743
    351 
    352  // MOOSE major hack:
    353  // The GetPointAtFirstContentOfLineOrParentBlockIfFirstContentOfBlock() and
    354  // GetPointAfterFollowingLineBreakOrAtFollowingBlock() don't really do the
    355  // right thing for collapsed ranges inside block elements that contain nothing
    356  // but a solo <br>.  It's easier/ to put a workaround here than to revamp
    357  // them.  :-(
    358  if (aStartPoint != aEndPoint) {
    359    return;
    360  }
    361 
    362  if (!aStartPoint.IsInContentNode()) {
    363    return;
    364  }
    365 
    366  // XXX Perhaps, this should be more careful.  This may not select only one
    367  //     node because this just check whether the block is empty or not,
    368  //     and may not select in non-editable block.  However, for inline
    369  //     editing host case, it's right to look for block element without
    370  //     editable state check.  Now, this method is used for preparation for
    371  //     other things.  So, cannot write test for this method behavior.
    372  //     So, perhaps, we should get rid of this method and each caller should
    373  //     handle its job better.
    374  Element* const maybeNonEditableBlockElement =
    375      HTMLEditUtils::GetInclusiveAncestorElement(
    376          *aStartPoint.ContainerAs<nsIContent>(),
    377          HTMLEditUtils::ClosestBlockElement,
    378          BlockInlineCheck::UseComputedDisplayStyle);
    379  if (!maybeNonEditableBlockElement) {
    380    return;
    381  }
    382 
    383  // Make sure we don't go higher than our root element in the content tree
    384  if (aEditingHost.IsInclusiveDescendantOf(maybeNonEditableBlockElement)) {
    385    return;
    386  }
    387 
    388  if (HTMLEditUtils::IsEmptyNode(
    389          *maybeNonEditableBlockElement,
    390          {EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
    391    aStartPoint.Set(maybeNonEditableBlockElement, 0u);
    392    aEndPoint.SetToEndOf(maybeNonEditableBlockElement);
    393  }
    394 }
    395 
    396 /**
    397 * Get the point before the line containing aPointInLine.
    398 *
    399 * @return              If the line starts after a `<br>` element, returns next
    400 *                      sibling of the `<br>` element.
    401 *                      If the line is first line of a block, returns point of
    402 *                      the block.
    403 * NOTE: The result may be point of editing host.  I.e., the container may be
    404 *       outside of editing host.
    405 */
    406 MOZ_NEVER_INLINE_DEBUG static EditorDOMPoint
    407 GetPointAtFirstContentOfLineOrParentHTMLBlockIfFirstContentOfBlock(
    408    const EditorDOMPoint& aPointInLine, EditSubAction aEditSubAction,
    409    BlockInlineCheck aBlockInlineCheck, const Element& aAncestorLimiter) {
    410  // FYI: This was moved from
    411  // https://searchfox.org/mozilla-central/rev/3419858c997f422e3e70020a46baae7f0ec6dacc/editor/libeditor/HTMLEditSubActionHandler.cpp#6447
    412 
    413  if (NS_WARN_IF(!aPointInLine.IsSet())) {
    414    return EditorDOMPoint();
    415  }
    416 
    417  EditorDOMPoint point(aPointInLine);
    418  // Start scanning from the container node if aPoint is in a text node.
    419  // XXX Perhaps, IsInDataNode() must be expected.
    420  if (point.IsInTextNode()) {
    421    if (!point.GetContainer()->GetParentNode()) {
    422      // Okay, can't promote any further
    423      // XXX Why don't we return start of the text node?
    424      return point;
    425    }
    426    // If there is a preformatted linefeed in the text node, let's return
    427    // the point after it.
    428    EditorDOMPoint atLastPreformattedNewLine =
    429        HTMLEditUtils::GetPreviousPreformattedNewLineInTextNode<EditorDOMPoint>(
    430            point);
    431    if (atLastPreformattedNewLine.IsSet()) {
    432      return atLastPreformattedNewLine.NextPoint();
    433    }
    434    point.Set(point.GetContainer());
    435  }
    436 
    437  // Look back through any further inline nodes that aren't across a <br>
    438  // from us, and that are enclosed in the same block.
    439  // I.e., looking for start of current hard line.
    440  constexpr HTMLEditUtils::WalkTreeOptions
    441      ignoreNonEditableNodeAndStopAtBlockBoundary{
    442          HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode,
    443          HTMLEditUtils::WalkTreeOption::StopAtBlockBoundary};
    444  for (nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent(
    445           point, ignoreNonEditableNodeAndStopAtBlockBoundary,
    446           aBlockInlineCheck, &aAncestorLimiter);
    447       previousEditableContent && previousEditableContent->GetParentNode() &&
    448       !HTMLEditUtils::IsVisibleBRElement(*previousEditableContent) &&
    449       !HTMLEditUtils::IsBlockElement(*previousEditableContent,
    450                                      aBlockInlineCheck);
    451       previousEditableContent = HTMLEditUtils::GetPreviousContent(
    452           point, ignoreNonEditableNodeAndStopAtBlockBoundary,
    453           aBlockInlineCheck, &aAncestorLimiter)) {
    454    EditorDOMPoint atLastPreformattedNewLine =
    455        HTMLEditUtils::GetPreviousPreformattedNewLineInTextNode<EditorDOMPoint>(
    456            EditorRawDOMPoint::AtEndOf(*previousEditableContent));
    457    if (atLastPreformattedNewLine.IsSet()) {
    458      return atLastPreformattedNewLine.NextPoint();
    459    }
    460    point.Set(previousEditableContent);
    461  }
    462 
    463  // Finding the real start for this point unless current line starts after
    464  // <br> element.  Look up the tree for as long as we are the first node in
    465  // the container (typically, start of nearest block ancestor), and as long
    466  // as we haven't hit the body node.
    467  for (nsIContent* nearContent = HTMLEditUtils::GetPreviousContent(
    468           point, ignoreNonEditableNodeAndStopAtBlockBoundary,
    469           aBlockInlineCheck, &aAncestorLimiter);
    470       !nearContent && !point.IsContainerHTMLElement(nsGkAtoms::body) &&
    471       point.GetContainerParent();
    472       nearContent = HTMLEditUtils::GetPreviousContent(
    473           point, ignoreNonEditableNodeAndStopAtBlockBoundary,
    474           aBlockInlineCheck, &aAncestorLimiter)) {
    475    // Don't keep looking up if we have found a blockquote element to act on
    476    // when we handle outdent.
    477    // XXX Sounds like this is hacky.  If possible, it should be check in
    478    //     outdent handler for consistency between edit sub-actions.
    479    //     We should check Chromium's behavior of outdent when Selection
    480    //     starts from `<blockquote>` and starts from first child of
    481    //     `<blockquote>`.
    482    if (aEditSubAction == EditSubAction::eOutdent &&
    483        point.IsContainerHTMLElement(nsGkAtoms::blockquote)) {
    484      break;
    485    }
    486 
    487    // Don't walk past the editable section. Note that we need to check
    488    // before walking up to a parent because we need to return the parent
    489    // object, so the parent itself might not be in the editable area, but
    490    // it's OK if we're not performing a block-level action.
    491    bool blockLevelAction =
    492        aEditSubAction == EditSubAction::eIndent ||
    493        aEditSubAction == EditSubAction::eOutdent ||
    494        aEditSubAction == EditSubAction::eSetOrClearAlignment ||
    495        aEditSubAction == EditSubAction::eCreateOrRemoveBlock ||
    496        aEditSubAction == EditSubAction::eFormatBlockForHTMLCommand;
    497    // XXX So, does this check whether the container is removable or not? It
    498    //     seems that here can be rewritten as obviously what here tries to
    499    //     check.
    500    if (!point.GetContainerParent()->IsInclusiveDescendantOf(
    501            &aAncestorLimiter) &&
    502        (blockLevelAction ||
    503         !point.GetContainer()->IsInclusiveDescendantOf(&aAncestorLimiter))) {
    504      break;
    505    }
    506 
    507    // If we're formatting a block, we should reformat first ancestor format
    508    // block.
    509    if (aEditSubAction == EditSubAction::eFormatBlockForHTMLCommand &&
    510        point.IsContainerElement() &&
    511        HTMLEditUtils::IsFormatElementForFormatBlockCommand(
    512            *point.ContainerAs<Element>())) {
    513      point.Set(point.GetContainer());
    514      break;
    515    }
    516 
    517    point.Set(point.GetContainer());
    518  }
    519  return point;
    520 }
    521 
    522 /**
    523 * Get the point after the following line break or the block which breaks the
    524 * line containing aPointInLine.
    525 *
    526 * @return              If the line ends with a visible `<br>` element, returns
    527 *                      the point after the `<br>` element.
    528 *                      If the line ends with a preformatted linefeed, returns
    529 *                      the point after the linefeed unless it's an invisible
    530 *                      line break immediately before a block boundary.
    531 *                      If the line ends with a block boundary, returns the
    532 *                      point of the block.
    533 */
    534 MOZ_NEVER_INLINE_DEBUG static EditorDOMPoint
    535 GetPointAfterFollowingLineBreakOrAtFollowingHTMLBlock(
    536    const EditorDOMPoint& aPointInLine, EditSubAction aEditSubAction,
    537    BlockInlineCheck aBlockInlineCheck, const Element& aAncestorLimiter) {
    538  // FYI: This was moved from
    539  // https://searchfox.org/mozilla-central/rev/3419858c997f422e3e70020a46baae7f0ec6dacc/editor/libeditor/HTMLEditSubActionHandler.cpp#6541
    540 
    541  if (NS_WARN_IF(!aPointInLine.IsSet())) {
    542    return EditorDOMPoint();
    543  }
    544 
    545  EditorDOMPoint point(aPointInLine);
    546  // Start scanning from the container node if aPoint is in a text node.
    547  // XXX Perhaps, IsInDataNode() must be expected.
    548  if (point.IsInTextNode()) {
    549    if (NS_WARN_IF(!point.GetContainer()->GetParentNode())) {
    550      // Okay, can't promote any further
    551      // XXX Why don't we return end of the text node?
    552      return point;
    553    }
    554    EditorDOMPoint atNextPreformattedNewLine =
    555        HTMLEditUtils::GetInclusiveNextPreformattedNewLineInTextNode<
    556            EditorDOMPoint>(point);
    557    if (atNextPreformattedNewLine.IsSet()) {
    558      // If the linefeed is last character of the text node, it may be
    559      // invisible if it's immediately before a block boundary.  In such
    560      // case, we should return the block boundary.
    561      Element* maybeNonEditableBlockElement = nullptr;
    562      if (HTMLEditUtils::IsInvisiblePreformattedNewLine(
    563              atNextPreformattedNewLine, &maybeNonEditableBlockElement) &&
    564          maybeNonEditableBlockElement) {
    565        // If the block is a parent of the editing host, let's return end
    566        // of editing host.
    567        if (maybeNonEditableBlockElement == &aAncestorLimiter ||
    568            !maybeNonEditableBlockElement->IsInclusiveDescendantOf(
    569                &aAncestorLimiter)) {
    570          return EditorDOMPoint::AtEndOf(aAncestorLimiter);
    571        }
    572        // If it's invisible because of parent block boundary, return end
    573        // of the block.  Otherwise, i.e., it's followed by a child block,
    574        // returns the point of the child block.
    575        if (atNextPreformattedNewLine.ContainerAs<Text>()
    576                ->IsInclusiveDescendantOf(maybeNonEditableBlockElement)) {
    577          return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement);
    578        }
    579        return EditorDOMPoint(maybeNonEditableBlockElement);
    580      }
    581      // Otherwise, return the point after the preformatted linefeed.
    582      return atNextPreformattedNewLine.NextPoint();
    583    }
    584    // want to be after the text node
    585    point.SetAfter(point.GetContainer());
    586    NS_WARNING_ASSERTION(point.IsSet(), "Failed to set to after the text node");
    587  }
    588 
    589  // Look ahead through any further inline nodes that aren't across a <br> from
    590  // us, and that are enclosed in the same block.
    591  // XXX Currently, we stop block-extending when finding visible <br> element.
    592  //     This might be different from "block-extend" of execCommand spec.
    593  //     However, the spec is really unclear.
    594  // XXX Probably, scanning only editable nodes is wrong for
    595  //     EditSubAction::eCreateOrRemoveBlock and
    596  //     EditSubAction::eFormatBlockForHTMLCommand because it might be better to
    597  //     wrap existing inline elements even if it's non-editable.  For example,
    598  //     following examples with insertParagraph causes different result:
    599  //     * <div contenteditable>foo[]<b contenteditable="false">bar</b></div>
    600  //     * <div contenteditable>foo[]<b>bar</b></div>
    601  //     * <div contenteditable>foo[]<b contenteditable="false">bar</b>baz</div>
    602  //     Only in the first case, after the caret position isn't wrapped with
    603  //     new <div> element.
    604  constexpr HTMLEditUtils::WalkTreeOptions
    605      ignoreNonEditableNodeAndStopAtBlockBoundary{
    606          HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode,
    607          HTMLEditUtils::WalkTreeOption::StopAtBlockBoundary};
    608  for (nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent(
    609           point, ignoreNonEditableNodeAndStopAtBlockBoundary,
    610           aBlockInlineCheck, &aAncestorLimiter);
    611       nextEditableContent &&
    612       !HTMLEditUtils::IsBlockElement(*nextEditableContent,
    613                                      aBlockInlineCheck) &&
    614       nextEditableContent->GetParent();
    615       nextEditableContent = HTMLEditUtils::GetNextContent(
    616           point, ignoreNonEditableNodeAndStopAtBlockBoundary,
    617           aBlockInlineCheck, &aAncestorLimiter)) {
    618    EditorDOMPoint atFirstPreformattedNewLine =
    619        HTMLEditUtils::GetInclusiveNextPreformattedNewLineInTextNode<
    620            EditorDOMPoint>(EditorRawDOMPoint(nextEditableContent, 0));
    621    if (atFirstPreformattedNewLine.IsSet()) {
    622      // If the linefeed is last character of the text node, it may be
    623      // invisible if it's immediately before a block boundary.  In such
    624      // case, we should return the block boundary.
    625      Element* maybeNonEditableBlockElement = nullptr;
    626      if (HTMLEditUtils::IsInvisiblePreformattedNewLine(
    627              atFirstPreformattedNewLine, &maybeNonEditableBlockElement) &&
    628          maybeNonEditableBlockElement) {
    629        // If the block is a parent of the editing host, let's return end
    630        // of editing host.
    631        if (maybeNonEditableBlockElement == &aAncestorLimiter ||
    632            !maybeNonEditableBlockElement->IsInclusiveDescendantOf(
    633                &aAncestorLimiter)) {
    634          return EditorDOMPoint::AtEndOf(aAncestorLimiter);
    635        }
    636        // If it's invisible because of parent block boundary, return end
    637        // of the block.  Otherwise, i.e., it's followed by a child block,
    638        // returns the point of the child block.
    639        if (atFirstPreformattedNewLine.ContainerAs<Text>()
    640                ->IsInclusiveDescendantOf(maybeNonEditableBlockElement)) {
    641          return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement);
    642        }
    643        return EditorDOMPoint(maybeNonEditableBlockElement);
    644      }
    645      // Otherwise, return the point after the preformatted linefeed.
    646      return atFirstPreformattedNewLine.NextPoint();
    647    }
    648    point.SetAfter(nextEditableContent);
    649    if (NS_WARN_IF(!point.IsSet())) {
    650      break;
    651    }
    652    if (HTMLEditUtils::IsVisibleBRElement(*nextEditableContent)) {
    653      break;
    654    }
    655  }
    656 
    657  // Finding the real end for this point unless current line ends with a <br>
    658  // element.  Look up the tree for as long as we are the last node in the
    659  // container (typically, block node), and as long as we haven't hit the body
    660  // node.
    661  for (nsIContent* nearContent = HTMLEditUtils::GetNextContent(
    662           point, ignoreNonEditableNodeAndStopAtBlockBoundary,
    663           aBlockInlineCheck, &aAncestorLimiter);
    664       !nearContent && !point.IsContainerHTMLElement(nsGkAtoms::body) &&
    665       point.GetContainerParent();
    666       nearContent = HTMLEditUtils::GetNextContent(
    667           point, ignoreNonEditableNodeAndStopAtBlockBoundary,
    668           aBlockInlineCheck, &aAncestorLimiter)) {
    669    // Don't walk past the editable section. Note that we need to check before
    670    // walking up to a parent because we need to return the parent object, so
    671    // the parent itself might not be in the editable area, but it's OK.
    672    // XXX Maybe returning parent of editing host is really error prone since
    673    //     everybody need to check whether the end point is in editing host
    674    //     when they touch there.
    675    if (!point.GetContainer()->IsInclusiveDescendantOf(&aAncestorLimiter) &&
    676        !point.GetContainerParent()->IsInclusiveDescendantOf(
    677            &aAncestorLimiter)) {
    678      break;
    679    }
    680 
    681    // If we're formatting a block, we should reformat first ancestor format
    682    // block.
    683    if (aEditSubAction == EditSubAction::eFormatBlockForHTMLCommand &&
    684        point.IsContainerElement() &&
    685        HTMLEditUtils::IsFormatElementForFormatBlockCommand(
    686            *point.ContainerAs<Element>())) {
    687      point.SetAfter(point.GetContainer());
    688      break;
    689    }
    690 
    691    point.SetAfter(point.GetContainer());
    692    if (NS_WARN_IF(!point.IsSet())) {
    693      break;
    694    }
    695  }
    696  return point;
    697 }
    698 
    699 void AutoClonedRangeArray::ExtendRangesToWrapLines(
    700    EditSubAction aEditSubAction, BlockInlineCheck aBlockInlineCheck,
    701    const Element& aAncestorLimiter) {
    702  // FYI: This is originated in
    703  // https://searchfox.org/mozilla-central/rev/1739f1301d658c9bff544a0a095ab11fca2e549d/editor/libeditor/HTMLEditSubActionHandler.cpp#6712
    704 
    705  bool removeSomeRanges = false;
    706  for (const OwningNonNull<nsRange>& range : mRanges) {
    707    // Remove non-positioned ranges.
    708    if (MOZ_UNLIKELY(!range->IsPositioned())) {
    709      removeSomeRanges = true;
    710      continue;
    711    }
    712    // If the range is native anonymous subtrees, we must meet a bug of
    713    // `Selection` so that we need to hack here.
    714    if (MOZ_UNLIKELY(range->GetStartContainer()->IsInNativeAnonymousSubtree() ||
    715                     range->GetEndContainer()->IsInNativeAnonymousSubtree())) {
    716      EditorRawDOMRange rawRange(range);
    717      if (!rawRange.EnsureNotInNativeAnonymousSubtree()) {
    718        range->Reset();
    719        removeSomeRanges = true;
    720        continue;
    721      }
    722      if (NS_FAILED(
    723              range->SetStartAndEnd(rawRange.StartRef().ToRawRangeBoundary(),
    724                                    rawRange.EndRef().ToRawRangeBoundary())) ||
    725          MOZ_UNLIKELY(!range->IsPositioned())) {
    726        range->Reset();
    727        removeSomeRanges = true;
    728        continue;
    729      }
    730    }
    731    // Finally, extend the range.
    732    if (NS_FAILED(ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
    733            range, aEditSubAction, aBlockInlineCheck, aAncestorLimiter))) {
    734      // If we failed to extend the range, we should use the original range
    735      // as-is unless the range is broken at setting the range.
    736      if (NS_WARN_IF(!range->IsPositioned())) {
    737        removeSomeRanges = true;
    738      }
    739    }
    740  }
    741  if (removeSomeRanges) {
    742    for (const size_t i : Reversed(IntegerRange(mRanges.Length()))) {
    743      if (!mRanges[i]->IsPositioned()) {
    744        mRanges.RemoveElementAt(i);
    745      }
    746    }
    747    if (!mAnchorFocusRange || !mAnchorFocusRange->IsPositioned()) {
    748      if (mRanges.IsEmpty()) {
    749        mAnchorFocusRange = nullptr;
    750      } else {
    751        mAnchorFocusRange = mRanges.LastElement();
    752      }
    753    }
    754  }
    755 }
    756 
    757 // static
    758 nsresult
    759 AutoClonedRangeArray::ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
    760    nsRange& aRange, EditSubAction aEditSubAction,
    761    BlockInlineCheck aBlockInlineCheck, const Element& aEditingHost) {
    762  MOZ_DIAGNOSTIC_ASSERT(
    763      !EditorRawDOMPoint(aRange.StartRef()).IsInNativeAnonymousSubtree());
    764  MOZ_DIAGNOSTIC_ASSERT(
    765      !EditorRawDOMPoint(aRange.EndRef()).IsInNativeAnonymousSubtree());
    766 
    767  if (NS_WARN_IF(!aRange.IsPositioned())) {
    768    return NS_ERROR_INVALID_ARG;
    769  }
    770 
    771  EditorDOMPoint startPoint(aRange.StartRef()), endPoint(aRange.EndRef());
    772 
    773  // If we're joining blocks, we call this for selecting a line to move.
    774  // Therefore, we don't want to select the ancestor blocks in this case
    775  // even if they are empty.
    776  if (aEditSubAction != EditSubAction::eMergeBlockContents) {
    777    AutoClonedRangeArray::
    778        UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement(
    779            startPoint, endPoint, aEditingHost);
    780  }
    781 
    782  // Make a new adjusted range to represent the appropriate block content.
    783  // This is tricky.  The basic idea is to push out the range endpoints to
    784  // truly enclose the blocks that we will affect.
    785 
    786  // Make sure that the new range ends up to be in the editable section.
    787  // XXX Looks like that this check wastes the time.  Perhaps, we should
    788  //     implement a method which checks both two DOM points in the editor
    789  //     root.
    790 
    791  startPoint =
    792      GetPointAtFirstContentOfLineOrParentHTMLBlockIfFirstContentOfBlock(
    793          startPoint, aEditSubAction, aBlockInlineCheck, aEditingHost);
    794  // XXX GetPointAtFirstContentOfLineOrParentBlockIfFirstContentOfBlock() may
    795  //     return point of editing host.  Perhaps, we should change it and stop
    796  //     checking it here since this check may be expensive.
    797  // XXX If the container is an element in the editing host but it points end of
    798  //     the container, this returns nullptr.  Is it intentional?
    799  if (!startPoint.GetChildOrContainerIfDataNode() ||
    800      !startPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
    801          &aEditingHost)) {
    802    return NS_ERROR_FAILURE;
    803  }
    804  endPoint = GetPointAfterFollowingLineBreakOrAtFollowingHTMLBlock(
    805      endPoint, aEditSubAction, aBlockInlineCheck, aEditingHost);
    806  const EditorDOMPoint lastRawPoint =
    807      endPoint.IsStartOfContainer() ? endPoint : endPoint.PreviousPoint();
    808  // XXX GetPointAfterFollowingLineBreakOrAtFollowingBlock() may return point of
    809  //     editing host.  Perhaps, we should change it and stop checking it here
    810  //     since this check may be expensive.
    811  // XXX If the container is an element in the editing host but it points end of
    812  //     the container, this returns nullptr.  Is it intentional?
    813  if (!lastRawPoint.GetChildOrContainerIfDataNode() ||
    814      !lastRawPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
    815          &aEditingHost)) {
    816    return NS_ERROR_FAILURE;
    817  }
    818 
    819  nsresult rv = aRange.SetStartAndEnd(startPoint.ToRawRangeBoundary(),
    820                                      endPoint.ToRawRangeBoundary());
    821  if (NS_FAILED(rv)) {
    822    return NS_ERROR_FAILURE;
    823  }
    824  return NS_OK;
    825 }
    826 
    827 Result<EditorDOMPoint, nsresult> AutoClonedRangeArray::
    828    SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
    829        HTMLEditor& aHTMLEditor, BlockInlineCheck aBlockInlineCheck,
    830        const Element& aEditingHost,
    831        const nsIContent* aAncestorLimiter /* = nullptr */) {
    832  // FYI: The following code is originated in
    833  // https://searchfox.org/mozilla-central/rev/c8e15e17bc6fd28f558c395c948a6251b38774ff/editor/libeditor/HTMLEditSubActionHandler.cpp#6971
    834 
    835  // Split text nodes. This is necessary, since given ranges may end in text
    836  // nodes in case where part of a pre-formatted elements needs to be moved.
    837  EditorDOMPoint pointToPutCaret;
    838  IgnoredErrorResult ignoredError;
    839  for (const OwningNonNull<nsRange>& range : mRanges) {
    840    EditorDOMPoint atEnd(range->EndRef());
    841    if (NS_WARN_IF(!atEnd.IsSet()) || !atEnd.IsInTextNode() ||
    842        atEnd.GetContainer() == aAncestorLimiter) {
    843      continue;
    844    }
    845 
    846    if (!atEnd.IsStartOfContainer() && !atEnd.IsEndOfContainer()) {
    847      // Split the text node.
    848      Result<SplitNodeResult, nsresult> splitAtEndResult =
    849          aHTMLEditor.SplitNodeWithTransaction(atEnd);
    850      if (MOZ_UNLIKELY(splitAtEndResult.isErr())) {
    851        NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
    852        return splitAtEndResult.propagateErr();
    853      }
    854      SplitNodeResult unwrappedSplitAtEndResult = splitAtEndResult.unwrap();
    855      unwrappedSplitAtEndResult.MoveCaretPointTo(
    856          pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
    857 
    858      // Correct the range.
    859      // The new end parent becomes the parent node of the text.
    860      MOZ_ASSERT(!range->IsInAnySelection());
    861      range->SetEnd(unwrappedSplitAtEndResult.AtNextContent<EditorRawDOMPoint>()
    862                        .ToRawRangeBoundary(),
    863                    ignoredError);
    864      NS_WARNING_ASSERTION(!ignoredError.Failed(),
    865                           "nsRange::SetEnd() failed, but ignored");
    866      ignoredError.SuppressException();
    867    }
    868  }
    869 
    870  // FYI: The following code is originated in
    871  // https://searchfox.org/mozilla-central/rev/c8e15e17bc6fd28f558c395c948a6251b38774ff/editor/libeditor/HTMLEditSubActionHandler.cpp#7023
    872  AutoTArray<OwningNonNull<RangeItem>, 8> rangeItemArray;
    873  rangeItemArray.AppendElements(mRanges.Length());
    874 
    875  // First register ranges for special editor gravity
    876  Maybe<size_t> anchorFocusRangeIndex;
    877  for (const size_t index : IntegerRange(rangeItemArray.Length())) {
    878    rangeItemArray[index] = new RangeItem();
    879    rangeItemArray[index]->StoreRange(*mRanges[index]);
    880    aHTMLEditor.RangeUpdaterRef().RegisterRangeItem(*rangeItemArray[index]);
    881    if (mRanges[index] == mAnchorFocusRange) {
    882      anchorFocusRangeIndex = Some(index);
    883    }
    884  }
    885  // TODO: We should keep the array, and just update the ranges.
    886  mRanges.Clear();
    887  mAnchorFocusRange = nullptr;
    888  // Now bust up inlines.
    889  nsresult rv = NS_OK;
    890  for (const OwningNonNull<RangeItem>& item : Reversed(rangeItemArray)) {
    891    // MOZ_KnownLive because 'rangeItemArray' is guaranteed to keep it alive.
    892    Result<EditorDOMPoint, nsresult> splitParentsResult =
    893        aHTMLEditor.SplitInlineAncestorsAtRangeBoundaries(
    894            MOZ_KnownLive(*item), aBlockInlineCheck, aEditingHost,
    895            aAncestorLimiter);
    896    if (MOZ_UNLIKELY(splitParentsResult.isErr())) {
    897      NS_WARNING("HTMLEditor::SplitInlineAncestorsAtRangeBoundaries() failed");
    898      rv = splitParentsResult.unwrapErr();
    899      break;
    900    }
    901    if (splitParentsResult.inspect().IsSet()) {
    902      pointToPutCaret = splitParentsResult.unwrap();
    903    }
    904  }
    905  // Then unregister the ranges
    906  for (const size_t index : IntegerRange(rangeItemArray.Length())) {
    907    aHTMLEditor.RangeUpdaterRef().DropRangeItem(rangeItemArray[index]);
    908    RefPtr<nsRange> range = rangeItemArray[index]->GetRange();
    909    if (range && range->IsPositioned()) {
    910      if (anchorFocusRangeIndex.isSome() && index == *anchorFocusRangeIndex) {
    911        mAnchorFocusRange = range;
    912      }
    913      mRanges.AppendElement(std::move(range));
    914    }
    915  }
    916  if (!mAnchorFocusRange && !mRanges.IsEmpty()) {
    917    mAnchorFocusRange = mRanges.LastElement();
    918  }
    919 
    920  // XXX Why do we ignore the other errors here??
    921  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    922    return Err(NS_ERROR_EDITOR_DESTROYED);
    923  }
    924  return pointToPutCaret;
    925 }
    926 
    927 nsresult AutoClonedRangeArray::CollectEditTargetNodes(
    928    const HTMLEditor& aHTMLEditor,
    929    nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents,
    930    EditSubAction aEditSubAction,
    931    CollectNonEditableNodes aCollectNonEditableNodes) const {
    932  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
    933 
    934  // FYI: This was moved from
    935  // https://searchfox.org/mozilla-central/rev/4bce7d85ba4796dd03c5dcc7cfe8eee0e4c07b3b/editor/libeditor/HTMLEditSubActionHandler.cpp#7060
    936 
    937  // Gather up a list of all the nodes
    938  for (const OwningNonNull<nsRange>& range : mRanges) {
    939    DOMSubtreeIterator iter;
    940    nsresult rv = iter.Init(*range);
    941    if (NS_FAILED(rv)) {
    942      NS_WARNING("DOMSubtreeIterator::Init() failed");
    943      return rv;
    944    }
    945    if (aOutArrayOfContents.IsEmpty()) {
    946      iter.AppendAllNodesToArray(aOutArrayOfContents);
    947    } else {
    948      AutoTArray<OwningNonNull<nsIContent>, 24> arrayOfTopChildren;
    949      iter.AppendNodesToArray(
    950          +[](nsINode& aNode, void* aArray) -> bool {
    951            MOZ_ASSERT(aArray);
    952            return !static_cast<nsTArray<OwningNonNull<nsIContent>>*>(aArray)
    953                        ->Contains(&aNode);
    954          },
    955          arrayOfTopChildren, &aOutArrayOfContents);
    956      aOutArrayOfContents.AppendElements(std::move(arrayOfTopChildren));
    957    }
    958    if (aCollectNonEditableNodes == CollectNonEditableNodes::No) {
    959      for (const size_t i :
    960           Reversed(IntegerRange(aOutArrayOfContents.Length()))) {
    961        if (!EditorUtils::IsEditableContent(aOutArrayOfContents[i],
    962                                            EditorUtils::EditorType::HTML)) {
    963          aOutArrayOfContents.RemoveElementAt(i);
    964        }
    965      }
    966    }
    967  }
    968 
    969  switch (aEditSubAction) {
    970    case EditSubAction::eCreateOrRemoveBlock:
    971    case EditSubAction::eFormatBlockForHTMLCommand: {
    972      // Certain operations should not act on li's and td's, but rather inside
    973      // them.  Alter the list as needed.
    974      CollectChildrenOptions options = {
    975          CollectChildrenOption::CollectListChildren,
    976          CollectChildrenOption::CollectTableChildren};
    977      if (aCollectNonEditableNodes == CollectNonEditableNodes::No) {
    978        options += CollectChildrenOption::IgnoreNonEditableChildren;
    979      }
    980      if (aEditSubAction == EditSubAction::eCreateOrRemoveBlock) {
    981        for (const size_t index :
    982             Reversed(IntegerRange(aOutArrayOfContents.Length()))) {
    983          const OwningNonNull<nsIContent> content = aOutArrayOfContents[index];
    984          if (HTMLEditUtils::IsListItemElement(*content)) {
    985            aOutArrayOfContents.RemoveElementAt(index);
    986            HTMLEditUtils::CollectChildren(*content, aOutArrayOfContents, index,
    987                                           options);
    988          }
    989        }
    990      } else {
    991        // <dd> and <dt> are format blocks.  Therefore, we should not handle
    992        // their children directly.  They should be replaced with new format
    993        // block.
    994        MOZ_ASSERT(
    995            HTMLEditUtils::IsFormatTagForFormatBlockCommand(*nsGkAtoms::dt));
    996        MOZ_ASSERT(
    997            HTMLEditUtils::IsFormatTagForFormatBlockCommand(*nsGkAtoms::dd));
    998        for (const size_t index :
    999             Reversed(IntegerRange(aOutArrayOfContents.Length()))) {
   1000          const OwningNonNull<nsIContent> content = aOutArrayOfContents[index];
   1001          MOZ_ASSERT_IF(HTMLEditUtils::IsListItemElement(*content),
   1002                        content->IsAnyOfHTMLElements(
   1003                            nsGkAtoms::dd, nsGkAtoms::dt, nsGkAtoms::li));
   1004          if (content->IsHTMLElement(nsGkAtoms::li)) {
   1005            aOutArrayOfContents.RemoveElementAt(index);
   1006            HTMLEditUtils::CollectChildren(*content, aOutArrayOfContents, index,
   1007                                           options);
   1008          }
   1009        }
   1010      }
   1011      // Empty text node shouldn't be selected if unnecessary
   1012      for (const size_t index :
   1013           Reversed(IntegerRange(aOutArrayOfContents.Length()))) {
   1014        if (const Text* text = aOutArrayOfContents[index]->GetAsText()) {
   1015          // Don't select empty text except to empty block
   1016          if (!HTMLEditUtils::IsVisibleTextNode(*text)) {
   1017            aOutArrayOfContents.RemoveElementAt(index);
   1018          }
   1019        }
   1020      }
   1021      break;
   1022    }
   1023    case EditSubAction::eCreateOrChangeList: {
   1024      // XXX aCollectNonEditableNodes is ignored here.  Maybe a bug.
   1025      CollectChildrenOptions options = {
   1026          CollectChildrenOption::CollectTableChildren};
   1027      for (const size_t index :
   1028           Reversed(IntegerRange(aOutArrayOfContents.Length()))) {
   1029        // Scan for table elements.  If we find table elements other than
   1030        // table, replace it with a list of any editable non-table content
   1031        // because if a selection range starts from end in a table-cell and
   1032        // ends at or starts from outside the `<table>`, we need to make
   1033        // lists in each selected table-cells.
   1034        const OwningNonNull<nsIContent> content = aOutArrayOfContents[index];
   1035        if (HTMLEditUtils::IsAnyTableElementExceptTableElementAndColumElement(
   1036                content)) {
   1037          aOutArrayOfContents.RemoveElementAt(index);
   1038          HTMLEditUtils::CollectChildren(content, aOutArrayOfContents, index,
   1039                                         options);
   1040        }
   1041      }
   1042      // If there is only one node in the array, and it is a `<div>`,
   1043      // `<blockquote>` or a list element, then look inside of it until we
   1044      // find inner list or content.
   1045      if (aOutArrayOfContents.Length() != 1) {
   1046        break;
   1047      }
   1048      Element* deepestDivBlockquoteOrListElement =
   1049          HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild(
   1050              aOutArrayOfContents[0],
   1051              {HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode},
   1052              BlockInlineCheck::Unused, nsGkAtoms::div, nsGkAtoms::blockquote,
   1053              nsGkAtoms::ul, nsGkAtoms::ol, nsGkAtoms::dl);
   1054      if (!deepestDivBlockquoteOrListElement) {
   1055        break;
   1056      }
   1057      if (deepestDivBlockquoteOrListElement->IsAnyOfHTMLElements(
   1058              nsGkAtoms::div, nsGkAtoms::blockquote)) {
   1059        aOutArrayOfContents.Clear();
   1060        // XXX Before we're called, non-editable nodes are ignored.  However,
   1061        //     we may append non-editable nodes here.
   1062        HTMLEditUtils::CollectChildren(*deepestDivBlockquoteOrListElement,
   1063                                       aOutArrayOfContents, 0, {});
   1064        break;
   1065      }
   1066      aOutArrayOfContents.ReplaceElementAt(
   1067          0, OwningNonNull<nsIContent>(*deepestDivBlockquoteOrListElement));
   1068      break;
   1069    }
   1070    case EditSubAction::eOutdent:
   1071    case EditSubAction::eIndent:
   1072    case EditSubAction::eSetPositionToAbsolute: {
   1073      // Indent/outdent already do something special for list items, but we
   1074      // still need to make sure we don't act on table elements
   1075      CollectChildrenOptions options = {
   1076          CollectChildrenOption::CollectListChildren,
   1077          CollectChildrenOption::CollectTableChildren};
   1078      if (aCollectNonEditableNodes == CollectNonEditableNodes::No) {
   1079        options += CollectChildrenOption::IgnoreNonEditableChildren;
   1080      }
   1081      for (const size_t index :
   1082           Reversed(IntegerRange(aOutArrayOfContents.Length()))) {
   1083        const OwningNonNull<nsIContent> content = aOutArrayOfContents[index];
   1084        if (HTMLEditUtils::IsAnyTableElementExceptTableElementAndColumElement(
   1085                content)) {
   1086          aOutArrayOfContents.RemoveElementAt(index);
   1087          HTMLEditUtils::CollectChildren(*content, aOutArrayOfContents, index,
   1088                                         options);
   1089        }
   1090      }
   1091      break;
   1092    }
   1093    default:
   1094      break;
   1095  }
   1096 
   1097  // Outdent should look inside of divs.
   1098  if (aEditSubAction == EditSubAction::eOutdent &&
   1099      !aHTMLEditor.IsCSSEnabled()) {
   1100    CollectChildrenOptions options = {};
   1101    if (aCollectNonEditableNodes == CollectNonEditableNodes::No) {
   1102      options += CollectChildrenOption::IgnoreNonEditableChildren;
   1103    }
   1104    for (const size_t index :
   1105         Reversed(IntegerRange(aOutArrayOfContents.Length()))) {
   1106      OwningNonNull<nsIContent> content = aOutArrayOfContents[index];
   1107      if (content->IsHTMLElement(nsGkAtoms::div)) {
   1108        aOutArrayOfContents.RemoveElementAt(index);
   1109        HTMLEditUtils::CollectChildren(*content, aOutArrayOfContents, index,
   1110                                       options);
   1111      }
   1112    }
   1113  }
   1114 
   1115  return NS_OK;
   1116 }
   1117 
   1118 Element* AutoClonedRangeArray::GetClosestAncestorAnyListElementOfRange() const {
   1119  for (const OwningNonNull<nsRange>& range : mRanges) {
   1120    nsINode* commonAncestorNode = range->GetClosestCommonInclusiveAncestor();
   1121    if (MOZ_UNLIKELY(!commonAncestorNode)) {
   1122      continue;
   1123    }
   1124    for (Element* const element :
   1125         commonAncestorNode->InclusiveAncestorsOfType<Element>()) {
   1126      if (HTMLEditUtils::IsListElement(*element)) {
   1127        return element;
   1128      }
   1129    }
   1130  }
   1131  return nullptr;
   1132 }
   1133 
   1134 void AutoClonedRangeArray::RemoveCollapsedRanges() {
   1135  for (const auto index : Reversed(IntegerRange(mRanges.Length()))) {
   1136    if (mRanges[index]->Collapsed()) {
   1137      mRanges.RemoveElementAt(index);
   1138    }
   1139  }
   1140  if (mAnchorFocusRange->Collapsed()) {
   1141    MOZ_ASSERT(!mRanges.Contains(mAnchorFocusRange.get()));
   1142    if (mRanges.IsEmpty()) {
   1143      RemoveAllRanges();
   1144    } else {
   1145      mAnchorFocusRange = mRanges.LastElement();
   1146    }
   1147  } else {
   1148    MOZ_ASSERT(mRanges.Contains(mAnchorFocusRange.get()));
   1149  }
   1150 }
   1151 
   1152 void AutoClonedRangeArray::ExtendRangeToContainSurroundingInvisibleWhiteSpaces(
   1153    nsIEditor::EStripWrappers aStripWrappers) {
   1154  const auto PointAfterLineBoundary =
   1155      [](const WSScanResult& aPreviousThing) -> EditorRawDOMPoint {
   1156    if (aPreviousThing.ReachedCurrentBlockBoundary()) {
   1157      return EditorRawDOMPoint(aPreviousThing.ElementPtr(), 0u);
   1158    }
   1159    return aPreviousThing.PointAfterReachedContent<EditorRawDOMPoint>();
   1160  };
   1161  const auto PointeAtLineBoundary =
   1162      [](const WSScanResult& aNextThing) -> EditorRawDOMPoint {
   1163    if (aNextThing.ReachedCurrentBlockBoundary()) {
   1164      return EditorRawDOMPoint::AtEndOf(*aNextThing.ElementPtr());
   1165    }
   1166    return aNextThing.PointAtReachedContent<EditorRawDOMPoint>();
   1167  };
   1168  for (const OwningNonNull<nsRange>& range : mRanges) {
   1169    if (MOZ_UNLIKELY(range->Collapsed())) {
   1170      // Don't extend the collapsed range to do nothing for the range.
   1171      continue;
   1172    }
   1173    const WSScanResult previousThing =
   1174        WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   1175            {WSRunScanner::Option::OnlyEditableNodes},
   1176            EditorRawDOMPoint(range->StartRef()));
   1177    if (previousThing.ReachedLineBoundary()) {
   1178      const EditorRawDOMPoint mostDistantNewStart =
   1179          [&]() MOZ_NEVER_INLINE_DEBUG {
   1180            if (aStripWrappers == nsIEditor::eStrip) {
   1181              nsINode* const commonAncestor =
   1182                  range->GetClosestCommonInclusiveAncestor();
   1183              MOZ_ASSERT(commonAncestor);
   1184              Element* const commonContainer =
   1185                  commonAncestor->GetAsElementOrParentElement();
   1186              if (NS_WARN_IF(!commonContainer)) {
   1187                return EditorRawDOMPoint();
   1188              }
   1189              return EditorRawDOMPoint(commonContainer, 0u);
   1190            }
   1191            Element* const container =
   1192                range->StartRef().GetContainer()->GetAsElementOrParentElement();
   1193            if (NS_WARN_IF(!container)) {
   1194              return EditorRawDOMPoint();
   1195            }
   1196            return EditorRawDOMPoint(container, 0u);
   1197          }();
   1198      const EditorRawDOMPoint afterLineBoundary =
   1199          PointAfterLineBoundary(previousThing);
   1200      const auto& newStart =
   1201          [&]() MOZ_NEVER_INLINE_DEBUG -> const EditorRawDOMPoint& {
   1202        // If the container wraps the line boundary, we can extend the range
   1203        // to the line boundary.
   1204        if (MOZ_UNLIKELY(!mostDistantNewStart.IsSet()) ||
   1205            mostDistantNewStart.IsBefore(afterLineBoundary)) {
   1206          return afterLineBoundary;
   1207        }
   1208        // If the container does not wrap the line boundary, we can delete
   1209        // first content of the container.
   1210        return mostDistantNewStart;
   1211      }();
   1212      const auto betterNewStart = [&]() MOZ_NEVER_INLINE_DEBUG {
   1213        if (MOZ_UNLIKELY(!newStart.IsSet())) {
   1214          return EditorRawDOMPoint();
   1215        }
   1216        MOZ_ASSERT_IF(mostDistantNewStart.IsSet(),
   1217                      mostDistantNewStart.IsStartOfContainer());
   1218        auto* const firstText = Text::FromNodeOrNull(
   1219            newStart == mostDistantNewStart
   1220                ? mostDistantNewStart.GetContainer()->GetFirstChild()
   1221                : newStart.GetChild());
   1222        if (!firstText) {
   1223          return newStart;
   1224        }
   1225        return EditorRawDOMPoint(firstText, 0u);
   1226      }();
   1227      if (MOZ_LIKELY(!NS_WARN_IF(!betterNewStart.IsSet())) &&
   1228          betterNewStart != range->StartRef()) {
   1229        IgnoredErrorResult ignoredError;
   1230        range->SetStart(betterNewStart.ToRawRangeBoundary(), ignoredError);
   1231        NS_WARNING_ASSERTION(!ignoredError.Failed(),
   1232                             "nsRange::SetStart() failed, but ignored");
   1233      }
   1234    }
   1235    const WSScanResult nextThing =
   1236        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   1237            {WSRunScanner::Option::OnlyEditableNodes},
   1238            EditorRawDOMPoint(range->EndRef()));
   1239    if (!nextThing.ReachedLineBoundary()) {
   1240      continue;
   1241    }
   1242    const EditorRawDOMPoint mostDistantNewEnd = [&]() MOZ_NEVER_INLINE_DEBUG {
   1243      if (aStripWrappers == nsIEditor::eStrip) {
   1244        nsINode* const commonAncestor =
   1245            range->GetClosestCommonInclusiveAncestor();
   1246        MOZ_ASSERT(commonAncestor);
   1247        Element* const commonContainer =
   1248            commonAncestor->GetAsElementOrParentElement();
   1249        if (NS_WARN_IF(!commonContainer)) {
   1250          return EditorRawDOMPoint();
   1251        }
   1252        return EditorRawDOMPoint::AtEndOf(*commonContainer);
   1253      }
   1254      Element* const container =
   1255          range->EndRef().GetContainer()->GetAsElementOrParentElement();
   1256      if (NS_WARN_IF(!container)) {
   1257        return EditorRawDOMPoint();
   1258      }
   1259      return EditorRawDOMPoint::AtEndOf(*container);
   1260    }();
   1261    if (MOZ_UNLIKELY(!mostDistantNewEnd.IsSet())) {
   1262      continue;
   1263    }
   1264    const EditorRawDOMPoint atLineBoundary = PointeAtLineBoundary(nextThing);
   1265    const auto& newEnd =
   1266        [&]() MOZ_NEVER_INLINE_DEBUG -> const EditorRawDOMPoint& {
   1267      // If the container wraps the line boundary, we can use the boundary
   1268      // point.
   1269      if (atLineBoundary.IsBefore(mostDistantNewEnd)) {
   1270        return atLineBoundary;
   1271      }
   1272      // If the container does not wrap the line boundary, we can delete last
   1273      // content of the container.
   1274      return mostDistantNewEnd;
   1275    }();
   1276    if (MOZ_UNLIKELY(!newEnd.IsSet())) {
   1277      continue;
   1278    }
   1279    const auto betterNewEnd = [&]() MOZ_NEVER_INLINE_DEBUG {
   1280      MOZ_ASSERT_IF(mostDistantNewEnd.IsSet(),
   1281                    mostDistantNewEnd.IsEndOfContainer());
   1282      auto* const lastText = Text::FromNodeOrNull(
   1283          newEnd == mostDistantNewEnd
   1284              ? mostDistantNewEnd.GetContainer()->GetLastChild()
   1285              : (!newEnd.IsStartOfContainer()
   1286                     ? newEnd.GetPreviousSiblingOfChild()
   1287                     : nullptr));
   1288      if (!lastText) {
   1289        return newEnd;
   1290      }
   1291      return EditorRawDOMPoint::AtEndOf(*lastText);
   1292    }();
   1293    if (NS_WARN_IF(!betterNewEnd.IsSet()) || betterNewEnd == range->EndRef()) {
   1294      continue;
   1295    }
   1296    IgnoredErrorResult ignoredError;
   1297    range->SetEnd(betterNewEnd.ToRawRangeBoundary(), ignoredError);
   1298    NS_WARNING_ASSERTION(!ignoredError.Failed(),
   1299                         "nsRange::SetEnd() failed, but ignored");
   1300  }
   1301 }
   1302 
   1303 /******************************************************************************
   1304 * mozilla::AutoClonedSelectionRangeArray
   1305 *****************************************************************************/
   1306 
   1307 template AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
   1308    const EditorDOMRange& aRange,
   1309    const LimitersAndCaretData& aLimitersAndCaretData);
   1310 template AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
   1311    const EditorRawDOMRange& aRange,
   1312    const LimitersAndCaretData& aLimitersAndCaretData);
   1313 template AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
   1314    const EditorDOMPoint& aRange,
   1315    const LimitersAndCaretData& aLimitersAndCaretData);
   1316 template AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
   1317    const EditorRawDOMPoint& aRange,
   1318    const LimitersAndCaretData& aLimitersAndCaretData);
   1319 
   1320 AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
   1321    const dom::Selection& aSelection) {
   1322  Initialize(aSelection);
   1323 }
   1324 
   1325 AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
   1326    const AutoClonedSelectionRangeArray& aOther)
   1327    : AutoClonedRangeArray(aOther),
   1328      mLimitersAndCaretData(aOther.mLimitersAndCaretData) {}
   1329 
   1330 template <typename PointType>
   1331 AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
   1332    const EditorDOMRangeBase<PointType>& aRange,
   1333    const LimitersAndCaretData& aLimitersAndCaretData)
   1334    : mLimitersAndCaretData(aLimitersAndCaretData) {
   1335  MOZ_ASSERT(aRange.IsPositionedAndValid());
   1336  RefPtr<nsRange> range = aRange.CreateRange(IgnoreErrors());
   1337  if (NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned()) ||
   1338      NS_WARN_IF(!RangeIsInLimiters(*range))) {
   1339    return;
   1340  }
   1341  mRanges.AppendElement(*range);
   1342  mAnchorFocusRange = std::move(range);
   1343 }
   1344 
   1345 template <typename PT, typename CT>
   1346 AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
   1347    const EditorDOMPointBase<PT, CT>& aPoint,
   1348    const LimitersAndCaretData& aLimitersAndCaretData)
   1349    : mLimitersAndCaretData(aLimitersAndCaretData) {
   1350  MOZ_ASSERT(aPoint.IsSetAndValid());
   1351  if (NS_WARN_IF(!NodeIsInLimiters(aPoint.GetContainer()))) {
   1352    return;
   1353  }
   1354  RefPtr<nsRange> range = aPoint.CreateCollapsedRange(IgnoreErrors());
   1355  if (NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned())) {
   1356    return;
   1357  }
   1358  mRanges.AppendElement(*range);
   1359  mAnchorFocusRange = std::move(range);
   1360  SetNewCaretAssociationHint(aPoint.ToRawRangeBoundary(),
   1361                             aPoint.GetInterlinePosition());
   1362 }
   1363 
   1364 AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
   1365    const nsRange& aRange, const LimitersAndCaretData& aLimitersAndCaretData)
   1366    : mLimitersAndCaretData(aLimitersAndCaretData) {
   1367  MOZ_ASSERT(aRange.IsPositioned());
   1368  if (NS_WARN_IF(!RangeIsInLimiters(aRange))) {
   1369    return;
   1370  }
   1371  mRanges.AppendElement(aRange.CloneRange());
   1372  mAnchorFocusRange = mRanges[0];
   1373 }
   1374 
   1375 void AutoClonedSelectionRangeArray::SetNewCaretAssociationHint(
   1376    const RawRangeBoundary& aRawRangeBoundary,
   1377    InterlinePosition aInternlinePosition) {
   1378  if (aInternlinePosition == Selection::InterlinePosition::Undefined) {
   1379    mLimitersAndCaretData.mCaretAssociationHint = ComputeCaretAssociationHint(
   1380        mLimitersAndCaretData.mCaretAssociationHint,
   1381        mLimitersAndCaretData.mCaretBidiLevel, aRawRangeBoundary);
   1382  } else {
   1383    SetInterlinePosition(aInternlinePosition);
   1384  }
   1385 }
   1386 
   1387 bool AutoClonedSelectionRangeArray::SaveAndTrackRanges(
   1388    HTMLEditor& aHTMLEditor) {
   1389  if (mSavedRanges.isSome()) {
   1390    return false;
   1391  }
   1392  mSavedRanges.emplace(*this);
   1393  aHTMLEditor.RangeUpdaterRef().RegisterSelectionState(mSavedRanges.ref());
   1394  mTrackingHTMLEditor = &aHTMLEditor;
   1395  return true;
   1396 }
   1397 
   1398 void AutoClonedSelectionRangeArray::ClearSavedRanges() {
   1399  if (mSavedRanges.isNothing()) {
   1400    return;
   1401  }
   1402  OwningNonNull<HTMLEditor> htmlEditor(std::move(mTrackingHTMLEditor));
   1403  MOZ_ASSERT(!mTrackingHTMLEditor);
   1404  htmlEditor->RangeUpdaterRef().DropSelectionState(mSavedRanges.ref());
   1405  mSavedRanges.reset();
   1406 }
   1407 
   1408 Result<nsIEditor::EDirection, nsresult>
   1409 AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor(
   1410    const EditorBase& aEditorBase, nsIEditor::EDirection aDirectionAndAmount) {
   1411  MOZ_ASSERT(aEditorBase.IsEditActionDataAvailable());
   1412  MOZ_ASSERT(mAnchorFocusRange);
   1413  MOZ_ASSERT(mAnchorFocusRange->IsPositioned());
   1414  MOZ_ASSERT(mAnchorFocusRange->StartRef().IsSet());
   1415  MOZ_ASSERT(mAnchorFocusRange->EndRef().IsSet());
   1416 
   1417  if (!EditorUtils::IsFrameSelectionRequiredToExtendSelection(
   1418          aDirectionAndAmount, *this)) {
   1419    return aDirectionAndAmount;
   1420  }
   1421 
   1422  if (NS_WARN_IF(mRanges.IsEmpty())) {
   1423    return Err(NS_ERROR_FAILURE);
   1424  }
   1425 
   1426  const RefPtr<PresShell> presShell = aEditorBase.GetPresShell();
   1427  if (NS_WARN_IF(!presShell)) {
   1428    return Err(NS_ERROR_FAILURE);
   1429  }
   1430 
   1431  const RefPtr<Element> editingHost =
   1432      aEditorBase.IsHTMLEditor()
   1433          ? aEditorBase.AsHTMLEditor()->ComputeEditingHost()
   1434          : nullptr;
   1435  if (aEditorBase.IsHTMLEditor() && NS_WARN_IF(!editingHost)) {
   1436    return Err(NS_ERROR_FAILURE);
   1437  }
   1438 
   1439  Result<RefPtr<nsRange>, nsresult> result(NS_ERROR_UNEXPECTED);
   1440  const OwningNonNull<nsRange> anchorFocusRange = *mAnchorFocusRange;
   1441  const LimitersAndCaretData limitersAndCaretData = mLimitersAndCaretData;
   1442  const nsDirection rangeDirection =
   1443      mDirection == eDirNext ? eDirNext : eDirPrevious;
   1444  nsIEditor::EDirection directionAndAmountResult = aDirectionAndAmount;
   1445  switch (aDirectionAndAmount) {
   1446    case nsIEditor::eNextWord:
   1447      result = nsFrameSelection::CreateRangeExtendedToNextWordBoundary<nsRange>(
   1448          *presShell, limitersAndCaretData, anchorFocusRange, rangeDirection);
   1449      if (NS_WARN_IF(aEditorBase.Destroyed())) {
   1450        return Err(NS_ERROR_EDITOR_DESTROYED);
   1451      }
   1452      NS_WARNING_ASSERTION(
   1453          result.isOk(),
   1454          "nsFrameSelection::CreateRangeExtendedToNextWordBoundary() failed");
   1455      // DeleteSelectionWithTransaction() doesn't handle these actions
   1456      // because it's inside batching, so don't confuse it:
   1457      directionAndAmountResult = nsIEditor::eNone;
   1458      break;
   1459    case nsIEditor::ePreviousWord:
   1460      result =
   1461          nsFrameSelection::CreateRangeExtendedToPreviousWordBoundary<nsRange>(
   1462              *presShell, limitersAndCaretData, anchorFocusRange,
   1463              rangeDirection);
   1464      if (NS_WARN_IF(aEditorBase.Destroyed())) {
   1465        return Err(NS_ERROR_EDITOR_DESTROYED);
   1466      }
   1467      NS_WARNING_ASSERTION(
   1468          result.isOk(),
   1469          "nsFrameSelection::CreateRangeExtendedToPreviousWordBoundary() "
   1470          "failed");
   1471      // DeleteSelectionWithTransaction() doesn't handle these actions
   1472      // because it's inside batching, so don't confuse it:
   1473      directionAndAmountResult = nsIEditor::eNone;
   1474      break;
   1475    case nsIEditor::eNext:
   1476      result =
   1477          nsFrameSelection::CreateRangeExtendedToNextGraphemeClusterBoundary<
   1478              nsRange>(*presShell, limitersAndCaretData, anchorFocusRange,
   1479                       rangeDirection);
   1480      if (NS_WARN_IF(aEditorBase.Destroyed())) {
   1481        return Err(NS_ERROR_EDITOR_DESTROYED);
   1482      }
   1483      NS_WARNING_ASSERTION(result.isOk(),
   1484                           "nsFrameSelection::"
   1485                           "CreateRangeExtendedToNextGraphemeClusterBoundary() "
   1486                           "failed");
   1487      // Don't set directionAndAmount to eNone (see Bug 502259)
   1488      break;
   1489    case nsIEditor::ePrevious: {
   1490      // Only extend the selection where the selection is after a UTF-16
   1491      // surrogate pair or a variation selector.
   1492      // For other cases we don't want to do that, in order
   1493      // to make sure that pressing backspace will only delete the last
   1494      // typed character.
   1495      // XXX This is odd if the previous one is a sequence for a grapheme
   1496      //     cluster.
   1497      const auto atStartOfSelection = GetFirstRangeStartPoint<EditorDOMPoint>();
   1498      if (MOZ_UNLIKELY(NS_WARN_IF(!atStartOfSelection.IsSet()))) {
   1499        return Err(NS_ERROR_FAILURE);
   1500      }
   1501 
   1502      // node might be anonymous DIV, so we find better text node
   1503      const EditorDOMPoint insertionPoint =
   1504          aEditorBase.IsTextEditor()
   1505              ? aEditorBase.AsTextEditor()->FindBetterInsertionPoint(
   1506                    atStartOfSelection)
   1507              : atStartOfSelection.GetPointInTextNodeIfPointingAroundTextNode<
   1508                    EditorDOMPoint>();
   1509      if (MOZ_UNLIKELY(!insertionPoint.IsSet())) {
   1510        NS_WARNING(
   1511            "EditorBase::FindBetterInsertionPoint() failed, but ignored");
   1512        return aDirectionAndAmount;
   1513      }
   1514 
   1515      if (!insertionPoint.IsInTextNode()) {
   1516        return aDirectionAndAmount;
   1517      }
   1518 
   1519      const CharacterDataBuffer* data =
   1520          &insertionPoint.ContainerAs<Text>()->DataBuffer();
   1521      uint32_t offset = insertionPoint.Offset();
   1522      if (!(offset > 1 &&
   1523            data->IsLowSurrogateFollowingHighSurrogateAt(offset - 1)) &&
   1524          !(offset > 0 &&
   1525            gfxFontUtils::IsVarSelector(data->CharAt(offset - 1)))) {
   1526        return aDirectionAndAmount;
   1527      }
   1528      // Different from the `eNext` case, we look for character boundary.
   1529      // I'm not sure whether this inconsistency between "Delete" and
   1530      // "Backspace" is intentional or not.
   1531      result = nsFrameSelection::CreateRangeExtendedToPreviousCharacterBoundary<
   1532          nsRange>(*presShell, limitersAndCaretData, anchorFocusRange,
   1533                   rangeDirection);
   1534      if (NS_WARN_IF(aEditorBase.Destroyed())) {
   1535        return Err(NS_ERROR_EDITOR_DESTROYED);
   1536      }
   1537      NS_WARNING_ASSERTION(
   1538          result.isOk(),
   1539          "nsFrameSelection::"
   1540          "CreateRangeExtendedToPreviousGraphemeClusterBoundary() failed");
   1541      break;
   1542    }
   1543    case nsIEditor::eToBeginningOfLine:
   1544      result =
   1545          nsFrameSelection::CreateRangeExtendedToPreviousHardLineBreak<nsRange>(
   1546              *presShell, limitersAndCaretData, anchorFocusRange,
   1547              rangeDirection);
   1548      if (NS_WARN_IF(aEditorBase.Destroyed())) {
   1549        return Err(NS_ERROR_EDITOR_DESTROYED);
   1550      }
   1551      NS_WARNING_ASSERTION(
   1552          result.isOk(),
   1553          "nsFrameSelection::CreateRangeExtendedToPreviousHardLineBreak() "
   1554          "failed");
   1555      directionAndAmountResult = nsIEditor::eNone;
   1556      break;
   1557    case nsIEditor::eToEndOfLine:
   1558      result =
   1559          nsFrameSelection::CreateRangeExtendedToNextHardLineBreak<nsRange>(
   1560              *presShell, limitersAndCaretData, anchorFocusRange,
   1561              rangeDirection);
   1562      if (NS_WARN_IF(aEditorBase.Destroyed())) {
   1563        return Err(NS_ERROR_EDITOR_DESTROYED);
   1564      }
   1565      NS_WARNING_ASSERTION(
   1566          result.isOk(),
   1567          "nsFrameSelection::CreateRangeExtendedToNextHardLineBreak() failed");
   1568      directionAndAmountResult = nsIEditor::eNext;
   1569      break;
   1570    default:
   1571      return aDirectionAndAmount;
   1572  }
   1573 
   1574  if (result.isErr()) {
   1575    return Err(result.inspectErr());
   1576  }
   1577  RefPtr<nsRange> extendedRange(result.unwrap().forget());
   1578  if (!extendedRange || NS_WARN_IF(!extendedRange->IsPositioned())) {
   1579    NS_WARNING("Failed to extend the range, but ignored");
   1580    return directionAndAmountResult;
   1581  }
   1582 
   1583  // If the new range isn't editable, keep using the original range.
   1584  if (aEditorBase.IsHTMLEditor() &&
   1585      !AutoClonedRangeArray::IsEditableRange(*extendedRange, *editingHost)) {
   1586    return aDirectionAndAmount;
   1587  }
   1588 
   1589  if (NS_WARN_IF(!mLimitersAndCaretData.RangeInLimiters(*extendedRange))) {
   1590    NS_WARNING("A range was extended to outer of selection limiter");
   1591    return Err(NS_ERROR_FAILURE);
   1592  }
   1593 
   1594  // Swap focus/anchor range with the extended range.
   1595  DebugOnly<bool> found = false;
   1596  for (OwningNonNull<nsRange>& range : mRanges) {
   1597    if (range == mAnchorFocusRange) {
   1598      range = *extendedRange;
   1599      found = true;
   1600      break;
   1601    }
   1602  }
   1603  MOZ_ASSERT(found);
   1604  mAnchorFocusRange.swap(extendedRange);
   1605  return directionAndAmountResult;
   1606 }
   1607 
   1608 }  // namespace mozilla