tor-browser

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

WSRunScannerNestedClasses.cpp (63962B)


      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 "EditorUtils.h"
     10 #include "HTMLEditUtils.h"
     11 
     12 #include "mozilla/Assertions.h"
     13 #include "mozilla/dom/AncestorIterator.h"
     14 #include "mozilla/dom/CharacterDataBuffer.h"
     15 
     16 #include "nsCRT.h"
     17 #include "nsDebug.h"
     18 #include "nsIContent.h"
     19 #include "nsIContentInlines.h"
     20 
     21 namespace mozilla {
     22 
     23 using namespace dom;
     24 
     25 using AncestorType = HTMLEditUtils::AncestorType;
     26 using AncestorTypes = HTMLEditUtils::AncestorTypes;
     27 using LeafNodeType = HTMLEditUtils::LeafNodeType;
     28 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
     29 
     30 template WSRunScanner::TextFragmentData::TextFragmentData(Options,
     31                                                          const EditorDOMPoint&,
     32                                                          const Element*);
     33 template WSRunScanner::TextFragmentData::TextFragmentData(
     34    Options, const EditorRawDOMPoint&, const Element*);
     35 template WSRunScanner::TextFragmentData::TextFragmentData(
     36    Options, const EditorDOMPointInText&, const Element*);
     37 template WSRunScanner::TextFragmentData::TextFragmentData(
     38    Options, const EditorRawDOMPointInText&, const Element*);
     39 
     40 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
     41    WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint,
     42    const EditorDOMPoint&, Options, IgnoreNonEditableNodes, const nsIContent*);
     43 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
     44    WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint,
     45    const EditorRawDOMPoint&, Options, IgnoreNonEditableNodes,
     46    const nsIContent*);
     47 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
     48    WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint,
     49    const EditorDOMPointInText&, Options, IgnoreNonEditableNodes,
     50    const nsIContent*);
     51 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
     52    WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint,
     53    const EditorRawDOMPointInText&, Options, IgnoreNonEditableNodes,
     54    const nsIContent*);
     55 
     56 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
     57    WSRunScanner::TextFragmentData::GetPreviousCharPoint, const EditorDOMPoint&,
     58    Options, IgnoreNonEditableNodes, const nsIContent*);
     59 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
     60    WSRunScanner::TextFragmentData::GetPreviousCharPoint,
     61    const EditorRawDOMPoint&, Options, IgnoreNonEditableNodes,
     62    const nsIContent*);
     63 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
     64    WSRunScanner::TextFragmentData::GetPreviousCharPoint,
     65    const EditorDOMPointInText&, Options, IgnoreNonEditableNodes,
     66    const nsIContent*);
     67 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
     68    WSRunScanner::TextFragmentData::GetPreviousCharPoint,
     69    const EditorRawDOMPointInText&, Options, IgnoreNonEditableNodes,
     70    const nsIContent*);
     71 
     72 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
     73    WSRunScanner::TextFragmentData::GetEndOfCollapsibleASCIIWhiteSpaces,
     74    const EditorDOMPointInText&, nsIEditor::EDirection, Options,
     75    IgnoreNonEditableNodes, const nsIContent*);
     76 
     77 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
     78    WSRunScanner::TextFragmentData::GetFirstASCIIWhiteSpacePointCollapsedTo,
     79    const EditorDOMPointInText&, nsIEditor::EDirection, Options,
     80    IgnoreNonEditableNodes, const nsIContent*);
     81 
     82 // FIXME: I think the scanner should not cross the <button> element boundaries.
     83 constexpr static const AncestorTypes kScanAnyRootAncestorTypes = {
     84    // If the point is in a block, we need to scan only in the block
     85    AncestorType::ClosestBlockElement,
     86    // So, we want a root element of the (shadow) tree root element of the
     87    // point if there is no parent block
     88    AncestorType::ReturnAncestorLimiterIfNoProperAncestor,
     89    // Basically, given point shouldn't be a void element, so, ignore
     90    // ancestor
     91    // void elements
     92    AncestorType::IgnoreHRElement};
     93 constexpr static const AncestorTypes kScanEditableRootAncestorTypes = {
     94    // Only editable elements
     95    AncestorType::EditableElement,
     96    // And the others are same as kScanAnyRootAncestorTypes
     97    AncestorType::ClosestBlockElement,
     98    AncestorType::ReturnAncestorLimiterIfNoProperAncestor,
     99    AncestorType::IgnoreHRElement};
    100 
    101 template <typename EditorDOMPointType>
    102 WSRunScanner::TextFragmentData::TextFragmentData(
    103    Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    104    const EditorDOMPointType& aPoint,
    105    const Element* aAncestorLimiter /* = nullptr */)
    106    : mAncestorLimiter(aAncestorLimiter), mOptions(aOptions) {
    107  if (NS_WARN_IF(!aPoint.IsInContentNodeAndValidInComposedDoc()) ||
    108      NS_WARN_IF(!aPoint.GetContainerOrContainerParentElement())) {
    109    // We don't need to support composing in uncomposed tree.
    110    return;
    111  }
    112 
    113  MOZ_ASSERT_IF(
    114      aAncestorLimiter,
    115      aPoint.template ContainerAs<nsIContent>()->IsInclusiveDescendantOf(
    116          aAncestorLimiter));
    117 
    118  mScanStartPoint = aPoint.template To<EditorDOMPoint>();
    119  const Element* const
    120      editableBlockElementOrInlineEditingHostOrNonEditableRootElement =
    121          HTMLEditUtils::GetInclusiveAncestorElement(
    122              *mScanStartPoint.ContainerAs<nsIContent>(),
    123              mOptions.contains(Option::OnlyEditableNodes)
    124                  ? kScanEditableRootAncestorTypes
    125                  : kScanAnyRootAncestorTypes,
    126              ReferredHTMLDefaultStyle()
    127                  ? BlockInlineCheck::UseHTMLDefaultStyle
    128                  : BlockInlineCheck::UseComputedDisplayOutsideStyle,
    129              aAncestorLimiter);
    130  if (NS_WARN_IF(
    131          !editableBlockElementOrInlineEditingHostOrNonEditableRootElement)) {
    132    return;
    133  }
    134  mStart = BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
    135      mOptions, mScanStartPoint, &mNBSPData,
    136      *editableBlockElementOrInlineEditingHostOrNonEditableRootElement);
    137  MOZ_ASSERT_IF(mStart.IsNonCollapsibleCharacters(),
    138                !mStart.PointRef().IsPreviousCharPreformattedNewLine());
    139  MOZ_ASSERT_IF(mStart.IsPreformattedLineBreak(),
    140                mStart.PointRef().IsPreviousCharPreformattedNewLine());
    141  mEnd = BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
    142      mOptions, mScanStartPoint, &mNBSPData,
    143      *editableBlockElementOrInlineEditingHostOrNonEditableRootElement);
    144  MOZ_ASSERT_IF(mEnd.IsNonCollapsibleCharacters(),
    145                !mEnd.PointRef().IsCharPreformattedNewLine());
    146  MOZ_ASSERT_IF(mEnd.IsPreformattedLineBreak(),
    147                mEnd.PointRef().IsCharPreformattedNewLine());
    148 }
    149 
    150 // static
    151 template <typename EditorDOMPointType>
    152 Maybe<WSRunScanner::TextFragmentData::BoundaryData> WSRunScanner::
    153    TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(
    154        const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData) {
    155  MOZ_ASSERT(aPoint.IsSetAndValid());
    156  MOZ_DIAGNOSTIC_ASSERT(aPoint.IsInTextNode());
    157 
    158  const bool isWhiteSpaceCollapsible = !EditorUtils::IsWhiteSpacePreformatted(
    159      *aPoint.template ContainerAs<Text>());
    160  const bool isNewLineCollapsible =
    161      isWhiteSpaceCollapsible &&
    162      !EditorUtils::IsNewLinePreformatted(*aPoint.template ContainerAs<Text>());
    163  const bool isNewLineLineBreak =
    164      EditorUtils::IsNewLinePreformatted(*aPoint.template ContainerAs<Text>());
    165  const CharacterDataBuffer& characterDataBuffer =
    166      aPoint.template ContainerAs<Text>()->DataBuffer();
    167  for (uint32_t i = std::min(aPoint.Offset(), characterDataBuffer.GetLength());
    168       i; i--) {
    169    WSType wsTypeOfNonCollapsibleChar;
    170    switch (characterDataBuffer.CharAt(i - 1)) {
    171      case HTMLEditUtils::kSpace:
    172      case HTMLEditUtils::kCarriageReturn:
    173      case HTMLEditUtils::kTab:
    174        if (isWhiteSpaceCollapsible) {
    175          continue;  // collapsible white-space or invisible white-space.
    176        }
    177        // preformatted white-space.
    178        wsTypeOfNonCollapsibleChar = WSType::NonCollapsibleCharacters;
    179        break;
    180      case HTMLEditUtils::kNewLine:
    181        if (isNewLineCollapsible) {
    182          continue;  // collapsible linefeed.
    183        }
    184        // preformatted linefeed or replaced with a non-collapsible white-space.
    185        wsTypeOfNonCollapsibleChar = isNewLineLineBreak
    186                                         ? WSType::PreformattedLineBreak
    187                                         : WSType::NonCollapsibleCharacters;
    188        break;
    189      case HTMLEditUtils::kNBSP:
    190        if (isWhiteSpaceCollapsible) {
    191          if (aNBSPData) {
    192            aNBSPData->NotifyNBSP(
    193                EditorDOMPointInText(aPoint.template ContainerAs<Text>(),
    194                                     i - 1),
    195                NoBreakingSpaceData::Scanning::Backward);
    196          }
    197          continue;
    198        }
    199        // NBSP is never converted from collapsible white-space/linefeed.
    200        wsTypeOfNonCollapsibleChar = WSType::NonCollapsibleCharacters;
    201        break;
    202      default:
    203        MOZ_ASSERT(!nsCRT::IsAsciiSpace(characterDataBuffer.CharAt(i - 1)));
    204        wsTypeOfNonCollapsibleChar = WSType::NonCollapsibleCharacters;
    205        break;
    206    }
    207 
    208    return Some(BoundaryData(
    209        EditorDOMPoint(aPoint.template ContainerAs<Text>(), i),
    210        *aPoint.template ContainerAs<Text>(), wsTypeOfNonCollapsibleChar));
    211  }
    212 
    213  return Nothing();
    214 }
    215 
    216 // static
    217 template <typename EditorDOMPointType>
    218 WSRunScanner::TextFragmentData::BoundaryData WSRunScanner::TextFragmentData::
    219    BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
    220        Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    221        const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData,
    222        const Element& aAncestorLimiter) {
    223  MOZ_ASSERT(aPoint.IsSetAndValid());
    224  MOZ_ASSERT_IF(aOptions.contains(Option::OnlyEditableNodes),
    225                // FIXME: Both values should be true here.
    226                HTMLEditUtils::IsSimplyEditableNode(*aPoint.GetContainer()) ==
    227                    HTMLEditUtils::IsSimplyEditableNode(aAncestorLimiter));
    228 
    229  if (aPoint.IsInTextNode() && !aPoint.IsStartOfContainer()) {
    230    Maybe<BoundaryData> startInTextNode =
    231        BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(aPoint,
    232                                                               aNBSPData);
    233    if (startInTextNode.isSome()) {
    234      return startInTextNode.ref();
    235    }
    236    // The text node does not have visible character, let's keep scanning
    237    // preceding nodes.
    238    return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
    239        aOptions, EditorDOMPoint(aPoint.template ContainerAs<Text>(), 0),
    240        aNBSPData, aAncestorLimiter);
    241  }
    242 
    243  const BlockInlineCheck blockInlineCheck =
    244      aOptions.contains(Option::ReferHTMLDefaultStyle)
    245          ? BlockInlineCheck::UseHTMLDefaultStyle
    246          : BlockInlineCheck::Auto;
    247  // Then, we need to check previous leaf node.
    248  const auto leafNodeTypes = [&]() -> LeafNodeTypes {
    249    auto types = aOptions.contains(Option::OnlyEditableNodes)
    250                     ? LeafNodeTypes{LeafNodeType::LeafNodeOrNonEditableNode}
    251                     : LeafNodeTypes{LeafNodeType::OnlyLeafNode};
    252    if (aOptions.contains(Option::StopAtComment)) {
    253      types += LeafNodeType::TreatCommentAsLeafNode;
    254    }
    255    return types;
    256  }();
    257  nsIContent* previousLeafContentOrBlock =
    258      HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
    259          aPoint, leafNodeTypes, blockInlineCheck, &aAncestorLimiter);
    260  if (!previousLeafContentOrBlock) {
    261    // No previous content means that we reached the aAncestorLimiter boundary.
    262    return BoundaryData(
    263        aPoint, const_cast<Element&>(aAncestorLimiter),
    264        HTMLEditUtils::IsBlockElement(
    265            aAncestorLimiter, UseComputedDisplayStyleIfAuto(blockInlineCheck))
    266            ? WSType::CurrentBlockBoundary
    267            : WSType::InlineEditingHostBoundary);
    268  }
    269 
    270  if (HTMLEditUtils::IsBlockElement(
    271          *previousLeafContentOrBlock,
    272          UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck))) {
    273    return BoundaryData(aPoint, *previousLeafContentOrBlock,
    274                        WSType::OtherBlockBoundary);
    275  }
    276 
    277  if (!previousLeafContentOrBlock->IsText() ||
    278      (aOptions.contains(Option::OnlyEditableNodes) &&
    279       HTMLEditUtils::IsSimplyEditableNode(*previousLeafContentOrBlock) !=
    280           HTMLEditUtils::IsSimplyEditableNode(aAncestorLimiter))) {
    281    // it's a break or a special node, like <img>, that is not a block and
    282    // not a break but still serves as a terminator to ws runs.
    283    return BoundaryData(aPoint, *previousLeafContentOrBlock,
    284                        previousLeafContentOrBlock->IsHTMLElement(nsGkAtoms::br)
    285                            ? WSType::BRElement
    286                            : WSType::SpecialContent);
    287  }
    288 
    289  if (!previousLeafContentOrBlock->AsText()->TextLength()) {
    290    // If it's an empty text node, keep looking for its previous leaf content.
    291    // Note that even if the empty text node is preformatted, we should keep
    292    // looking for the previous one.
    293    return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
    294        aOptions, EditorDOMPointInText(previousLeafContentOrBlock->AsText(), 0),
    295        aNBSPData, aAncestorLimiter);
    296  }
    297 
    298  Maybe<BoundaryData> startInTextNode =
    299      BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(
    300          EditorDOMPointInText::AtEndOf(*previousLeafContentOrBlock->AsText()),
    301          aNBSPData);
    302  if (startInTextNode.isSome()) {
    303    return startInTextNode.ref();
    304  }
    305 
    306  // The text node does not have visible character, let's keep scanning
    307  // preceding nodes.
    308  return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
    309      aOptions, EditorDOMPointInText(previousLeafContentOrBlock->AsText(), 0),
    310      aNBSPData, aAncestorLimiter);
    311 }
    312 
    313 // static
    314 template <typename EditorDOMPointType>
    315 Maybe<WSRunScanner::TextFragmentData::BoundaryData> WSRunScanner::
    316    TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(
    317        const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData) {
    318  MOZ_ASSERT(aPoint.IsSetAndValid());
    319  MOZ_DIAGNOSTIC_ASSERT(aPoint.IsInTextNode());
    320 
    321  const bool isWhiteSpaceCollapsible = !EditorUtils::IsWhiteSpacePreformatted(
    322      *aPoint.template ContainerAs<Text>());
    323  const bool isNewLineCollapsible =
    324      isWhiteSpaceCollapsible &&
    325      !EditorUtils::IsNewLinePreformatted(*aPoint.template ContainerAs<Text>());
    326  const bool isNewLineLineBreak =
    327      EditorUtils::IsNewLinePreformatted(*aPoint.template ContainerAs<Text>());
    328  const CharacterDataBuffer& characterDataBuffer =
    329      aPoint.template ContainerAs<Text>()->DataBuffer();
    330  for (uint32_t i = aPoint.Offset(); i < characterDataBuffer.GetLength(); i++) {
    331    WSType wsTypeOfNonCollapsibleChar;
    332    switch (characterDataBuffer.CharAt(i)) {
    333      case HTMLEditUtils::kSpace:
    334      case HTMLEditUtils::kCarriageReturn:
    335      case HTMLEditUtils::kTab:
    336        if (isWhiteSpaceCollapsible) {
    337          continue;  // collapsible white-space or invisible white-space.
    338        }
    339        // preformatted white-space.
    340        wsTypeOfNonCollapsibleChar = WSType::NonCollapsibleCharacters;
    341        break;
    342      case HTMLEditUtils::kNewLine:
    343        if (isNewLineCollapsible) {
    344          continue;  // collapsible linefeed.
    345        }
    346        // preformatted linefeed or replaced with a non-collapsible white-space.
    347        wsTypeOfNonCollapsibleChar = isNewLineLineBreak
    348                                         ? WSType::PreformattedLineBreak
    349                                         : WSType::NonCollapsibleCharacters;
    350        break;
    351      case HTMLEditUtils::kNBSP:
    352        if (isWhiteSpaceCollapsible) {
    353          if (aNBSPData) {
    354            aNBSPData->NotifyNBSP(
    355                EditorDOMPointInText(aPoint.template ContainerAs<Text>(), i),
    356                NoBreakingSpaceData::Scanning::Forward);
    357          }
    358          continue;
    359        }
    360        // NBSP is never converted from collapsible white-space/linefeed.
    361        wsTypeOfNonCollapsibleChar = WSType::NonCollapsibleCharacters;
    362        break;
    363      default:
    364        MOZ_ASSERT(!nsCRT::IsAsciiSpace(characterDataBuffer.CharAt(i)));
    365        wsTypeOfNonCollapsibleChar = WSType::NonCollapsibleCharacters;
    366        break;
    367    }
    368 
    369    return Some(BoundaryData(
    370        EditorDOMPoint(aPoint.template ContainerAs<Text>(), i),
    371        *aPoint.template ContainerAs<Text>(), wsTypeOfNonCollapsibleChar));
    372  }
    373 
    374  return Nothing();
    375 }
    376 
    377 // static
    378 template <typename EditorDOMPointType>
    379 WSRunScanner::TextFragmentData::BoundaryData
    380 WSRunScanner::TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
    381    Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    382    const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData,
    383    const Element& aAncestorLimiter) {
    384  MOZ_ASSERT(aPoint.IsSetAndValid());
    385  MOZ_ASSERT_IF(aOptions.contains(Option::OnlyEditableNodes),
    386                // FIXME: Both values should be true here.
    387                HTMLEditUtils::IsSimplyEditableNode(*aPoint.GetContainer()) ==
    388                    HTMLEditUtils::IsSimplyEditableNode(aAncestorLimiter));
    389 
    390  if (aPoint.IsInTextNode() && !aPoint.IsEndOfContainer()) {
    391    Maybe<BoundaryData> endInTextNode =
    392        BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(aPoint, aNBSPData);
    393    if (endInTextNode.isSome()) {
    394      return endInTextNode.ref();
    395    }
    396    // The text node does not have visible character, let's keep scanning
    397    // following nodes.
    398    return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
    399        aOptions,
    400        EditorDOMPointInText::AtEndOf(*aPoint.template ContainerAs<Text>()),
    401        aNBSPData, aAncestorLimiter);
    402  }
    403 
    404  const BlockInlineCheck blockInlineCheck =
    405      aOptions.contains(Option::ReferHTMLDefaultStyle)
    406          ? BlockInlineCheck::UseHTMLDefaultStyle
    407          : BlockInlineCheck::Auto;
    408 
    409  // Then, we need to check next leaf node.
    410  const auto leafNodeTypes = [&]() -> LeafNodeTypes {
    411    auto types = aOptions.contains(Option::OnlyEditableNodes)
    412                     ? LeafNodeTypes{LeafNodeType::LeafNodeOrNonEditableNode}
    413                     : LeafNodeTypes{LeafNodeType::OnlyLeafNode};
    414    if (aOptions.contains(Option::StopAtComment)) {
    415      types += LeafNodeType::TreatCommentAsLeafNode;
    416    }
    417    return types;
    418  }();
    419  nsIContent* nextLeafContentOrBlock =
    420      HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
    421          aPoint, leafNodeTypes, blockInlineCheck, &aAncestorLimiter);
    422  if (!nextLeafContentOrBlock) {
    423    // No next content means that we reached aAncestorLimiter boundary.
    424    return BoundaryData(
    425        aPoint.template To<EditorDOMPoint>(),
    426        const_cast<Element&>(aAncestorLimiter),
    427        HTMLEditUtils::IsBlockElement(
    428            aAncestorLimiter, UseComputedDisplayStyleIfAuto(blockInlineCheck))
    429            ? WSType::CurrentBlockBoundary
    430            : WSType::InlineEditingHostBoundary);
    431  }
    432 
    433  if (HTMLEditUtils::IsBlockElement(
    434          *nextLeafContentOrBlock,
    435          UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck))) {
    436    // we encountered a new block.  therefore no more ws.
    437    return BoundaryData(aPoint, *nextLeafContentOrBlock,
    438                        WSType::OtherBlockBoundary);
    439  }
    440 
    441  if (!nextLeafContentOrBlock->IsText() ||
    442      (aOptions.contains(Option::OnlyEditableNodes) &&
    443       HTMLEditUtils::IsSimplyEditableNode(*nextLeafContentOrBlock) !=
    444           HTMLEditUtils::IsSimplyEditableNode(aAncestorLimiter))) {
    445    // we encountered a break or a special node, like <img>,
    446    // that is not a block and not a break but still
    447    // serves as a terminator to ws runs.
    448    return BoundaryData(aPoint, *nextLeafContentOrBlock,
    449                        nextLeafContentOrBlock->IsHTMLElement(nsGkAtoms::br)
    450                            ? WSType::BRElement
    451                            : WSType::SpecialContent);
    452  }
    453 
    454  if (!nextLeafContentOrBlock->AsText()->DataBuffer().GetLength()) {
    455    // If it's an empty text node, keep looking for its next leaf content.
    456    // Note that even if the empty text node is preformatted, we should keep
    457    // looking for the next one.
    458    return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
    459        aOptions, EditorDOMPointInText(nextLeafContentOrBlock->AsText(), 0),
    460        aNBSPData, aAncestorLimiter);
    461  }
    462 
    463  Maybe<BoundaryData> endInTextNode =
    464      BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(
    465          EditorDOMPointInText(nextLeafContentOrBlock->AsText(), 0), aNBSPData);
    466  if (endInTextNode.isSome()) {
    467    return endInTextNode.ref();
    468  }
    469 
    470  // The text node does not have visible character, let's keep scanning
    471  // following nodes.
    472  return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
    473      aOptions,
    474      EditorDOMPointInText::AtEndOf(*nextLeafContentOrBlock->AsText()),
    475      aNBSPData, aAncestorLimiter);
    476 }
    477 
    478 const EditorDOMRange&
    479 WSRunScanner::TextFragmentData::InvisibleLeadingWhiteSpaceRangeRef() const {
    480  if (mLeadingWhiteSpaceRange.isSome()) {
    481    return mLeadingWhiteSpaceRange.ref();
    482  }
    483 
    484  // If it's start of line, there is no invisible leading white-spaces.
    485  if (!StartsFromHardLineBreak() && !StartsFromInlineEditingHostBoundary()) {
    486    mLeadingWhiteSpaceRange.emplace();
    487    return mLeadingWhiteSpaceRange.ref();
    488  }
    489 
    490  // If there is no NBSP, all of the given range is leading white-spaces.
    491  // Note that this result may be collapsed if there is no leading white-spaces.
    492  if (!mNBSPData.FoundNBSP()) {
    493    MOZ_ASSERT(mStart.PointRef().IsSet() || mEnd.PointRef().IsSet());
    494    mLeadingWhiteSpaceRange.emplace(mStart.PointRef(), mEnd.PointRef());
    495    return mLeadingWhiteSpaceRange.ref();
    496  }
    497 
    498  MOZ_ASSERT(mNBSPData.LastPointRef().IsSetAndValid());
    499 
    500  // Even if the first NBSP is the start, i.e., there is no invisible leading
    501  // white-space, return collapsed range.
    502  mLeadingWhiteSpaceRange.emplace(mStart.PointRef(), mNBSPData.FirstPointRef());
    503  return mLeadingWhiteSpaceRange.ref();
    504 }
    505 
    506 const EditorDOMRange&
    507 WSRunScanner::TextFragmentData::InvisibleTrailingWhiteSpaceRangeRef() const {
    508  if (mTrailingWhiteSpaceRange.isSome()) {
    509    return mTrailingWhiteSpaceRange.ref();
    510  }
    511 
    512  // If it's not immediately before a block boundary, there is no invisible
    513  // trailing white-spaces.  Note that a collapsible white-space before a <br>
    514  // element or a preformatted linefeed is visible.
    515  if (!EndsByBlockBoundary() && !EndsByInlineEditingHostBoundary()) {
    516    mTrailingWhiteSpaceRange.emplace();
    517    return mTrailingWhiteSpaceRange.ref();
    518  }
    519 
    520  // If there is no NBSP, all of the given range is trailing white-spaces.
    521  // Note that this result may be collapsed if there is no trailing white-
    522  // spaces.
    523  if (!mNBSPData.FoundNBSP()) {
    524    MOZ_ASSERT(mStart.PointRef().IsSet() || mEnd.PointRef().IsSet());
    525    mTrailingWhiteSpaceRange.emplace(mStart.PointRef(), mEnd.PointRef());
    526    return mTrailingWhiteSpaceRange.ref();
    527  }
    528 
    529  MOZ_ASSERT(mNBSPData.LastPointRef().IsSetAndValid());
    530 
    531  // If last NBSP is immediately before the end, there is no trailing white-
    532  // spaces.
    533  if (mEnd.PointRef().IsSet() &&
    534      mNBSPData.LastPointRef().GetContainer() ==
    535          mEnd.PointRef().GetContainer() &&
    536      mNBSPData.LastPointRef().Offset() == mEnd.PointRef().Offset() - 1) {
    537    mTrailingWhiteSpaceRange.emplace();
    538    return mTrailingWhiteSpaceRange.ref();
    539  }
    540 
    541  // Otherwise, the may be some trailing white-spaces.
    542  MOZ_ASSERT(!mNBSPData.LastPointRef().IsEndOfContainer());
    543  mTrailingWhiteSpaceRange.emplace(mNBSPData.LastPointRef().NextPoint(),
    544                                   mEnd.PointRef());
    545  return mTrailingWhiteSpaceRange.ref();
    546 }
    547 
    548 EditorDOMRangeInTexts
    549 WSRunScanner::TextFragmentData::GetNonCollapsedRangeInTexts(
    550    const EditorDOMRange& aRange) const {
    551  if (!aRange.IsPositioned()) {
    552    return EditorDOMRangeInTexts();
    553  }
    554  if (aRange.Collapsed()) {
    555    // If collapsed, we can do nothing.
    556    return EditorDOMRangeInTexts();
    557  }
    558  if (aRange.IsInTextNodes()) {
    559    // Note that this may return a range which don't include any invisible
    560    // white-spaces due to empty text nodes.
    561    return aRange.GetAsInTexts();
    562  }
    563 
    564  const auto firstPoint =
    565      aRange.StartRef().IsInTextNode()
    566          ? aRange.StartRef().AsInText()
    567          : GetInclusiveNextCharPoint<EditorDOMPointInText>(
    568                aRange.StartRef(),
    569                ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions));
    570  if (!firstPoint.IsSet()) {
    571    return EditorDOMRangeInTexts();
    572  }
    573  EditorDOMPointInText endPoint;
    574  if (aRange.EndRef().IsInTextNode()) {
    575    endPoint = aRange.EndRef().AsInText();
    576  } else {
    577    // FYI: GetPreviousCharPoint() returns last character's point of preceding
    578    //      text node if it's not empty, but we need end of the text node here.
    579    endPoint = GetPreviousCharPoint<EditorDOMPointInText>(
    580        aRange.EndRef(),
    581        ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions));
    582    if (endPoint.IsSet() && endPoint.IsAtLastContent()) {
    583      MOZ_ALWAYS_TRUE(endPoint.AdvanceOffset());
    584    }
    585  }
    586  if (!endPoint.IsSet() || firstPoint == endPoint) {
    587    return EditorDOMRangeInTexts();
    588  }
    589  return EditorDOMRangeInTexts(firstPoint, endPoint);
    590 }
    591 
    592 const WSRunScanner::VisibleWhiteSpacesData&
    593 WSRunScanner::TextFragmentData::VisibleWhiteSpacesDataRef() const {
    594  if (mVisibleWhiteSpacesData.isSome()) {
    595    return mVisibleWhiteSpacesData.ref();
    596  }
    597 
    598  {
    599    // If all things are obviously visible, we can return range for all of the
    600    // things quickly.
    601    const bool mayHaveInvisibleLeadingSpace =
    602        !StartsFromNonCollapsibleCharacters() && !StartsFromSpecialContent();
    603    const bool mayHaveInvisibleTrailingWhiteSpace =
    604        !EndsByNonCollapsibleCharacters() && !EndsBySpecialContent() &&
    605        !EndsByBRElement() && !EndsByInvisiblePreformattedLineBreak();
    606 
    607    if (!mayHaveInvisibleLeadingSpace && !mayHaveInvisibleTrailingWhiteSpace) {
    608      VisibleWhiteSpacesData visibleWhiteSpaces;
    609      if (mStart.PointRef().IsSet()) {
    610        visibleWhiteSpaces.SetStartPoint(mStart.PointRef());
    611      }
    612      visibleWhiteSpaces.SetStartFrom(mStart.RawReason());
    613      if (mEnd.PointRef().IsSet()) {
    614        visibleWhiteSpaces.SetEndPoint(mEnd.PointRef());
    615      }
    616      visibleWhiteSpaces.SetEndBy(mEnd.RawReason());
    617      mVisibleWhiteSpacesData.emplace(visibleWhiteSpaces);
    618      return mVisibleWhiteSpacesData.ref();
    619    }
    620  }
    621 
    622  // If all of the range is invisible leading or trailing white-spaces,
    623  // there is no visible content.
    624  const EditorDOMRange& leadingWhiteSpaceRange =
    625      InvisibleLeadingWhiteSpaceRangeRef();
    626  const bool maybeHaveLeadingWhiteSpaces =
    627      leadingWhiteSpaceRange.StartRef().IsSet() ||
    628      leadingWhiteSpaceRange.EndRef().IsSet();
    629  if (maybeHaveLeadingWhiteSpaces &&
    630      leadingWhiteSpaceRange.StartRef() == mStart.PointRef() &&
    631      leadingWhiteSpaceRange.EndRef() == mEnd.PointRef()) {
    632    mVisibleWhiteSpacesData.emplace(VisibleWhiteSpacesData());
    633    return mVisibleWhiteSpacesData.ref();
    634  }
    635  const EditorDOMRange& trailingWhiteSpaceRange =
    636      InvisibleTrailingWhiteSpaceRangeRef();
    637  const bool maybeHaveTrailingWhiteSpaces =
    638      trailingWhiteSpaceRange.StartRef().IsSet() ||
    639      trailingWhiteSpaceRange.EndRef().IsSet();
    640  if (maybeHaveTrailingWhiteSpaces &&
    641      trailingWhiteSpaceRange.StartRef() == mStart.PointRef() &&
    642      trailingWhiteSpaceRange.EndRef() == mEnd.PointRef()) {
    643    mVisibleWhiteSpacesData.emplace(VisibleWhiteSpacesData());
    644    return mVisibleWhiteSpacesData.ref();
    645  }
    646 
    647  if (!StartsFromHardLineBreak() && !StartsFromInlineEditingHostBoundary()) {
    648    VisibleWhiteSpacesData visibleWhiteSpaces;
    649    if (mStart.PointRef().IsSet()) {
    650      visibleWhiteSpaces.SetStartPoint(mStart.PointRef());
    651    }
    652    visibleWhiteSpaces.SetStartFrom(mStart.RawReason());
    653    if (!maybeHaveTrailingWhiteSpaces) {
    654      visibleWhiteSpaces.SetEndPoint(mEnd.PointRef());
    655      visibleWhiteSpaces.SetEndBy(mEnd.RawReason());
    656      mVisibleWhiteSpacesData = Some(visibleWhiteSpaces);
    657      return mVisibleWhiteSpacesData.ref();
    658    }
    659    if (trailingWhiteSpaceRange.StartRef().IsSet()) {
    660      visibleWhiteSpaces.SetEndPoint(trailingWhiteSpaceRange.StartRef());
    661    }
    662    visibleWhiteSpaces.SetEndByTrailingWhiteSpaces();
    663    mVisibleWhiteSpacesData.emplace(visibleWhiteSpaces);
    664    return mVisibleWhiteSpacesData.ref();
    665  }
    666 
    667  MOZ_ASSERT(StartsFromHardLineBreak() ||
    668             StartsFromInlineEditingHostBoundary());
    669  MOZ_ASSERT(maybeHaveLeadingWhiteSpaces);
    670 
    671  VisibleWhiteSpacesData visibleWhiteSpaces;
    672  if (leadingWhiteSpaceRange.EndRef().IsSet()) {
    673    visibleWhiteSpaces.SetStartPoint(leadingWhiteSpaceRange.EndRef());
    674  }
    675  visibleWhiteSpaces.SetStartFromLeadingWhiteSpaces();
    676  if (!EndsByBlockBoundary() && !EndsByInlineEditingHostBoundary()) {
    677    // then no trailing ws.  this normal run ends the overall ws run.
    678    if (mEnd.PointRef().IsSet()) {
    679      visibleWhiteSpaces.SetEndPoint(mEnd.PointRef());
    680    }
    681    visibleWhiteSpaces.SetEndBy(mEnd.RawReason());
    682    mVisibleWhiteSpacesData.emplace(visibleWhiteSpaces);
    683    return mVisibleWhiteSpacesData.ref();
    684  }
    685 
    686  MOZ_ASSERT(EndsByBlockBoundary() || EndsByInlineEditingHostBoundary());
    687 
    688  if (!maybeHaveTrailingWhiteSpaces) {
    689    // normal ws runs right up to adjacent block (nbsp next to block)
    690    visibleWhiteSpaces.SetEndPoint(mEnd.PointRef());
    691    visibleWhiteSpaces.SetEndBy(mEnd.RawReason());
    692    mVisibleWhiteSpacesData.emplace(visibleWhiteSpaces);
    693    return mVisibleWhiteSpacesData.ref();
    694  }
    695 
    696  if (trailingWhiteSpaceRange.StartRef().IsSet()) {
    697    visibleWhiteSpaces.SetEndPoint(trailingWhiteSpaceRange.StartRef());
    698  }
    699  visibleWhiteSpaces.SetEndByTrailingWhiteSpaces();
    700  mVisibleWhiteSpacesData.emplace(visibleWhiteSpaces);
    701  return mVisibleWhiteSpacesData.ref();
    702 }
    703 
    704 ReplaceRangeData
    705 WSRunScanner::TextFragmentData::GetReplaceRangeDataAtEndOfDeletionRange(
    706    const TextFragmentData& aTextFragmentDataAtStartToDelete) const {
    707  const EditorDOMPoint& startToDelete =
    708      aTextFragmentDataAtStartToDelete.ScanStartRef();
    709  const EditorDOMPoint& endToDelete = mScanStartPoint;
    710 
    711  MOZ_ASSERT(startToDelete.IsSetAndValid());
    712  MOZ_ASSERT(endToDelete.IsSetAndValid());
    713  MOZ_ASSERT(startToDelete.EqualsOrIsBefore(endToDelete));
    714 
    715  if (EndRef().EqualsOrIsBefore(endToDelete)) {
    716    return ReplaceRangeData();
    717  }
    718 
    719  // If deleting range is followed by invisible trailing white-spaces, we need
    720  // to remove it for making them not visible.
    721  const EditorDOMRange invisibleTrailingWhiteSpaceRangeAtEnd =
    722      GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(endToDelete);
    723  if (invisibleTrailingWhiteSpaceRangeAtEnd.IsPositioned()) {
    724    if (invisibleTrailingWhiteSpaceRangeAtEnd.Collapsed()) {
    725      return ReplaceRangeData();
    726    }
    727    // XXX Why don't we remove all invisible white-spaces?
    728    MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeAtEnd.StartRef() == endToDelete);
    729    return ReplaceRangeData(invisibleTrailingWhiteSpaceRangeAtEnd, u""_ns);
    730  }
    731 
    732  // If end of the deleting range is followed by visible white-spaces which
    733  // is not preformatted, we might need to replace the following ASCII
    734  // white-spaces with an NBSP.
    735  const VisibleWhiteSpacesData& nonPreformattedVisibleWhiteSpacesAtEnd =
    736      VisibleWhiteSpacesDataRef();
    737  if (!nonPreformattedVisibleWhiteSpacesAtEnd.IsInitialized()) {
    738    return ReplaceRangeData();
    739  }
    740  const PointPosition pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd =
    741      nonPreformattedVisibleWhiteSpacesAtEnd.ComparePoint(endToDelete);
    742  if (pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd !=
    743          PointPosition::StartOfFragment &&
    744      pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd !=
    745          PointPosition::MiddleOfFragment) {
    746    return ReplaceRangeData();
    747  }
    748  // If start of deleting range follows white-spaces or end of delete
    749  // will be start of a line, the following text cannot start with an
    750  // ASCII white-space for keeping it visible.
    751  if (!aTextFragmentDataAtStartToDelete
    752           .FollowingContentMayBecomeFirstVisibleContent(startToDelete)) {
    753    return ReplaceRangeData();
    754  }
    755  auto nextCharOfStartOfEnd = GetInclusiveNextCharPoint<EditorDOMPointInText>(
    756      endToDelete, ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions));
    757  if (!nextCharOfStartOfEnd.IsSet() ||
    758      nextCharOfStartOfEnd.IsEndOfContainer() ||
    759      !nextCharOfStartOfEnd.IsCharCollapsibleASCIISpace()) {
    760    return ReplaceRangeData();
    761  }
    762  if (nextCharOfStartOfEnd.IsStartOfContainer() ||
    763      nextCharOfStartOfEnd.IsPreviousCharCollapsibleASCIISpace()) {
    764    nextCharOfStartOfEnd =
    765        aTextFragmentDataAtStartToDelete
    766            .GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointInText>(
    767                nextCharOfStartOfEnd, nsIEditor::eNone,
    768                ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions));
    769  }
    770  const auto endOfCollapsibleASCIIWhiteSpaces =
    771      aTextFragmentDataAtStartToDelete
    772          .GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>(
    773              nextCharOfStartOfEnd, nsIEditor::eNone,
    774              ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions));
    775  return ReplaceRangeData(nextCharOfStartOfEnd,
    776                          endOfCollapsibleASCIIWhiteSpaces,
    777                          nsDependentSubstring(&HTMLEditUtils::kNBSP, 1));
    778 }
    779 
    780 ReplaceRangeData
    781 WSRunScanner::TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange(
    782    const TextFragmentData& aTextFragmentDataAtEndToDelete) const {
    783  const EditorDOMPoint& startToDelete = mScanStartPoint;
    784  const EditorDOMPoint& endToDelete =
    785      aTextFragmentDataAtEndToDelete.ScanStartRef();
    786 
    787  MOZ_ASSERT(startToDelete.IsSetAndValid());
    788  MOZ_ASSERT(endToDelete.IsSetAndValid());
    789  MOZ_ASSERT(startToDelete.EqualsOrIsBefore(endToDelete));
    790 
    791  if (startToDelete.EqualsOrIsBefore(StartRef())) {
    792    return ReplaceRangeData();
    793  }
    794 
    795  const EditorDOMRange invisibleLeadingWhiteSpaceRangeAtStart =
    796      GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(startToDelete);
    797 
    798  // If deleting range follows invisible leading white-spaces, we need to
    799  // remove them for making them not visible.
    800  if (invisibleLeadingWhiteSpaceRangeAtStart.IsPositioned()) {
    801    if (invisibleLeadingWhiteSpaceRangeAtStart.Collapsed()) {
    802      return ReplaceRangeData();
    803    }
    804 
    805    // XXX Why don't we remove all leading white-spaces?
    806    return ReplaceRangeData(invisibleLeadingWhiteSpaceRangeAtStart, u""_ns);
    807  }
    808 
    809  // If start of the deleting range follows visible white-spaces which is not
    810  // preformatted, we might need to replace previous ASCII white-spaces with
    811  // an NBSP.
    812  const VisibleWhiteSpacesData& nonPreformattedVisibleWhiteSpacesAtStart =
    813      VisibleWhiteSpacesDataRef();
    814  if (!nonPreformattedVisibleWhiteSpacesAtStart.IsInitialized()) {
    815    return ReplaceRangeData();
    816  }
    817  const PointPosition
    818      pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart =
    819          nonPreformattedVisibleWhiteSpacesAtStart.ComparePoint(startToDelete);
    820  if (pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart !=
    821          PointPosition::MiddleOfFragment &&
    822      pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart !=
    823          PointPosition::EndOfFragment) {
    824    return ReplaceRangeData();
    825  }
    826  // If end of the deleting range is (was) followed by white-spaces or
    827  // previous character of start of deleting range will be immediately
    828  // before a block boundary, the text cannot ends with an ASCII white-space
    829  // for keeping it visible.
    830  if (!aTextFragmentDataAtEndToDelete.PrecedingContentMayBecomeInvisible(
    831          endToDelete)) {
    832    return ReplaceRangeData();
    833  }
    834  auto atPreviousCharOfStart = GetPreviousCharPoint<EditorDOMPointInText>(
    835      startToDelete, ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions));
    836  if (!atPreviousCharOfStart.IsSet() ||
    837      atPreviousCharOfStart.IsEndOfContainer() ||
    838      !atPreviousCharOfStart.IsCharCollapsibleASCIISpace()) {
    839    return ReplaceRangeData();
    840  }
    841  if (atPreviousCharOfStart.IsStartOfContainer() ||
    842      atPreviousCharOfStart.IsPreviousCharASCIISpace()) {
    843    atPreviousCharOfStart =
    844        GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointInText>(
    845            atPreviousCharOfStart, nsIEditor::eNone,
    846            ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions));
    847  }
    848  const auto endOfCollapsibleASCIIWhiteSpaces =
    849      GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>(
    850          atPreviousCharOfStart, nsIEditor::eNone,
    851          ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions));
    852  return ReplaceRangeData(atPreviousCharOfStart,
    853                          endOfCollapsibleASCIIWhiteSpaces,
    854                          nsDependentSubstring(&HTMLEditUtils::kNBSP, 1));
    855 }
    856 
    857 // static
    858 template <typename EditorDOMPointType, typename PT, typename CT>
    859 EditorDOMPointType WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint(
    860    const EditorDOMPointBase<PT, CT>& aPoint,
    861    Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    862    IgnoreNonEditableNodes aIgnoreNonEditableNodes,
    863    const nsIContent* aFollowingLimiterContent /* = nullptr */) {
    864  MOZ_ASSERT(aPoint.IsSetAndValid());
    865 
    866  if (NS_WARN_IF(!aPoint.IsInContentNode())) {
    867    return EditorDOMPointType();
    868  }
    869 
    870  const BlockInlineCheck blockInlineCheck =
    871      aOptions.contains(Option::ReferHTMLDefaultStyle)
    872          ? BlockInlineCheck::UseHTMLDefaultStyle
    873          : BlockInlineCheck::Auto;
    874  const EditorRawDOMPoint point = [&]() MOZ_NEVER_INLINE_DEBUG {
    875    nsIContent* const child = [&]() -> nsIContent* {
    876      nsIContent* child =
    877          aPoint.CanContainerHaveChildren() ? aPoint.GetChild() : nullptr;
    878      // XXX Why don't we skip non-editable nodes here?
    879      while (child && child->IsComment() &&
    880             !aOptions.contains(Option::StopAtComment)) {
    881        child = child->GetNextSibling();
    882      }
    883      return child;
    884    }();
    885    if (!child ||
    886        HTMLEditUtils::IsBlockElement(
    887            *child, UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) ||
    888        HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*child)) {
    889      return aPoint.template To<EditorRawDOMPoint>();
    890    }
    891    if (!child->HasChildNodes()) {
    892      return child->IsText() || HTMLEditUtils::IsContainerNode(*child)
    893                 ? EditorRawDOMPoint(child, 0)
    894                 : EditorRawDOMPoint::After(*child);
    895    }
    896    // FIXME: This may skip aFollowingLimiterContent, so, this utility should
    897    // take a stopper param.
    898    // FIXME: I think we should stop looking for a leaf node if there is a child
    899    // block because end reason content should not be the other side of the
    900    // following block boundary.
    901    nsIContent* const leafContent = HTMLEditUtils::GetFirstLeafContent(
    902        *child, {LeafNodeType::LeafNodeOrChildBlock}, blockInlineCheck);
    903    if (!leafContent) {
    904      return EditorRawDOMPoint(child, 0);
    905    }
    906    if (HTMLEditUtils::IsBlockElement(
    907            *leafContent,
    908            UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) ||
    909        HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*leafContent)) {
    910      return EditorRawDOMPoint();
    911    }
    912    return EditorRawDOMPoint(leafContent, 0);
    913  }();
    914  if (!point.IsSet()) {
    915    return EditorDOMPointType();
    916  }
    917 
    918  // If it points a character in a text node, return it.
    919  // XXX For the performance, this does not check whether the container
    920  //     is outside of our range.
    921  if (point.IsInTextNode() &&
    922      (aIgnoreNonEditableNodes == IgnoreNonEditableNodes::No ||
    923       HTMLEditUtils::IsSimplyEditableNode(*point.GetContainer())) &&
    924      !point.IsEndOfContainer()) {
    925    return EditorDOMPointType(point.ContainerAs<Text>(), point.Offset());
    926  }
    927 
    928  if (point.GetContainer() == aFollowingLimiterContent) {
    929    return EditorDOMPointType();
    930  }
    931 
    932  const Element* const
    933      editableBlockElementOrInlineEditingHostOrNonEditableRootElement =
    934          HTMLEditUtils::GetInclusiveAncestorElement(
    935              *aPoint.template ContainerAs<nsIContent>(),
    936              HTMLEditUtils::IsSimplyEditableNode(
    937                  *aPoint.template ContainerAs<nsIContent>())
    938                  ? kScanEditableRootAncestorTypes
    939                  : kScanAnyRootAncestorTypes,
    940              blockInlineCheck);
    941  if (NS_WARN_IF(
    942          !editableBlockElementOrInlineEditingHostOrNonEditableRootElement)) {
    943    return EditorDOMPointType();
    944  }
    945 
    946  const auto leafNodeTypes = [&]() -> LeafNodeTypes {
    947    auto types = aIgnoreNonEditableNodes == IgnoreNonEditableNodes::Yes
    948                     ? LeafNodeTypes(LeafNodeType::LeafNodeOrNonEditableNode,
    949                                     LeafNodeType::LeafNodeOrChildBlock)
    950                     : LeafNodeTypes(LeafNodeType::LeafNodeOrChildBlock);
    951    if (aOptions.contains(Option::StopAtComment)) {
    952      types += LeafNodeType::TreatCommentAsLeafNode;
    953    }
    954    return types;
    955  }();
    956  for (nsIContent* nextContent =
    957           HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
    958               *point.ContainerAs<nsIContent>(), leafNodeTypes,
    959               blockInlineCheck,
    960               editableBlockElementOrInlineEditingHostOrNonEditableRootElement);
    961       nextContent;
    962       nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
    963           *nextContent, leafNodeTypes, blockInlineCheck,
    964           editableBlockElementOrInlineEditingHostOrNonEditableRootElement)) {
    965    if (!nextContent->IsText() ||
    966        (aIgnoreNonEditableNodes == IgnoreNonEditableNodes::Yes &&
    967         !HTMLEditUtils::IsSimplyEditableNode(*nextContent))) {
    968      if (nextContent == aFollowingLimiterContent ||
    969          HTMLEditUtils::IsBlockElement(
    970              *nextContent,
    971              UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) ||
    972          HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*nextContent)) {
    973        break;  // Reached end of current runs.
    974      }
    975      continue;
    976    }
    977    return EditorDOMPointType(nextContent->AsText(), 0);
    978  }
    979  return EditorDOMPointType();
    980 }
    981 
    982 // static
    983 template <typename EditorDOMPointType, typename PT, typename CT>
    984 EditorDOMPointType WSRunScanner::TextFragmentData::GetPreviousCharPoint(
    985    const EditorDOMPointBase<PT, CT>& aPoint,
    986    Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    987    IgnoreNonEditableNodes aIgnoreNonEditableNodes,
    988    const nsIContent* aPrecedingLimiterContent /* = nullptr */) {
    989  MOZ_ASSERT(aPoint.IsSetAndValid());
    990 
    991  if (NS_WARN_IF(!aPoint.IsInContentNode())) {
    992    return EditorDOMPointType();
    993  }
    994 
    995  const BlockInlineCheck blockInlineCheck =
    996      aOptions.contains(Option::ReferHTMLDefaultStyle)
    997          ? BlockInlineCheck::UseHTMLDefaultStyle
    998          : BlockInlineCheck::Auto;
    999  const EditorRawDOMPoint point = [&]() MOZ_NEVER_INLINE_DEBUG {
   1000    nsIContent* const previousChild = [&]() -> nsIContent* {
   1001      nsIContent* previousChild = aPoint.CanContainerHaveChildren()
   1002                                      ? aPoint.GetPreviousSiblingOfChild()
   1003                                      : nullptr;
   1004      // XXX Why don't we skip non-editable nodes here?
   1005      while (previousChild && previousChild->IsComment() &&
   1006             !aOptions.contains(Option::StopAtComment)) {
   1007        previousChild = previousChild->GetPreviousSibling();
   1008      }
   1009      return previousChild;
   1010    }();
   1011    if (!previousChild ||
   1012        HTMLEditUtils::IsBlockElement(
   1013            *previousChild,
   1014            UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) ||
   1015        HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousChild)) {
   1016      return aPoint.template To<EditorRawDOMPoint>();
   1017    }
   1018    if (!previousChild->HasChildren()) {
   1019      return previousChild->IsText() ||
   1020                     HTMLEditUtils::IsContainerNode(*previousChild)
   1021                 ? EditorRawDOMPoint::AtEndOf(*previousChild)
   1022                 : EditorRawDOMPoint::After(*previousChild);
   1023    }
   1024    // FIXME: This may skip aPrecedingLimiterContent, so, this utility should
   1025    // take a stopper param.
   1026    // FIXME: I think we should stop looking for a leaf node if there is a child
   1027    // block because end reason content should not be the other side of the
   1028    // following block boundary.
   1029    nsIContent* const leafContent = HTMLEditUtils::GetLastLeafContent(
   1030        *previousChild, {LeafNodeType::LeafNodeOrChildBlock}, blockInlineCheck);
   1031    if (!leafContent) {
   1032      return EditorRawDOMPoint::AtEndOf(*previousChild);
   1033    }
   1034    if (HTMLEditUtils::IsBlockElement(
   1035            *leafContent,
   1036            UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) ||
   1037        HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*leafContent)) {
   1038      return EditorRawDOMPoint();
   1039    }
   1040    return EditorRawDOMPoint::AtEndOf(*leafContent);
   1041  }();
   1042  if (!point.IsSet()) {
   1043    return EditorDOMPointType();
   1044  }
   1045 
   1046  // If it points a character in a text node and it's not first character
   1047  // in it, return its previous point.
   1048  // XXX For the performance, this does not check whether the container
   1049  //     is outside of our range.
   1050  if (point.IsInTextNode() &&
   1051      (aIgnoreNonEditableNodes == IgnoreNonEditableNodes::No ||
   1052       HTMLEditUtils::IsSimplyEditableNode(*point.GetContainer())) &&
   1053      !point.IsStartOfContainer()) {
   1054    return EditorDOMPointType(point.ContainerAs<Text>(), point.Offset() - 1);
   1055  }
   1056 
   1057  if (point.GetContainer() == aPrecedingLimiterContent) {
   1058    return EditorDOMPointType();
   1059  }
   1060 
   1061  const Element* const
   1062      editableBlockElementOrInlineEditingHostOrNonEditableRootElement =
   1063          HTMLEditUtils::GetInclusiveAncestorElement(
   1064              *aPoint.template ContainerAs<nsIContent>(),
   1065              HTMLEditUtils::IsSimplyEditableNode(
   1066                  *aPoint.template ContainerAs<nsIContent>())
   1067                  ? kScanEditableRootAncestorTypes
   1068                  : kScanAnyRootAncestorTypes,
   1069              blockInlineCheck);
   1070  if (NS_WARN_IF(
   1071          !editableBlockElementOrInlineEditingHostOrNonEditableRootElement)) {
   1072    return EditorDOMPointType();
   1073  }
   1074 
   1075  const auto leafNodeTypes = [&]() -> LeafNodeTypes {
   1076    auto types = aIgnoreNonEditableNodes == IgnoreNonEditableNodes::Yes
   1077                     ? LeafNodeTypes(LeafNodeType::LeafNodeOrNonEditableNode,
   1078                                     LeafNodeType::LeafNodeOrChildBlock)
   1079                     : LeafNodeTypes(LeafNodeType::LeafNodeOrChildBlock);
   1080    if (aOptions.contains(Option::StopAtComment)) {
   1081      types += LeafNodeType::TreatCommentAsLeafNode;
   1082    }
   1083    return types;
   1084  }();
   1085  for (
   1086      nsIContent* previousContent =
   1087          HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1088              *point.ContainerAs<nsIContent>(), leafNodeTypes, blockInlineCheck,
   1089              editableBlockElementOrInlineEditingHostOrNonEditableRootElement);
   1090      previousContent;
   1091      previousContent =
   1092          HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1093              *previousContent, leafNodeTypes, blockInlineCheck,
   1094              editableBlockElementOrInlineEditingHostOrNonEditableRootElement)) {
   1095    if (!previousContent->IsText() ||
   1096        (aIgnoreNonEditableNodes == IgnoreNonEditableNodes::Yes &&
   1097         !HTMLEditUtils::IsSimplyEditableNode(*previousContent))) {
   1098      if (previousContent == aPrecedingLimiterContent ||
   1099          HTMLEditUtils::IsBlockElement(
   1100              *previousContent,
   1101              UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) ||
   1102          HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousContent)) {
   1103        break;  // Reached start of current runs.
   1104      }
   1105      continue;
   1106    }
   1107    return EditorDOMPointType(previousContent->AsText(),
   1108                              previousContent->AsText()->TextLength()
   1109                                  ? previousContent->AsText()->TextLength() - 1
   1110                                  : 0);
   1111  }
   1112  return EditorDOMPointType();
   1113 }
   1114 
   1115 // static
   1116 template <typename EditorDOMPointType>
   1117 EditorDOMPointType
   1118 WSRunScanner::TextFragmentData::GetEndOfCollapsibleASCIIWhiteSpaces(
   1119    const EditorDOMPointInText& aPointAtASCIIWhiteSpace,
   1120    nsIEditor::EDirection aDirectionToDelete,
   1121    Options aOptions,  // NOLINT(performance-unnecessary-value-param)
   1122    IgnoreNonEditableNodes aIgnoreNonEditableNodes,
   1123    const nsIContent* aFollowingLimiterContent /* = nullptr */) {
   1124  MOZ_ASSERT(aDirectionToDelete == nsIEditor::eNone ||
   1125             aDirectionToDelete == nsIEditor::eNext ||
   1126             aDirectionToDelete == nsIEditor::ePrevious);
   1127  MOZ_ASSERT(aPointAtASCIIWhiteSpace.IsSet());
   1128  MOZ_ASSERT(!aPointAtASCIIWhiteSpace.IsEndOfContainer());
   1129  MOZ_ASSERT_IF(!EditorUtils::IsNewLinePreformatted(
   1130                    *aPointAtASCIIWhiteSpace.ContainerAs<Text>()),
   1131                aPointAtASCIIWhiteSpace.IsCharCollapsibleASCIISpace());
   1132  MOZ_ASSERT_IF(EditorUtils::IsNewLinePreformatted(
   1133                    *aPointAtASCIIWhiteSpace.ContainerAs<Text>()),
   1134                aPointAtASCIIWhiteSpace.IsCharASCIISpace());
   1135 
   1136  // If we're deleting text forward and the next visible character is first
   1137  // preformatted new line but white-spaces can be collapsed, we need to
   1138  // delete its following collapsible white-spaces too.
   1139  bool hasSeenPreformattedNewLine =
   1140      aPointAtASCIIWhiteSpace.IsCharPreformattedNewLine();
   1141  auto NeedToScanFollowingWhiteSpaces =
   1142      [&hasSeenPreformattedNewLine,
   1143       &aDirectionToDelete](const EditorDOMPointInText& aAtNextVisibleCharacter)
   1144          MOZ_NEVER_INLINE_DEBUG -> bool {
   1145    MOZ_ASSERT(!aAtNextVisibleCharacter.IsEndOfContainer());
   1146    return !hasSeenPreformattedNewLine &&
   1147           aDirectionToDelete == nsIEditor::eNext &&
   1148           aAtNextVisibleCharacter
   1149               .IsCharPreformattedNewLineCollapsedWithWhiteSpaces();
   1150  };
   1151  auto ScanNextNonCollapsibleChar =
   1152      [&hasSeenPreformattedNewLine,
   1153       &NeedToScanFollowingWhiteSpaces](const EditorDOMPointInText& aPoint)
   1154          MOZ_NEVER_INLINE_DEBUG -> EditorDOMPointInText {
   1155    Maybe<uint32_t> nextVisibleCharOffset =
   1156        HTMLEditUtils::GetNextNonCollapsibleCharOffset(aPoint);
   1157    if (!nextVisibleCharOffset.isSome()) {
   1158      return EditorDOMPointInText();  // Keep scanning following text nodes
   1159    }
   1160    EditorDOMPointInText atNextVisibleChar(aPoint.ContainerAs<Text>(),
   1161                                           nextVisibleCharOffset.value());
   1162    if (!NeedToScanFollowingWhiteSpaces(atNextVisibleChar)) {
   1163      return atNextVisibleChar;
   1164    }
   1165    hasSeenPreformattedNewLine |= atNextVisibleChar.IsCharPreformattedNewLine();
   1166    nextVisibleCharOffset =
   1167        HTMLEditUtils::GetNextNonCollapsibleCharOffset(atNextVisibleChar);
   1168    if (nextVisibleCharOffset.isSome()) {
   1169      MOZ_ASSERT(aPoint.ContainerAs<Text>() ==
   1170                 atNextVisibleChar.ContainerAs<Text>());
   1171      return EditorDOMPointInText(atNextVisibleChar.ContainerAs<Text>(),
   1172                                  nextVisibleCharOffset.value());
   1173    }
   1174    return EditorDOMPointInText();  // Keep scanning following text nodes
   1175  };
   1176 
   1177  // If it's not the last character in the text node, let's scan following
   1178  // characters in it.
   1179  if (!aPointAtASCIIWhiteSpace.IsAtLastContent()) {
   1180    const EditorDOMPointInText atNextVisibleChar(
   1181        ScanNextNonCollapsibleChar(aPointAtASCIIWhiteSpace));
   1182    if (atNextVisibleChar.IsSet()) {
   1183      return atNextVisibleChar.To<EditorDOMPointType>();
   1184    }
   1185  }
   1186 
   1187  // Otherwise, i.e., the text node ends with ASCII white-space, keep scanning
   1188  // the following text nodes.
   1189  // XXX Perhaps, we should stop scanning if there is non-editable and visible
   1190  //     content.
   1191  EditorDOMPointInText afterLastWhiteSpace = EditorDOMPointInText::AtEndOf(
   1192      *aPointAtASCIIWhiteSpace.ContainerAs<Text>());
   1193  for (EditorDOMPointInText atEndOfPreviousTextNode = afterLastWhiteSpace;;) {
   1194    const auto atStartOfNextTextNode =
   1195        TextFragmentData::GetInclusiveNextCharPoint<EditorDOMPointInText>(
   1196            atEndOfPreviousTextNode, aOptions, aIgnoreNonEditableNodes,
   1197            aFollowingLimiterContent);
   1198    if (!atStartOfNextTextNode.IsSet()) {
   1199      // There is no more text nodes.  Return end of the previous text node.
   1200      return afterLastWhiteSpace.To<EditorDOMPointType>();
   1201    }
   1202 
   1203    // We can ignore empty text nodes (even if it's preformatted).
   1204    if (atStartOfNextTextNode.IsContainerEmpty()) {
   1205      atEndOfPreviousTextNode = atStartOfNextTextNode;
   1206      continue;
   1207    }
   1208 
   1209    // If next node starts with non-white-space character or next node is
   1210    // preformatted, return end of previous text node.  However, if it
   1211    // starts with a preformatted linefeed but white-spaces are collapsible,
   1212    // we need to scan following collapsible white-spaces when we're deleting
   1213    // text forward.
   1214    if (!atStartOfNextTextNode.IsCharCollapsibleASCIISpace() &&
   1215        !NeedToScanFollowingWhiteSpaces(atStartOfNextTextNode)) {
   1216      return afterLastWhiteSpace.To<EditorDOMPointType>();
   1217    }
   1218 
   1219    // Otherwise, scan the text node.
   1220    const EditorDOMPointInText atNextVisibleChar(
   1221        ScanNextNonCollapsibleChar(atStartOfNextTextNode));
   1222    if (atNextVisibleChar.IsSet()) {
   1223      return atNextVisibleChar.To<EditorDOMPointType>();
   1224    }
   1225 
   1226    // The next text nodes ends with white-space too.  Try next one.
   1227    afterLastWhiteSpace = atEndOfPreviousTextNode =
   1228        EditorDOMPointInText::AtEndOf(
   1229            *atStartOfNextTextNode.ContainerAs<Text>());
   1230  }
   1231 }
   1232 
   1233 // static
   1234 template <typename EditorDOMPointType>
   1235 EditorDOMPointType
   1236 WSRunScanner::TextFragmentData::GetFirstASCIIWhiteSpacePointCollapsedTo(
   1237    const EditorDOMPointInText& aPointAtASCIIWhiteSpace,
   1238    nsIEditor::EDirection aDirectionToDelete,
   1239    Options aOptions,  // NOLINT(performance-unnecessary-value-param)
   1240    IgnoreNonEditableNodes aIgnoreNonEditableNodes,
   1241    const nsIContent* aPrecedingLimiterContent) {
   1242  MOZ_ASSERT(aDirectionToDelete == nsIEditor::eNone ||
   1243             aDirectionToDelete == nsIEditor::eNext ||
   1244             aDirectionToDelete == nsIEditor::ePrevious);
   1245  MOZ_ASSERT(aPointAtASCIIWhiteSpace.IsSet());
   1246  MOZ_ASSERT(!aPointAtASCIIWhiteSpace.IsEndOfContainer());
   1247  MOZ_ASSERT_IF(!EditorUtils::IsNewLinePreformatted(
   1248                    *aPointAtASCIIWhiteSpace.ContainerAs<Text>()),
   1249                aPointAtASCIIWhiteSpace.IsCharCollapsibleASCIISpace());
   1250  MOZ_ASSERT_IF(EditorUtils::IsNewLinePreformatted(
   1251                    *aPointAtASCIIWhiteSpace.ContainerAs<Text>()),
   1252                aPointAtASCIIWhiteSpace.IsCharASCIISpace());
   1253 
   1254  // If we're deleting text backward and the previous visible character is first
   1255  // preformatted new line but white-spaces can be collapsed, we need to delete
   1256  // its preceding collapsible white-spaces too.
   1257  bool hasSeenPreformattedNewLine =
   1258      aPointAtASCIIWhiteSpace.IsCharPreformattedNewLine();
   1259  auto NeedToScanPrecedingWhiteSpaces =
   1260      [&hasSeenPreformattedNewLine, &aDirectionToDelete](
   1261          const EditorDOMPointInText& aAtPreviousVisibleCharacter)
   1262          MOZ_NEVER_INLINE_DEBUG -> bool {
   1263    MOZ_ASSERT(!aAtPreviousVisibleCharacter.IsEndOfContainer());
   1264    return !hasSeenPreformattedNewLine &&
   1265           aDirectionToDelete == nsIEditor::ePrevious &&
   1266           aAtPreviousVisibleCharacter
   1267               .IsCharPreformattedNewLineCollapsedWithWhiteSpaces();
   1268  };
   1269  auto ScanPreviousNonCollapsibleChar =
   1270      [&hasSeenPreformattedNewLine,
   1271       &NeedToScanPrecedingWhiteSpaces](const EditorDOMPointInText& aPoint)
   1272          MOZ_NEVER_INLINE_DEBUG -> EditorDOMPointInText {
   1273    Maybe<uint32_t> previousVisibleCharOffset =
   1274        HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(aPoint);
   1275    if (previousVisibleCharOffset.isNothing()) {
   1276      return EditorDOMPointInText();  // Keep scanning preceding text nodes
   1277    }
   1278    EditorDOMPointInText atPreviousVisibleCharacter(
   1279        aPoint.ContainerAs<Text>(), previousVisibleCharOffset.value());
   1280    if (!NeedToScanPrecedingWhiteSpaces(atPreviousVisibleCharacter)) {
   1281      return atPreviousVisibleCharacter.NextPoint();
   1282    }
   1283    hasSeenPreformattedNewLine |=
   1284        atPreviousVisibleCharacter.IsCharPreformattedNewLine();
   1285    previousVisibleCharOffset =
   1286        HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(
   1287            atPreviousVisibleCharacter);
   1288    if (previousVisibleCharOffset.isSome()) {
   1289      MOZ_ASSERT(aPoint.ContainerAs<Text>() ==
   1290                 atPreviousVisibleCharacter.ContainerAs<Text>());
   1291      return EditorDOMPointInText(
   1292          atPreviousVisibleCharacter.ContainerAs<Text>(),
   1293          previousVisibleCharOffset.value() + 1);
   1294    }
   1295    return EditorDOMPointInText();  // Keep scanning preceding text nodes
   1296  };
   1297 
   1298  // If there is some characters before it, scan it in the text node first.
   1299  if (!aPointAtASCIIWhiteSpace.IsStartOfContainer()) {
   1300    EditorDOMPointInText atFirstASCIIWhiteSpace(
   1301        ScanPreviousNonCollapsibleChar(aPointAtASCIIWhiteSpace));
   1302    if (atFirstASCIIWhiteSpace.IsSet()) {
   1303      return atFirstASCIIWhiteSpace.To<EditorDOMPointType>();
   1304    }
   1305  }
   1306 
   1307  // Otherwise, i.e., the text node starts with ASCII white-space, keep scanning
   1308  // the preceding text nodes.
   1309  // XXX Perhaps, we should stop scanning if there is non-editable and visible
   1310  //     content.
   1311  EditorDOMPointInText atLastWhiteSpace =
   1312      EditorDOMPointInText(aPointAtASCIIWhiteSpace.ContainerAs<Text>(), 0u);
   1313  for (EditorDOMPointInText atStartOfPreviousTextNode = atLastWhiteSpace;;) {
   1314    const auto atLastCharOfPreviousTextNode =
   1315        TextFragmentData::GetPreviousCharPoint<EditorDOMPointInText>(
   1316            atStartOfPreviousTextNode, aOptions, aIgnoreNonEditableNodes,
   1317            aPrecedingLimiterContent);
   1318    if (!atLastCharOfPreviousTextNode.IsSet()) {
   1319      // There is no more text nodes.  Return end of last text node.
   1320      return atLastWhiteSpace.To<EditorDOMPointType>();
   1321    }
   1322 
   1323    // We can ignore empty text nodes (even if it's preformatted).
   1324    if (atLastCharOfPreviousTextNode.IsContainerEmpty()) {
   1325      atStartOfPreviousTextNode = atLastCharOfPreviousTextNode;
   1326      continue;
   1327    }
   1328 
   1329    // If next node ends with non-white-space character or next node is
   1330    // preformatted, return start of previous text node.
   1331    if (!atLastCharOfPreviousTextNode.IsCharCollapsibleASCIISpace() &&
   1332        !NeedToScanPrecedingWhiteSpaces(atLastCharOfPreviousTextNode)) {
   1333      return atLastWhiteSpace.To<EditorDOMPointType>();
   1334    }
   1335 
   1336    // Otherwise, scan the text node.
   1337    const EditorDOMPointInText atFirstASCIIWhiteSpace(
   1338        ScanPreviousNonCollapsibleChar(atLastCharOfPreviousTextNode));
   1339    if (atFirstASCIIWhiteSpace.IsSet()) {
   1340      return atFirstASCIIWhiteSpace.To<EditorDOMPointType>();
   1341    }
   1342 
   1343    // The next text nodes starts with white-space too.  Try next one.
   1344    atLastWhiteSpace = atStartOfPreviousTextNode = EditorDOMPointInText(
   1345        atLastCharOfPreviousTextNode.ContainerAs<Text>(), 0u);
   1346  }
   1347 }
   1348 
   1349 EditorDOMPointInText WSRunScanner::TextFragmentData::
   1350    GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
   1351        const EditorDOMPoint& aPointToInsert) const {
   1352  MOZ_ASSERT(aPointToInsert.IsSetAndValid());
   1353  MOZ_ASSERT(VisibleWhiteSpacesDataRef().IsInitialized());
   1354  NS_ASSERTION(VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert) ==
   1355                       PointPosition::MiddleOfFragment ||
   1356                   VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert) ==
   1357                       PointPosition::EndOfFragment,
   1358               "Previous char of aPoint should be in the visible white-spaces");
   1359 
   1360  // Try to change an NBSP to a space, if possible, just to prevent NBSP
   1361  // proliferation.  This routine is called when we are about to make this
   1362  // point in the ws abut an inserted break or text, so we don't have to worry
   1363  // about what is after it.  What is after it now will end up after the
   1364  // inserted object.
   1365  const auto atPreviousChar = GetPreviousCharPoint<EditorDOMPointInText>(
   1366      aPointToInsert, ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions));
   1367  if (!atPreviousChar.IsSet() || atPreviousChar.IsEndOfContainer() ||
   1368      !atPreviousChar.IsCharNBSP() ||
   1369      EditorUtils::IsWhiteSpacePreformatted(
   1370          *atPreviousChar.ContainerAs<Text>())) {
   1371    return EditorDOMPointInText();
   1372  }
   1373 
   1374  const auto atPreviousCharOfPreviousChar =
   1375      GetPreviousCharPoint<EditorDOMPointInText>(
   1376          atPreviousChar,
   1377          ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions));
   1378  if (atPreviousCharOfPreviousChar.IsSet()) {
   1379    // If the previous char is in different text node and it's preformatted,
   1380    // we shouldn't touch it.
   1381    if (atPreviousChar.ContainerAs<Text>() !=
   1382            atPreviousCharOfPreviousChar.ContainerAs<Text>() &&
   1383        EditorUtils::IsWhiteSpacePreformatted(
   1384            *atPreviousCharOfPreviousChar.ContainerAs<Text>())) {
   1385      return EditorDOMPointInText();
   1386    }
   1387    // If the previous char of the NBSP at previous position of aPointToInsert
   1388    // is an ASCII white-space, we don't need to replace it with same character.
   1389    if (!atPreviousCharOfPreviousChar.IsEndOfContainer() &&
   1390        atPreviousCharOfPreviousChar.IsCharASCIISpace()) {
   1391      return EditorDOMPointInText();
   1392    }
   1393    return atPreviousChar;
   1394  }
   1395 
   1396  // If previous content of the NBSP is block boundary, we cannot replace the
   1397  // NBSP with an ASCII white-space to keep it rendered.
   1398  const VisibleWhiteSpacesData& visibleWhiteSpaces =
   1399      VisibleWhiteSpacesDataRef();
   1400  if (!visibleWhiteSpaces.StartsFromNonCollapsibleCharacters() &&
   1401      !visibleWhiteSpaces.StartsFromSpecialContent()) {
   1402    return EditorDOMPointInText();
   1403  }
   1404  return atPreviousChar;
   1405 }
   1406 
   1407 EditorDOMPointInText WSRunScanner::TextFragmentData::
   1408    GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
   1409        const EditorDOMPoint& aPointToInsert) const {
   1410  MOZ_ASSERT(aPointToInsert.IsSetAndValid());
   1411  MOZ_ASSERT(VisibleWhiteSpacesDataRef().IsInitialized());
   1412  NS_ASSERTION(VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert) ==
   1413                       PointPosition::StartOfFragment ||
   1414                   VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert) ==
   1415                       PointPosition::MiddleOfFragment,
   1416               "Inclusive next char of aPointToInsert should be in the visible "
   1417               "white-spaces");
   1418 
   1419  // Try to change an nbsp to a space, if possible, just to prevent nbsp
   1420  // proliferation This routine is called when we are about to make this point
   1421  // in the ws abut an inserted text, so we don't have to worry about what is
   1422  // before it.  What is before it now will end up before the inserted text.
   1423  const auto atNextChar = GetInclusiveNextCharPoint<EditorDOMPointInText>(
   1424      aPointToInsert, ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions));
   1425  if (!atNextChar.IsSet() || NS_WARN_IF(atNextChar.IsEndOfContainer()) ||
   1426      !atNextChar.IsCharNBSP() ||
   1427      EditorUtils::IsWhiteSpacePreformatted(*atNextChar.ContainerAs<Text>())) {
   1428    return EditorDOMPointInText();
   1429  }
   1430 
   1431  const auto atNextCharOfNextCharOfNBSP =
   1432      GetInclusiveNextCharPoint<EditorDOMPointInText>(
   1433          atNextChar.NextPoint<EditorRawDOMPointInText>(),
   1434          ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions));
   1435  if (atNextCharOfNextCharOfNBSP.IsSet()) {
   1436    // If the next char is in different text node and it's preformatted,
   1437    // we shouldn't touch it.
   1438    if (atNextChar.ContainerAs<Text>() !=
   1439            atNextCharOfNextCharOfNBSP.ContainerAs<Text>() &&
   1440        EditorUtils::IsWhiteSpacePreformatted(
   1441            *atNextCharOfNextCharOfNBSP.ContainerAs<Text>())) {
   1442      return EditorDOMPointInText();
   1443    }
   1444    // If following character of an NBSP is an ASCII white-space, we don't
   1445    // need to replace it with same character.
   1446    if (!atNextCharOfNextCharOfNBSP.IsEndOfContainer() &&
   1447        atNextCharOfNextCharOfNBSP.IsCharASCIISpace()) {
   1448      return EditorDOMPointInText();
   1449    }
   1450    return atNextChar;
   1451  }
   1452 
   1453  // If the NBSP is last character in the hard line, we don't need to
   1454  // replace it because it's required to render multiple white-spaces.
   1455  const VisibleWhiteSpacesData& visibleWhiteSpaces =
   1456      VisibleWhiteSpacesDataRef();
   1457  if (!visibleWhiteSpaces.EndsByNonCollapsibleCharacters() &&
   1458      !visibleWhiteSpaces.EndsBySpecialContent() &&
   1459      !visibleWhiteSpaces.EndsByBRElement()) {
   1460    return EditorDOMPointInText();
   1461  }
   1462 
   1463  return atNextChar;
   1464 }
   1465 
   1466 }  // namespace mozilla