tor-browser

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

WSRunScanner.cpp (50219B)


      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 "WSRunScanner.h"
      7 
      8 #include "EditorDOMPoint.h"
      9 #include "ErrorList.h"
     10 #include "HTMLEditor.h"
     11 #include "HTMLEditUtils.h"
     12 
     13 #include "mozilla/Assertions.h"
     14 #include "mozilla/Casting.h"  // for AssertedCast
     15 #include "mozilla/dom/Comment.h"
     16 
     17 #include "nsDebug.h"
     18 #include "nsError.h"
     19 #include "nsIContent.h"
     20 #include "nsIContentInlines.h"
     21 #include "nsRange.h"
     22 
     23 namespace mozilla {
     24 
     25 using namespace dom;
     26 
     27 /******************************************************************************
     28 * mozilla::WSScanResult
     29 ******************************************************************************/
     30 
     31 void WSScanResult::AssertIfInvalidData(const WSRunScanner& aScanner) const {
     32 #ifdef DEBUG
     33  MOZ_ASSERT(mReason == WSType::UnexpectedError ||
     34             mReason == WSType::InUncomposedDoc ||
     35             mReason == WSType::NonCollapsibleCharacters ||
     36             mReason == WSType::CollapsibleWhiteSpaces ||
     37             mReason == WSType::BRElement ||
     38             mReason == WSType::PreformattedLineBreak ||
     39             mReason == WSType::SpecialContent ||
     40             mReason == WSType::CurrentBlockBoundary ||
     41             mReason == WSType::OtherBlockBoundary ||
     42             mReason == WSType::InlineEditingHostBoundary);
     43  MOZ_ASSERT_IF(mReason == WSType::UnexpectedError, !mContent);
     44  MOZ_ASSERT_IF(mReason != WSType::UnexpectedError, mContent);
     45  MOZ_ASSERT_IF(mReason == WSType::InUncomposedDoc,
     46                !mContent->IsInComposedDoc());
     47  MOZ_ASSERT_IF(mContent && !mContent->IsInComposedDoc(),
     48                mReason == WSType::InUncomposedDoc);
     49  MOZ_ASSERT_IF(mReason == WSType::NonCollapsibleCharacters ||
     50                    mReason == WSType::CollapsibleWhiteSpaces ||
     51                    mReason == WSType::PreformattedLineBreak,
     52                mContent->IsText());
     53  MOZ_ASSERT_IF(mReason == WSType::NonCollapsibleCharacters ||
     54                    mReason == WSType::CollapsibleWhiteSpaces ||
     55                    mReason == WSType::PreformattedLineBreak,
     56                mOffset.isSome());
     57  MOZ_ASSERT_IF(mReason == WSType::NonCollapsibleCharacters ||
     58                    mReason == WSType::CollapsibleWhiteSpaces ||
     59                    mReason == WSType::PreformattedLineBreak,
     60                mContent->AsText()->TextDataLength() > 0);
     61  MOZ_ASSERT_IF(mDirection == ScanDirection::Backward &&
     62                    (mReason == WSType::NonCollapsibleCharacters ||
     63                     mReason == WSType::CollapsibleWhiteSpaces ||
     64                     mReason == WSType::PreformattedLineBreak),
     65                *mOffset > 0);
     66  MOZ_ASSERT_IF(mDirection == ScanDirection::Forward &&
     67                    (mReason == WSType::NonCollapsibleCharacters ||
     68                     mReason == WSType::CollapsibleWhiteSpaces ||
     69                     mReason == WSType::PreformattedLineBreak),
     70                *mOffset < mContent->AsText()->TextDataLength());
     71  MOZ_ASSERT_IF(mReason == WSType::BRElement,
     72                mContent->IsHTMLElement(nsGkAtoms::br));
     73  MOZ_ASSERT_IF(mReason == WSType::PreformattedLineBreak,
     74                EditorUtils::IsNewLinePreformatted(*mContent));
     75  MOZ_ASSERT_IF(
     76      mReason == WSType::SpecialContent,
     77      (mContent->IsText() && !mContent->IsEditable()) ||
     78          (!mContent->IsHTMLElement(nsGkAtoms::br) &&
     79           !HTMLEditUtils::IsBlockElement(
     80               *mContent,
     81               aScanner.ReferredHTMLDefaultStyle()
     82                   ? BlockInlineCheck::UseHTMLDefaultStyle
     83                   : BlockInlineCheck::UseComputedDisplayOutsideStyle)));
     84  MOZ_ASSERT_IF(
     85      mReason == WSType::OtherBlockBoundary,
     86      HTMLEditUtils::IsBlockElement(
     87          *mContent, aScanner.ReferredHTMLDefaultStyle()
     88                         ? BlockInlineCheck::UseHTMLDefaultStyle
     89                         : BlockInlineCheck::UseComputedDisplayOutsideStyle));
     90  MOZ_ASSERT_IF(mReason == WSType::CurrentBlockBoundary, mContent->IsElement());
     91  MOZ_ASSERT_IF(mReason == WSType::CurrentBlockBoundary &&
     92                    aScanner.ScanOptions().contains(
     93                        WSRunScanner::Option::OnlyEditableNodes),
     94                mContent->IsEditable());
     95  MOZ_ASSERT_IF(
     96      mReason == WSType::CurrentBlockBoundary,
     97      HTMLEditUtils::IsBlockElement(
     98          *mContent, aScanner.ReferredHTMLDefaultStyle()
     99                         ? BlockInlineCheck::UseHTMLDefaultStyle
    100                         : BlockInlineCheck::UseComputedDisplayStyle));
    101  MOZ_ASSERT_IF(mReason == WSType::InlineEditingHostBoundary,
    102                mContent->IsElement());
    103  MOZ_ASSERT_IF(mReason == WSType::InlineEditingHostBoundary &&
    104                    aScanner.ScanOptions().contains(
    105                        WSRunScanner::Option::OnlyEditableNodes),
    106                mContent->IsEditable());
    107  MOZ_ASSERT_IF(
    108      mReason == WSType::InlineEditingHostBoundary,
    109      !HTMLEditUtils::IsBlockElement(
    110          *mContent, aScanner.ReferredHTMLDefaultStyle()
    111                         ? BlockInlineCheck::UseHTMLDefaultStyle
    112                         : BlockInlineCheck::UseComputedDisplayStyle));
    113  MOZ_ASSERT_IF(mReason == WSType::InlineEditingHostBoundary,
    114                !mContent->GetParentElement() ||
    115                    !mContent->GetParentElement()->IsEditable());
    116 #endif  // #ifdef DEBUG
    117 }
    118 
    119 /******************************************************************************
    120 * mozilla::WSRunScanner
    121 ******************************************************************************/
    122 
    123 template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
    124    const EditorDOMPoint& aPoint) const;
    125 template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
    126    const EditorRawDOMPoint& aPoint) const;
    127 template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
    128    const EditorDOMPointInText& aPoint) const;
    129 template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
    130    const EditorRawDOMPointInText& aPoint) const;
    131 template WSScanResult
    132 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
    133    const EditorDOMPoint& aPoint) const;
    134 template WSScanResult
    135 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
    136    const EditorRawDOMPoint& aPoint) const;
    137 template WSScanResult
    138 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
    139    const EditorDOMPointInText& aPoint) const;
    140 template WSScanResult
    141 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
    142    const EditorRawDOMPointInText& aPoint) const;
    143 template EditorDOMPoint WSRunScanner::GetAfterLastVisiblePoint(Options, Text&,
    144                                                               const Element*);
    145 template EditorRawDOMPoint WSRunScanner::GetAfterLastVisiblePoint(
    146    Options, Text&, const Element*);
    147 template EditorDOMPoint WSRunScanner::GetFirstVisiblePoint(Options, Text&,
    148                                                           const Element*);
    149 template EditorRawDOMPoint WSRunScanner::GetFirstVisiblePoint(Options, Text&,
    150                                                              const Element*);
    151 
    152 template <typename PT, typename CT>
    153 WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
    154    const EditorDOMPointBase<PT, CT>& aPoint) const {
    155  MOZ_ASSERT(aPoint.IsSet());
    156  MOZ_ASSERT(aPoint.IsInComposedDoc());
    157 
    158  if (MOZ_UNLIKELY(!aPoint.IsSet())) {
    159    return WSScanResult::Error();
    160  }
    161 
    162  // We may not be able to check editable state in uncomposed tree as expected.
    163  // For example, only some descendants in an editing host is temporarily
    164  // removed from the tree, they are not editable unless nested contenteditable
    165  // attribute is set to "true".
    166  if (MOZ_UNLIKELY(!aPoint.IsInComposedDoc())) {
    167    return WSScanResult(*this, WSScanResult::ScanDirection::Backward,
    168                        *aPoint.template ContainerAs<nsIContent>(),
    169                        WSType::InUncomposedDoc);
    170  }
    171 
    172  if (!TextFragmentDataAtStartRef().IsInitialized()) {
    173    return WSScanResult::Error();
    174  }
    175 
    176  // If the range has visible text and start of the visible text is before
    177  // aPoint, return previous character in the text.
    178  const VisibleWhiteSpacesData& visibleWhiteSpaces =
    179      TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef();
    180  if (visibleWhiteSpaces.IsInitialized() &&
    181      visibleWhiteSpaces.StartRef().IsBefore(aPoint)) {
    182    // If the visible things are not editable, we shouldn't scan "editable"
    183    // things now.  Whether keep scanning editable things or not should be
    184    // considered by the caller.
    185    if (ScanOptions().contains(Option::OnlyEditableNodes) &&
    186        aPoint.GetChild() &&
    187        !HTMLEditUtils::IsSimplyEditableNode((*aPoint.GetChild()))) {
    188      return WSScanResult(*this, WSScanResult::ScanDirection::Backward,
    189                          *aPoint.GetChild(), WSType::SpecialContent);
    190    }
    191    const auto atPreviousChar =
    192        GetPreviousCharPoint<EditorRawDOMPointInText>(aPoint);
    193    // When it's a non-empty text node, return it.
    194    if (atPreviousChar.IsSet() && !atPreviousChar.IsContainerEmpty()) {
    195      MOZ_ASSERT(!atPreviousChar.IsEndOfContainer());
    196      return WSScanResult(*this, WSScanResult::ScanDirection::Backward,
    197                          atPreviousChar.template NextPoint<EditorDOMPoint>(),
    198                          atPreviousChar.IsCharCollapsibleASCIISpaceOrNBSP()
    199                              ? WSType::CollapsibleWhiteSpaces
    200                          : atPreviousChar.IsCharPreformattedNewLine()
    201                              ? WSType::PreformattedLineBreak
    202                              : WSType::NonCollapsibleCharacters);
    203    }
    204  }
    205 
    206  if (NS_WARN_IF(TextFragmentDataAtStartRef().StartRawReason() ==
    207                 WSType::UnexpectedError)) {
    208    return WSScanResult::Error();
    209  }
    210 
    211  MOZ_ASSERT_IF(!ScanOptions().contains(Option::StopAtComment),
    212                !Comment::FromNodeOrNull(
    213                    TextFragmentDataAtStartRef().GetStartReasonContent()));
    214  switch (TextFragmentDataAtStartRef().StartRawReason()) {
    215    case WSType::CollapsibleWhiteSpaces:
    216    case WSType::NonCollapsibleCharacters:
    217    case WSType::PreformattedLineBreak:
    218      MOZ_ASSERT(TextFragmentDataAtStartRef().StartRef().IsSet());
    219      // XXX: If we find the character at last of a text node and we started
    220      // scanning from following text node of it, some callers may work with the
    221      // point in the following text node instead of end of the found text node.
    222      return WSScanResult(*this, WSScanResult::ScanDirection::Backward,
    223                          TextFragmentDataAtStartRef().StartRef(),
    224                          TextFragmentDataAtStartRef().StartRawReason());
    225    default:
    226      break;
    227  }
    228 
    229  // Otherwise, return the start of the range.
    230  if (TextFragmentDataAtStartRef().GetStartReasonContent() !=
    231      TextFragmentDataAtStartRef().StartRef().GetContainer()) {
    232    if (NS_WARN_IF(!TextFragmentDataAtStartRef().GetStartReasonContent())) {
    233      return WSScanResult::Error();
    234    }
    235    // In this case, TextFragmentDataAtStartRef().StartRef().Offset() is not
    236    // meaningful.
    237    return WSScanResult(*this, WSScanResult::ScanDirection::Backward,
    238                        *TextFragmentDataAtStartRef().GetStartReasonContent(),
    239                        TextFragmentDataAtStartRef().StartRawReason());
    240  }
    241  if (NS_WARN_IF(!TextFragmentDataAtStartRef().StartRef().IsSet())) {
    242    return WSScanResult::Error();
    243  }
    244  return WSScanResult(*this, WSScanResult::ScanDirection::Backward,
    245                      TextFragmentDataAtStartRef().StartRef(),
    246                      TextFragmentDataAtStartRef().StartRawReason());
    247 }
    248 
    249 template <typename PT, typename CT>
    250 WSScanResult WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
    251    const EditorDOMPointBase<PT, CT>& aPoint) const {
    252  MOZ_ASSERT(aPoint.IsSet());
    253  MOZ_ASSERT(aPoint.IsInComposedDoc());
    254 
    255  if (MOZ_UNLIKELY(!aPoint.IsSet())) {
    256    return WSScanResult::Error();
    257  }
    258 
    259  // We may not be able to check editable state in uncomposed tree as expected.
    260  // For example, only some descendants in an editing host is temporarily
    261  // removed from the tree, they are not editable unless nested contenteditable
    262  // attribute is set to "true".
    263  if (MOZ_UNLIKELY(!aPoint.IsInComposedDoc())) {
    264    return WSScanResult(*this, WSScanResult::ScanDirection::Forward,
    265                        *aPoint.template ContainerAs<nsIContent>(),
    266                        WSType::InUncomposedDoc);
    267  }
    268 
    269  if (!TextFragmentDataAtStartRef().IsInitialized()) {
    270    return WSScanResult::Error();
    271  }
    272 
    273  // If the range has visible text and aPoint equals or is before the end of the
    274  // visible text, return inclusive next character in the text.
    275  const VisibleWhiteSpacesData& visibleWhiteSpaces =
    276      TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef();
    277  if (visibleWhiteSpaces.IsInitialized() &&
    278      aPoint.EqualsOrIsBefore(visibleWhiteSpaces.EndRef())) {
    279    // If the visible things are not editable, we shouldn't scan "editable"
    280    // things now.  Whether keep scanning editable things or not should be
    281    // considered by the caller.
    282    if (ScanOptions().contains(Option::OnlyEditableNodes) &&
    283        aPoint.GetChild() &&
    284        !HTMLEditUtils::IsSimplyEditableNode(*aPoint.GetChild())) {
    285      return WSScanResult(*this, WSScanResult::ScanDirection::Forward,
    286                          *aPoint.GetChild(), WSType::SpecialContent);
    287    }
    288    const auto atNextChar = GetInclusiveNextCharPoint<EditorDOMPoint>(aPoint);
    289    // When it's a non-empty text node, return it.
    290    if (atNextChar.IsSet() && !atNextChar.IsContainerEmpty()) {
    291      return WSScanResult(*this, WSScanResult::ScanDirection::Forward,
    292                          atNextChar,
    293                          !atNextChar.IsEndOfContainer() &&
    294                                  atNextChar.IsCharCollapsibleASCIISpaceOrNBSP()
    295                              ? WSType::CollapsibleWhiteSpaces
    296                          : !atNextChar.IsEndOfContainer() &&
    297                                  atNextChar.IsCharPreformattedNewLine()
    298                              ? WSType::PreformattedLineBreak
    299                              : WSType::NonCollapsibleCharacters);
    300    }
    301  }
    302 
    303  if (NS_WARN_IF(TextFragmentDataAtStartRef().EndRawReason() ==
    304                 WSType::UnexpectedError)) {
    305    return WSScanResult::Error();
    306  }
    307 
    308  MOZ_ASSERT_IF(!ScanOptions().contains(Option::StopAtComment),
    309                !Comment::FromNodeOrNull(
    310                    TextFragmentDataAtStartRef().GetEndReasonContent()));
    311  switch (TextFragmentDataAtStartRef().EndRawReason()) {
    312    case WSType::CollapsibleWhiteSpaces:
    313    case WSType::NonCollapsibleCharacters:
    314    case WSType::PreformattedLineBreak:
    315      MOZ_ASSERT(TextFragmentDataAtStartRef().StartRef().IsSet());
    316      // XXX: If we find the character at start of a text node and we
    317      // started scanning from preceding text node of it, some callers may want
    318      // to work with the point at end of the preceding text node instead of
    319      // start of the found text node.
    320      return WSScanResult(*this, WSScanResult::ScanDirection::Forward,
    321                          TextFragmentDataAtStartRef().EndRef(),
    322                          TextFragmentDataAtStartRef().EndRawReason());
    323    default:
    324      break;
    325  }
    326 
    327  // Otherwise, return the end of the range.
    328  if (TextFragmentDataAtStartRef().GetEndReasonContent() !=
    329      TextFragmentDataAtStartRef().EndRef().GetContainer()) {
    330    if (NS_WARN_IF(!TextFragmentDataAtStartRef().GetEndReasonContent())) {
    331      return WSScanResult::Error();
    332    }
    333    // In this case, TextFragmentDataAtStartRef().EndRef().Offset() is not
    334    // meaningful.
    335    return WSScanResult(*this, WSScanResult::ScanDirection::Forward,
    336                        *TextFragmentDataAtStartRef().GetEndReasonContent(),
    337                        TextFragmentDataAtStartRef().EndRawReason());
    338  }
    339  if (NS_WARN_IF(!TextFragmentDataAtStartRef().EndRef().IsSet())) {
    340    return WSScanResult::Error();
    341  }
    342  return WSScanResult(*this, WSScanResult::ScanDirection::Forward,
    343                      TextFragmentDataAtStartRef().EndRef(),
    344                      TextFragmentDataAtStartRef().EndRawReason());
    345 }
    346 
    347 // static
    348 template <typename EditorDOMPointType>
    349 EditorDOMPointType WSRunScanner::GetAfterLastVisiblePoint(
    350    Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    351    Text& aTextNode, const Element* aAncestorLimiter /* = nullptr */) {
    352  MOZ_ASSERT(!aOptions.contains(Option::ReferHTMLDefaultStyle));
    353 
    354  EditorDOMPoint atLastCharOfTextNode(
    355      &aTextNode, AssertedCast<uint32_t>(std::max<int64_t>(
    356                      static_cast<int64_t>(aTextNode.Length()) - 1, 0)));
    357  if (!atLastCharOfTextNode.IsContainerEmpty() &&
    358      !atLastCharOfTextNode.IsCharCollapsibleASCIISpace()) {
    359    return EditorDOMPointType::AtEndOf(aTextNode);
    360  }
    361  const TextFragmentData textFragmentData(aOptions, atLastCharOfTextNode,
    362                                          aAncestorLimiter);
    363  if (NS_WARN_IF(!textFragmentData.IsInitialized())) {
    364    return EditorDOMPointType();  // TODO: Make here return error with Err.
    365  }
    366  const EditorDOMRange& invisibleWhiteSpaceRange =
    367      textFragmentData.InvisibleTrailingWhiteSpaceRangeRef();
    368  if (!invisibleWhiteSpaceRange.IsPositioned() ||
    369      invisibleWhiteSpaceRange.Collapsed()) {
    370    return EditorDOMPointType::AtEndOf(aTextNode);
    371  }
    372  return invisibleWhiteSpaceRange.StartRef().To<EditorDOMPointType>();
    373 }
    374 
    375 // static
    376 template <typename EditorDOMPointType>
    377 EditorDOMPointType WSRunScanner::GetFirstVisiblePoint(
    378    Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    379    Text& aTextNode, const Element* aAncestorLimiter /* = nullptr */) {
    380  MOZ_ASSERT(!aOptions.contains(Option::ReferHTMLDefaultStyle));
    381 
    382  EditorDOMPoint atStartOfTextNode(&aTextNode, 0);
    383  if (!atStartOfTextNode.IsContainerEmpty() &&
    384      !atStartOfTextNode.IsCharCollapsibleASCIISpace()) {
    385    return atStartOfTextNode.To<EditorDOMPointType>();
    386  }
    387  const TextFragmentData textFragmentData(aOptions, atStartOfTextNode,
    388                                          aAncestorLimiter);
    389  if (NS_WARN_IF(!textFragmentData.IsInitialized())) {
    390    return EditorDOMPointType();  // TODO: Make here return error with Err.
    391  }
    392  const EditorDOMRange& invisibleWhiteSpaceRange =
    393      textFragmentData.InvisibleLeadingWhiteSpaceRangeRef();
    394  if (!invisibleWhiteSpaceRange.IsPositioned() ||
    395      invisibleWhiteSpaceRange.Collapsed()) {
    396    return atStartOfTextNode.To<EditorDOMPointType>();
    397  }
    398  return invisibleWhiteSpaceRange.EndRef().To<EditorDOMPointType>();
    399 }
    400 
    401 /*****************************************************************************
    402 * Implementation for new white-space normalizer
    403 *****************************************************************************/
    404 
    405 // static
    406 EditorDOMRangeInTexts
    407 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
    408    const TextFragmentData& aStart, const TextFragmentData& aEnd) {
    409  MOZ_ASSERT(aStart.ScanOptions() == aEnd.ScanOptions());
    410 
    411  // Corresponding to handling invisible white-spaces part of
    412  // `TextFragmentData::GetReplaceRangeDataAtEndOfDeletionRange()` and
    413  // `TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange()`
    414 
    415  MOZ_ASSERT(aStart.ScanStartRef().IsSetAndValid());
    416  MOZ_ASSERT(aEnd.ScanStartRef().IsSetAndValid());
    417  MOZ_ASSERT(aStart.ScanStartRef().EqualsOrIsBefore(aEnd.ScanStartRef()));
    418  MOZ_ASSERT(aStart.ScanStartRef().IsInTextNode());
    419  MOZ_ASSERT(aEnd.ScanStartRef().IsInTextNode());
    420 
    421  // XXX `GetReplaceRangeDataAtEndOfDeletionRange()` and
    422  //     `GetReplaceRangeDataAtStartOfDeletionRange()` use
    423  //     `GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt()` and
    424  //     `GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt()`.
    425  //     However, they are really odd as mentioned with "XXX" comments
    426  //     in them.  For the new white-space normalizer, we need to treat
    427  //     invisible white-spaces stricter because the legacy path handles
    428  //     white-spaces multiple times (e.g., calling `HTMLEditor::
    429  //     DeleteNodeIfInvisibleAndEditableTextNode()` later) and that hides
    430  //     the bug, but in the new path, we should stop doing same things
    431  //     multiple times for both performance and footprint.  Therefore,
    432  //     even though the result might be different in some edge cases,
    433  //     we should use clean path for now.  Perhaps, we should fix the odd
    434  //     cases before shipping `beforeinput` event in release channel.
    435 
    436  const EditorDOMRange& invisibleLeadingWhiteSpaceRange =
    437      aStart.InvisibleLeadingWhiteSpaceRangeRef();
    438  const EditorDOMRange& invisibleTrailingWhiteSpaceRange =
    439      aEnd.InvisibleTrailingWhiteSpaceRangeRef();
    440  const bool hasInvisibleLeadingWhiteSpaces =
    441      invisibleLeadingWhiteSpaceRange.IsPositioned() &&
    442      !invisibleLeadingWhiteSpaceRange.Collapsed();
    443  const bool hasInvisibleTrailingWhiteSpaces =
    444      invisibleLeadingWhiteSpaceRange != invisibleTrailingWhiteSpaceRange &&
    445      invisibleTrailingWhiteSpaceRange.IsPositioned() &&
    446      !invisibleTrailingWhiteSpaceRange.Collapsed();
    447 
    448  EditorDOMRangeInTexts result(aStart.ScanStartRef().AsInText(),
    449                               aEnd.ScanStartRef().AsInText());
    450  MOZ_ASSERT(result.IsPositionedAndValid());
    451  if (!hasInvisibleLeadingWhiteSpaces && !hasInvisibleTrailingWhiteSpaces) {
    452    return result;
    453  }
    454 
    455  MOZ_ASSERT_IF(
    456      hasInvisibleLeadingWhiteSpaces && hasInvisibleTrailingWhiteSpaces,
    457      invisibleLeadingWhiteSpaceRange.StartRef().IsBefore(
    458          invisibleTrailingWhiteSpaceRange.StartRef()));
    459  const EditorDOMPoint& aroundFirstInvisibleWhiteSpace =
    460      hasInvisibleLeadingWhiteSpaces
    461          ? invisibleLeadingWhiteSpaceRange.StartRef()
    462          : invisibleTrailingWhiteSpaceRange.StartRef();
    463  if (aroundFirstInvisibleWhiteSpace.IsBefore(result.StartRef())) {
    464    if (aroundFirstInvisibleWhiteSpace.IsInTextNode()) {
    465      result.SetStart(aroundFirstInvisibleWhiteSpace.AsInText());
    466      MOZ_ASSERT(result.IsPositionedAndValid());
    467    } else {
    468      const auto atFirstInvisibleWhiteSpace =
    469          hasInvisibleLeadingWhiteSpaces
    470              ? aStart.GetInclusiveNextCharPoint<EditorDOMPointInText>(
    471                    aroundFirstInvisibleWhiteSpace,
    472                    ShouldIgnoreNonEditableSiblingsOrDescendants(
    473                        aStart.ScanOptions()))
    474              : aEnd.GetInclusiveNextCharPoint<EditorDOMPointInText>(
    475                    aroundFirstInvisibleWhiteSpace,
    476                    ShouldIgnoreNonEditableSiblingsOrDescendants(
    477                        aEnd.ScanOptions()));
    478      MOZ_ASSERT(atFirstInvisibleWhiteSpace.IsSet());
    479      MOZ_ASSERT(
    480          atFirstInvisibleWhiteSpace.EqualsOrIsBefore(result.StartRef()));
    481      result.SetStart(atFirstInvisibleWhiteSpace);
    482      MOZ_ASSERT(result.IsPositionedAndValid());
    483    }
    484  }
    485  MOZ_ASSERT_IF(
    486      hasInvisibleLeadingWhiteSpaces && hasInvisibleTrailingWhiteSpaces,
    487      invisibleLeadingWhiteSpaceRange.EndRef().IsBefore(
    488          invisibleTrailingWhiteSpaceRange.EndRef()));
    489  const EditorDOMPoint& afterLastInvisibleWhiteSpace =
    490      hasInvisibleTrailingWhiteSpaces
    491          ? invisibleTrailingWhiteSpaceRange.EndRef()
    492          : invisibleLeadingWhiteSpaceRange.EndRef();
    493  if (afterLastInvisibleWhiteSpace.EqualsOrIsBefore(result.EndRef())) {
    494    MOZ_ASSERT(result.IsPositionedAndValid());
    495    return result;
    496  }
    497  if (afterLastInvisibleWhiteSpace.IsInTextNode()) {
    498    result.SetEnd(afterLastInvisibleWhiteSpace.AsInText());
    499    MOZ_ASSERT(result.IsPositionedAndValid());
    500    return result;
    501  }
    502  const auto atLastInvisibleWhiteSpace =
    503      hasInvisibleTrailingWhiteSpaces
    504          ? aEnd.GetPreviousCharPoint<EditorDOMPointInText>(
    505                afterLastInvisibleWhiteSpace,
    506                ShouldIgnoreNonEditableSiblingsOrDescendants(
    507                    aEnd.ScanOptions()))
    508          : aStart.GetPreviousCharPoint<EditorDOMPointInText>(
    509                afterLastInvisibleWhiteSpace,
    510                ShouldIgnoreNonEditableSiblingsOrDescendants(
    511                    aStart.ScanOptions()));
    512  MOZ_ASSERT(atLastInvisibleWhiteSpace.IsSet());
    513  MOZ_ASSERT(atLastInvisibleWhiteSpace.IsContainerEmpty() ||
    514             atLastInvisibleWhiteSpace.IsAtLastContent());
    515  MOZ_ASSERT(result.EndRef().EqualsOrIsBefore(atLastInvisibleWhiteSpace));
    516  result.SetEnd(atLastInvisibleWhiteSpace.IsEndOfContainer()
    517                    ? atLastInvisibleWhiteSpace
    518                    : atLastInvisibleWhiteSpace.NextPoint());
    519  MOZ_ASSERT(result.IsPositionedAndValid());
    520  return result;
    521 }
    522 
    523 // static
    524 Result<EditorDOMRangeInTexts, nsresult>
    525 WSRunScanner::GetRangeInTextNodesToBackspaceFrom(
    526    Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    527    const EditorDOMPoint& aPoint,
    528    const Element* aAncestorLimiter /* = nullptr */) {
    529  // Corresponding to computing delete range part of
    530  // `WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace()`
    531  MOZ_ASSERT(aPoint.IsSetAndValid());
    532  MOZ_ASSERT(!aOptions.contains(Option::ReferHTMLDefaultStyle));
    533 
    534  const TextFragmentData textFragmentDataAtCaret(aOptions, aPoint,
    535                                                 aAncestorLimiter);
    536  if (NS_WARN_IF(!textFragmentDataAtCaret.IsInitialized())) {
    537    return Err(NS_ERROR_FAILURE);
    538  }
    539  auto atPreviousChar =
    540      textFragmentDataAtCaret.GetPreviousCharPoint<EditorDOMPointInText>(
    541          aPoint, ShouldIgnoreNonEditableSiblingsOrDescendants(aOptions));
    542  if (!atPreviousChar.IsSet()) {
    543    return EditorDOMRangeInTexts();  // There is no content in the block.
    544  }
    545 
    546  // XXX When previous char point is in an empty text node, we do nothing,
    547  //     but this must look odd from point of user view.  We should delete
    548  //     something before aPoint.
    549  if (atPreviousChar.IsEndOfContainer()) {
    550    return EditorDOMRangeInTexts();
    551  }
    552 
    553  // Extend delete range if previous char is a low surrogate following
    554  // a high surrogate.
    555  EditorDOMPointInText atNextChar = atPreviousChar.NextPoint();
    556  if (!atPreviousChar.IsStartOfContainer()) {
    557    if (atPreviousChar.IsCharLowSurrogateFollowingHighSurrogate()) {
    558      atPreviousChar = atPreviousChar.PreviousPoint();
    559    }
    560    // If caret is in middle of a surrogate pair, delete the surrogate pair
    561    // (blink-compat).
    562    else if (atPreviousChar.IsCharHighSurrogateFollowedByLowSurrogate()) {
    563      atNextChar = atNextChar.NextPoint();
    564    }
    565  }
    566 
    567  // If previous char is an collapsible white-spaces, delete all adjacent
    568  // white-spaces which are collapsed together.
    569  EditorDOMRangeInTexts rangeToDelete;
    570  if (atPreviousChar.IsCharCollapsibleASCIISpace() ||
    571      atPreviousChar.IsCharPreformattedNewLineCollapsedWithWhiteSpaces()) {
    572    const auto startToDelete =
    573        textFragmentDataAtCaret
    574            .GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointInText>(
    575                atPreviousChar, nsIEditor::ePrevious,
    576                ShouldIgnoreNonEditableSiblingsOrDescendants(aOptions));
    577    if (!startToDelete.IsSet()) {
    578      NS_WARNING(
    579          "WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed");
    580      return Err(NS_ERROR_FAILURE);
    581    }
    582    const auto endToDelete =
    583        textFragmentDataAtCaret
    584            .GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>(
    585                atPreviousChar, nsIEditor::ePrevious,
    586                ShouldIgnoreNonEditableSiblingsOrDescendants(aOptions));
    587    if (!endToDelete.IsSet()) {
    588      NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed");
    589      return Err(NS_ERROR_FAILURE);
    590    }
    591    rangeToDelete = EditorDOMRangeInTexts(startToDelete, endToDelete);
    592  }
    593  // if previous char is not a collapsible white-space, remove it.
    594  else {
    595    rangeToDelete = EditorDOMRangeInTexts(atPreviousChar, atNextChar);
    596  }
    597 
    598  // If there is no removable and visible content, we should do nothing.
    599  if (rangeToDelete.Collapsed()) {
    600    return EditorDOMRangeInTexts();
    601  }
    602 
    603  // And also delete invisible white-spaces if they become visible.
    604  const TextFragmentData textFragmentDataAtStart =
    605      rangeToDelete.StartRef() != aPoint
    606          ? TextFragmentData(aOptions, rangeToDelete.StartRef(),
    607                             aAncestorLimiter)
    608          : textFragmentDataAtCaret;
    609  const TextFragmentData textFragmentDataAtEnd =
    610      rangeToDelete.EndRef() != aPoint
    611          ? TextFragmentData(aOptions, rangeToDelete.EndRef(), aAncestorLimiter)
    612          : textFragmentDataAtCaret;
    613  if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized()) ||
    614      NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) {
    615    return Err(NS_ERROR_FAILURE);
    616  }
    617  EditorDOMRangeInTexts extendedRangeToDelete =
    618      WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
    619          textFragmentDataAtStart, textFragmentDataAtEnd);
    620  MOZ_ASSERT(extendedRangeToDelete.IsPositionedAndValid());
    621  return extendedRangeToDelete.IsPositioned() ? extendedRangeToDelete
    622                                              : rangeToDelete;
    623 }
    624 
    625 // static
    626 Result<EditorDOMRangeInTexts, nsresult>
    627 WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom(
    628    Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    629    const EditorDOMPoint& aPoint,
    630    const Element* aAncestorLimiter /* = nullptr */) {
    631  // Corresponding to computing delete range part of
    632  // `WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace()`
    633  MOZ_ASSERT(aPoint.IsSetAndValid());
    634  MOZ_ASSERT(!aOptions.contains(Option::ReferHTMLDefaultStyle));
    635 
    636  const TextFragmentData textFragmentDataAtCaret(aOptions, aPoint,
    637                                                 aAncestorLimiter);
    638  if (NS_WARN_IF(!textFragmentDataAtCaret.IsInitialized())) {
    639    return Err(NS_ERROR_FAILURE);
    640  }
    641  auto atCaret =
    642      textFragmentDataAtCaret.GetInclusiveNextCharPoint<EditorDOMPointInText>(
    643          aPoint, ShouldIgnoreNonEditableSiblingsOrDescendants(aOptions));
    644  if (!atCaret.IsSet()) {
    645    return EditorDOMRangeInTexts();  // There is no content in the block.
    646  }
    647  // If caret is in middle of a surrogate pair, we should remove next
    648  // character (blink-compat).
    649  if (!atCaret.IsEndOfContainer() &&
    650      atCaret.IsCharLowSurrogateFollowingHighSurrogate()) {
    651    atCaret = atCaret.NextPoint();
    652  }
    653 
    654  // XXX When next char point is in an empty text node, we do nothing,
    655  //     but this must look odd from point of user view.  We should delete
    656  //     something after aPoint.
    657  if (atCaret.IsEndOfContainer()) {
    658    return EditorDOMRangeInTexts();
    659  }
    660 
    661  // Extend delete range if previous char is a low surrogate following
    662  // a high surrogate.
    663  EditorDOMPointInText atNextChar = atCaret.NextPoint();
    664  if (atCaret.IsCharHighSurrogateFollowedByLowSurrogate()) {
    665    atNextChar = atNextChar.NextPoint();
    666  }
    667 
    668  // If next char is a collapsible white-space, delete all adjacent white-spaces
    669  // which are collapsed together.
    670  EditorDOMRangeInTexts rangeToDelete;
    671  if (atCaret.IsCharCollapsibleASCIISpace() ||
    672      atCaret.IsCharPreformattedNewLineCollapsedWithWhiteSpaces()) {
    673    const auto startToDelete =
    674        textFragmentDataAtCaret
    675            .GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointInText>(
    676                atCaret, nsIEditor::eNext,
    677                ShouldIgnoreNonEditableSiblingsOrDescendants(aOptions));
    678    if (!startToDelete.IsSet()) {
    679      NS_WARNING(
    680          "WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed");
    681      return Err(NS_ERROR_FAILURE);
    682    }
    683    const EditorDOMPointInText endToDelete =
    684        textFragmentDataAtCaret
    685            .GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>(
    686                atCaret, nsIEditor::eNext,
    687                ShouldIgnoreNonEditableSiblingsOrDescendants(aOptions));
    688    if (!endToDelete.IsSet()) {
    689      NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed");
    690      return Err(NS_ERROR_FAILURE);
    691    }
    692    rangeToDelete = EditorDOMRangeInTexts(startToDelete, endToDelete);
    693  }
    694  // if next char is not a collapsible white-space, remove it.
    695  else {
    696    rangeToDelete = EditorDOMRangeInTexts(atCaret, atNextChar);
    697  }
    698 
    699  // If there is no removable and visible content, we should do nothing.
    700  if (rangeToDelete.Collapsed()) {
    701    return EditorDOMRangeInTexts();
    702  }
    703 
    704  // And also delete invisible white-spaces if they become visible.
    705  const TextFragmentData textFragmentDataAtStart =
    706      rangeToDelete.StartRef() != aPoint
    707          ? TextFragmentData(aOptions, rangeToDelete.StartRef(),
    708                             aAncestorLimiter)
    709          : textFragmentDataAtCaret;
    710  const TextFragmentData textFragmentDataAtEnd =
    711      rangeToDelete.EndRef() != aPoint
    712          ? TextFragmentData(aOptions, rangeToDelete.EndRef(), aAncestorLimiter)
    713          : textFragmentDataAtCaret;
    714  if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized()) ||
    715      NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) {
    716    return Err(NS_ERROR_FAILURE);
    717  }
    718  EditorDOMRangeInTexts extendedRangeToDelete =
    719      WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
    720          textFragmentDataAtStart, textFragmentDataAtEnd);
    721  MOZ_ASSERT(extendedRangeToDelete.IsPositionedAndValid());
    722  return extendedRangeToDelete.IsPositioned() ? extendedRangeToDelete
    723                                              : rangeToDelete;
    724 }
    725 
    726 // static
    727 EditorDOMRange WSRunScanner::GetRangesForDeletingAtomicContent(
    728    Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    729    const nsIContent& aAtomicContent,
    730    const Element* aAncestorLimiter /* = nullptr */) {
    731  MOZ_ASSERT(!aOptions.contains(Option::ReferHTMLDefaultStyle));
    732 
    733  if (aAtomicContent.IsHTMLElement(nsGkAtoms::br)) {
    734    // Preceding white-spaces should be preserved, but the following
    735    // white-spaces should be invisible around `<br>` element.
    736    const TextFragmentData textFragmentDataAfterBRElement(
    737        aOptions, EditorDOMPoint::After(aAtomicContent), aAncestorLimiter);
    738    if (NS_WARN_IF(!textFragmentDataAfterBRElement.IsInitialized())) {
    739      return EditorDOMRange();  // TODO: Make here return error with Err.
    740    }
    741    const EditorDOMRangeInTexts followingInvisibleWhiteSpaces =
    742        textFragmentDataAfterBRElement.GetNonCollapsedRangeInTexts(
    743            textFragmentDataAfterBRElement
    744                .InvisibleLeadingWhiteSpaceRangeRef());
    745    return followingInvisibleWhiteSpaces.IsPositioned() &&
    746                   !followingInvisibleWhiteSpaces.Collapsed()
    747               ? EditorDOMRange(
    748                     EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
    749                     followingInvisibleWhiteSpaces.EndRef())
    750               : EditorDOMRange(
    751                     EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
    752                     EditorDOMPoint::After(aAtomicContent));
    753  }
    754 
    755  if (!HTMLEditUtils::IsBlockElement(
    756          aAtomicContent, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
    757    // Both preceding and following white-spaces around it should be preserved
    758    // around inline elements like `<img>`.
    759    return EditorDOMRange(
    760        EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
    761        EditorDOMPoint::After(aAtomicContent));
    762  }
    763 
    764  // Both preceding and following white-spaces can be invisible around a
    765  // block element.
    766  const TextFragmentData textFragmentDataBeforeAtomicContent(
    767      aOptions, EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
    768      aAncestorLimiter);
    769  if (NS_WARN_IF(!textFragmentDataBeforeAtomicContent.IsInitialized())) {
    770    return EditorDOMRange();  // TODO: Make here return error with Err.
    771  }
    772  const EditorDOMRangeInTexts precedingInvisibleWhiteSpaces =
    773      textFragmentDataBeforeAtomicContent.GetNonCollapsedRangeInTexts(
    774          textFragmentDataBeforeAtomicContent
    775              .InvisibleTrailingWhiteSpaceRangeRef());
    776  const TextFragmentData textFragmentDataAfterAtomicContent(
    777      aOptions, EditorDOMPoint::After(aAtomicContent), aAncestorLimiter);
    778  if (NS_WARN_IF(!textFragmentDataAfterAtomicContent.IsInitialized())) {
    779    return EditorDOMRange();  // TODO: Make here return error with Err.
    780  }
    781  const EditorDOMRangeInTexts followingInvisibleWhiteSpaces =
    782      textFragmentDataAfterAtomicContent.GetNonCollapsedRangeInTexts(
    783          textFragmentDataAfterAtomicContent
    784              .InvisibleLeadingWhiteSpaceRangeRef());
    785  if (precedingInvisibleWhiteSpaces.StartRef().IsSet() &&
    786      followingInvisibleWhiteSpaces.EndRef().IsSet()) {
    787    return EditorDOMRange(precedingInvisibleWhiteSpaces.StartRef(),
    788                          followingInvisibleWhiteSpaces.EndRef());
    789  }
    790  if (precedingInvisibleWhiteSpaces.StartRef().IsSet()) {
    791    return EditorDOMRange(precedingInvisibleWhiteSpaces.StartRef(),
    792                          EditorDOMPoint::After(aAtomicContent));
    793  }
    794  if (followingInvisibleWhiteSpaces.EndRef().IsSet()) {
    795    return EditorDOMRange(
    796        EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
    797        followingInvisibleWhiteSpaces.EndRef());
    798  }
    799  return EditorDOMRange(
    800      EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
    801      EditorDOMPoint::After(aAtomicContent));
    802 }
    803 
    804 // static
    805 EditorDOMRange WSRunScanner::GetRangeForDeletingBlockElementBoundaries(
    806    Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    807    const Element& aLeftBlockElement, const Element& aRightBlockElement,
    808    const EditorDOMPoint& aPointContainingTheOtherBlock,
    809    const Element* aAncestorLimiter /* = nullptr */) {
    810  MOZ_ASSERT(&aLeftBlockElement != &aRightBlockElement);
    811  MOZ_ASSERT_IF(
    812      aPointContainingTheOtherBlock.IsSet(),
    813      aPointContainingTheOtherBlock.GetContainer() == &aLeftBlockElement ||
    814          aPointContainingTheOtherBlock.GetContainer() == &aRightBlockElement);
    815  MOZ_ASSERT_IF(
    816      aPointContainingTheOtherBlock.GetContainer() == &aLeftBlockElement,
    817      aRightBlockElement.IsInclusiveDescendantOf(
    818          aPointContainingTheOtherBlock.GetChild()));
    819  MOZ_ASSERT_IF(
    820      aPointContainingTheOtherBlock.GetContainer() == &aRightBlockElement,
    821      aLeftBlockElement.IsInclusiveDescendantOf(
    822          aPointContainingTheOtherBlock.GetChild()));
    823  MOZ_ASSERT_IF(
    824      !aPointContainingTheOtherBlock.IsSet(),
    825      !aRightBlockElement.IsInclusiveDescendantOf(&aLeftBlockElement));
    826  MOZ_ASSERT_IF(
    827      !aPointContainingTheOtherBlock.IsSet(),
    828      !aLeftBlockElement.IsInclusiveDescendantOf(&aRightBlockElement));
    829  MOZ_ASSERT_IF(!aPointContainingTheOtherBlock.IsSet(),
    830                EditorRawDOMPoint(const_cast<Element*>(&aLeftBlockElement))
    831                    .IsBefore(EditorRawDOMPoint(
    832                        const_cast<Element*>(&aRightBlockElement))));
    833  MOZ_ASSERT_IF(aAncestorLimiter,
    834                aLeftBlockElement.IsInclusiveDescendantOf(aAncestorLimiter));
    835  MOZ_ASSERT_IF(aAncestorLimiter,
    836                aRightBlockElement.IsInclusiveDescendantOf(aAncestorLimiter));
    837  MOZ_ASSERT_IF(aOptions.contains(Option::OnlyEditableNodes),
    838                const_cast<Element&>(aLeftBlockElement).GetEditingHost() ==
    839                    const_cast<Element&>(aRightBlockElement).GetEditingHost());
    840  MOZ_ASSERT(!aOptions.contains(Option::ReferHTMLDefaultStyle));
    841 
    842  EditorDOMRange range;
    843  // Include trailing invisible white-spaces in aLeftBlockElement.
    844  const TextFragmentData textFragmentDataAtEndOfLeftBlockElement(
    845      aOptions,
    846      aPointContainingTheOtherBlock.GetContainer() == &aLeftBlockElement
    847          ? aPointContainingTheOtherBlock
    848          : EditorDOMPoint::AtEndOf(const_cast<Element&>(aLeftBlockElement)),
    849      aAncestorLimiter);
    850  if (NS_WARN_IF(!textFragmentDataAtEndOfLeftBlockElement.IsInitialized())) {
    851    return EditorDOMRange();  // TODO: Make here return error with Err.
    852  }
    853  if (textFragmentDataAtEndOfLeftBlockElement.StartsFromInvisibleBRElement()) {
    854    // If the left block element ends with an invisible `<br>` element,
    855    // it'll be deleted (and it means there is no invisible trailing
    856    // white-spaces).  Therefore, the range should start from the invisible
    857    // `<br>` element.
    858    range.SetStart(EditorDOMPoint(
    859        textFragmentDataAtEndOfLeftBlockElement.StartReasonBRElementPtr()));
    860  } else {
    861    const EditorDOMRange& trailingWhiteSpaceRange =
    862        textFragmentDataAtEndOfLeftBlockElement
    863            .InvisibleTrailingWhiteSpaceRangeRef();
    864    if (trailingWhiteSpaceRange.StartRef().IsSet()) {
    865      range.SetStart(trailingWhiteSpaceRange.StartRef());
    866    } else {
    867      range.SetStart(textFragmentDataAtEndOfLeftBlockElement.ScanStartRef());
    868    }
    869  }
    870  // Include leading invisible white-spaces in aRightBlockElement.
    871  const TextFragmentData textFragmentDataAtStartOfRightBlockElement(
    872      aOptions,
    873      aPointContainingTheOtherBlock.GetContainer() == &aRightBlockElement &&
    874              !aPointContainingTheOtherBlock.IsEndOfContainer()
    875          ? aPointContainingTheOtherBlock.NextPoint()
    876          : EditorDOMPoint(const_cast<Element*>(&aRightBlockElement), 0u),
    877      aAncestorLimiter);
    878  if (NS_WARN_IF(!textFragmentDataAtStartOfRightBlockElement.IsInitialized())) {
    879    return EditorDOMRange();  // TODO: Make here return error with Err.
    880  }
    881  const EditorDOMRange& leadingWhiteSpaceRange =
    882      textFragmentDataAtStartOfRightBlockElement
    883          .InvisibleLeadingWhiteSpaceRangeRef();
    884  if (leadingWhiteSpaceRange.EndRef().IsSet()) {
    885    range.SetEnd(leadingWhiteSpaceRange.EndRef());
    886  } else {
    887    range.SetEnd(textFragmentDataAtStartOfRightBlockElement.ScanStartRef());
    888  }
    889  return range;
    890 }
    891 
    892 // static
    893 EditorDOMRange
    894 WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
    895    Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    896    const EditorDOMRange& aRange,
    897    const Element* aAncestorLimiter /* = nullptr */) {
    898  MOZ_ASSERT(aRange.IsPositionedAndValid());
    899  MOZ_ASSERT(aRange.EndRef().IsSetAndValid());
    900  MOZ_ASSERT(aRange.StartRef().IsSetAndValid());
    901  MOZ_ASSERT(!aOptions.contains(Option::ReferHTMLDefaultStyle));
    902 
    903  EditorDOMRange result;
    904  const TextFragmentData textFragmentDataAtStart(aOptions, aRange.StartRef(),
    905                                                 aAncestorLimiter);
    906  if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized())) {
    907    return EditorDOMRange();  // TODO: Make here return error with Err.
    908  }
    909  const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtStart =
    910      textFragmentDataAtStart.GetNonCollapsedRangeInTexts(
    911          textFragmentDataAtStart.InvisibleLeadingWhiteSpaceRangeRef());
    912  if (invisibleLeadingWhiteSpacesAtStart.IsPositioned() &&
    913      !invisibleLeadingWhiteSpacesAtStart.Collapsed()) {
    914    result.SetStart(invisibleLeadingWhiteSpacesAtStart.StartRef());
    915  } else {
    916    const EditorDOMRangeInTexts invisibleTrailingWhiteSpacesAtStart =
    917        textFragmentDataAtStart.GetNonCollapsedRangeInTexts(
    918            textFragmentDataAtStart.InvisibleTrailingWhiteSpaceRangeRef());
    919    if (invisibleTrailingWhiteSpacesAtStart.IsPositioned() &&
    920        !invisibleTrailingWhiteSpacesAtStart.Collapsed()) {
    921      MOZ_ASSERT(
    922          invisibleTrailingWhiteSpacesAtStart.StartRef().EqualsOrIsBefore(
    923              aRange.StartRef()));
    924      result.SetStart(invisibleTrailingWhiteSpacesAtStart.StartRef());
    925    }
    926    // If there is no invisible white-space and the line starts with a
    927    // text node, shrink the range to start of the text node.
    928    else if (!aRange.StartRef().IsInTextNode() &&
    929             (textFragmentDataAtStart.StartsFromBlockBoundary() ||
    930              textFragmentDataAtStart.StartsFromInlineEditingHostBoundary()) &&
    931             textFragmentDataAtStart.EndRef().IsInTextNode()) {
    932      result.SetStart(textFragmentDataAtStart.EndRef());
    933    }
    934  }
    935  if (!result.StartRef().IsSet()) {
    936    result.SetStart(aRange.StartRef());
    937  }
    938 
    939  const TextFragmentData textFragmentDataAtEnd(aOptions, aRange.EndRef(),
    940                                               aAncestorLimiter);
    941  if (NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) {
    942    return EditorDOMRange();  // TODO: Make here return error with Err.
    943  }
    944  const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd =
    945      textFragmentDataAtEnd.GetNonCollapsedRangeInTexts(
    946          textFragmentDataAtEnd.InvisibleTrailingWhiteSpaceRangeRef());
    947  if (invisibleLeadingWhiteSpacesAtEnd.IsPositioned() &&
    948      !invisibleLeadingWhiteSpacesAtEnd.Collapsed()) {
    949    result.SetEnd(invisibleLeadingWhiteSpacesAtEnd.EndRef());
    950  } else {
    951    const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd =
    952        textFragmentDataAtEnd.GetNonCollapsedRangeInTexts(
    953            textFragmentDataAtEnd.InvisibleLeadingWhiteSpaceRangeRef());
    954    if (invisibleLeadingWhiteSpacesAtEnd.IsPositioned() &&
    955        !invisibleLeadingWhiteSpacesAtEnd.Collapsed()) {
    956      MOZ_ASSERT(aRange.EndRef().EqualsOrIsBefore(
    957          invisibleLeadingWhiteSpacesAtEnd.EndRef()));
    958      result.SetEnd(invisibleLeadingWhiteSpacesAtEnd.EndRef());
    959    }
    960    // If there is no invisible white-space and the line ends with a text
    961    // node, shrink the range to end of the text node.
    962    else if (!aRange.EndRef().IsInTextNode() &&
    963             (textFragmentDataAtEnd.EndsByBlockBoundary() ||
    964              textFragmentDataAtEnd.EndsByInlineEditingHostBoundary()) &&
    965             textFragmentDataAtEnd.StartRef().IsInTextNode()) {
    966      result.SetEnd(EditorDOMPoint::AtEndOf(
    967          *textFragmentDataAtEnd.StartRef().ContainerAs<Text>()));
    968    }
    969  }
    970  if (!result.EndRef().IsSet()) {
    971    result.SetEnd(aRange.EndRef());
    972  }
    973  MOZ_ASSERT(result.IsPositionedAndValid());
    974  return result;
    975 }
    976 
    977 /******************************************************************************
    978 * Utilities for other things.
    979 ******************************************************************************/
    980 
    981 // static
    982 Result<bool, nsresult>
    983 WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
    984    Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    985    nsRange& aRange, const Element* aAncestorLimiter /* = nullptr */) {
    986  MOZ_ASSERT(aRange.IsPositioned());
    987  MOZ_ASSERT(!aRange.IsInAnySelection(),
    988             "Changing range in selection may cause running script");
    989  MOZ_ASSERT(!aOptions.contains(Option::ReferHTMLDefaultStyle));
    990 
    991  if (NS_WARN_IF(!aRange.GetStartContainer()) ||
    992      NS_WARN_IF(!aRange.GetEndContainer())) {
    993    return Err(NS_ERROR_FAILURE);
    994  }
    995 
    996  if (!aRange.GetStartContainer()->IsContent() ||
    997      !aRange.GetEndContainer()->IsContent()) {
    998    return false;
    999  }
   1000 
   1001  // If the range crosses a block boundary, we should do nothing for now
   1002  // because it hits a bug of inserting a padding `<br>` element after
   1003  // joining the blocks.
   1004  if (HTMLEditUtils::GetInclusiveAncestorElement(
   1005          *aRange.GetStartContainer()->AsContent(),
   1006          aOptions.contains(Option::OnlyEditableNodes)
   1007              ? HTMLEditUtils::ClosestEditableBlockElementExceptHRElement
   1008              : HTMLEditUtils::ClosestBlockElementExceptHRElement,
   1009          BlockInlineCheck::UseComputedDisplayStyle) !=
   1010      HTMLEditUtils::GetInclusiveAncestorElement(
   1011          *aRange.GetEndContainer()->AsContent(),
   1012          aOptions.contains(Option::OnlyEditableNodes)
   1013              ? HTMLEditUtils::ClosestEditableBlockElementExceptHRElement
   1014              : HTMLEditUtils::ClosestBlockElementExceptHRElement,
   1015          BlockInlineCheck::UseComputedDisplayStyle)) {
   1016    return false;
   1017  }
   1018 
   1019  nsIContent* startContent = nullptr;
   1020  if (aRange.GetStartContainer() && aRange.GetStartContainer()->IsText() &&
   1021      aRange.GetStartContainer()->AsText()->Length() == aRange.StartOffset()) {
   1022    // If next content is a visible `<br>` element, special inline content
   1023    // (e.g., `<img>`, non-editable text node, etc) or a block level void
   1024    // element like `<hr>`, the range should start with it.
   1025    const TextFragmentData textFragmentDataAtStart(
   1026        aOptions, EditorRawDOMPoint(aRange.StartRef()), aAncestorLimiter);
   1027    if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized())) {
   1028      return Err(NS_ERROR_FAILURE);
   1029    }
   1030    if (textFragmentDataAtStart.EndsByVisibleBRElement()) {
   1031      startContent = textFragmentDataAtStart.EndReasonBRElementPtr();
   1032    } else if (textFragmentDataAtStart.EndsBySpecialContent() ||
   1033               (textFragmentDataAtStart.EndsByOtherBlockElement() &&
   1034                !HTMLEditUtils::IsContainerNode(
   1035                    *textFragmentDataAtStart
   1036                         .EndReasonOtherBlockElementPtr()))) {
   1037      startContent = textFragmentDataAtStart.GetEndReasonContent();
   1038    }
   1039  }
   1040 
   1041  nsIContent* endContent = nullptr;
   1042  if (aRange.GetEndContainer() && aRange.GetEndContainer()->IsText() &&
   1043      !aRange.EndOffset()) {
   1044    // If previous content is a visible `<br>` element, special inline content
   1045    // (e.g., `<img>`, non-editable text node, etc) or a block level void
   1046    // element like `<hr>`, the range should end after it.
   1047    const TextFragmentData textFragmentDataAtEnd(
   1048        aOptions, EditorRawDOMPoint(aRange.EndRef()), aAncestorLimiter);
   1049    if (NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) {
   1050      return Err(NS_ERROR_FAILURE);
   1051    }
   1052    if (textFragmentDataAtEnd.StartsFromVisibleBRElement()) {
   1053      endContent = textFragmentDataAtEnd.StartReasonBRElementPtr();
   1054    } else if (textFragmentDataAtEnd.StartsFromSpecialContent() ||
   1055               (textFragmentDataAtEnd.StartsFromOtherBlockElement() &&
   1056                !HTMLEditUtils::IsContainerNode(
   1057                    *textFragmentDataAtEnd
   1058                         .StartReasonOtherBlockElementPtr()))) {
   1059      endContent = textFragmentDataAtEnd.GetStartReasonContent();
   1060    }
   1061  }
   1062 
   1063  if (!startContent && !endContent) {
   1064    return false;
   1065  }
   1066 
   1067  nsresult rv = aRange.SetStartAndEnd(
   1068      startContent ? RangeBoundary(
   1069                         startContent->GetParentNode(),
   1070                         startContent->GetPreviousSibling())  // at startContent
   1071                   : aRange.StartRef(),
   1072      endContent ? RangeBoundary(endContent->GetParentNode(),
   1073                                 endContent)  // after endContent
   1074                 : aRange.EndRef());
   1075  if (NS_FAILED(rv)) {
   1076    NS_WARNING("nsRange::SetStartAndEnd() failed");
   1077    return Err(rv);
   1078  }
   1079  return true;
   1080 }
   1081 
   1082 }  // namespace mozilla