tor-browser

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

WSRunScanner.h (61169B)


      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 #ifndef WSRunScanner_h
      7 #define WSRunScanner_h
      8 
      9 #include "EditorBase.h"
     10 #include "EditorForwards.h"
     11 #include "EditorDOMPoint.h"   // for EditorDOMPoint
     12 #include "EditorLineBreak.h"  // for EditorLineBreakBase
     13 #include "HTMLEditor.h"
     14 #include "HTMLEditUtils.h"
     15 
     16 #include "mozilla/Assertions.h"
     17 #include "mozilla/Maybe.h"
     18 #include "mozilla/Result.h"
     19 #include "mozilla/dom/Element.h"
     20 #include "mozilla/dom/HTMLBRElement.h"
     21 #include "mozilla/dom/Text.h"
     22 #include "nsCOMPtr.h"
     23 #include "nsIContent.h"
     24 
     25 namespace mozilla {
     26 
     27 /**
     28 * WSScanResult is result of ScanNextVisibleNodeOrBlockBoundaryFrom(),
     29 * ScanPreviousVisibleNodeOrBlockBoundaryFrom(), and their static wrapper
     30 * methods.  This will have information of found visible content (and its
     31 * position) or reached block element or topmost editable content at the
     32 * start of scanner.
     33 */
     34 class MOZ_STACK_CLASS WSScanResult final {
     35 private:
     36  using Element = dom::Element;
     37  using HTMLBRElement = dom::HTMLBRElement;
     38  using Text = dom::Text;
     39 
     40  enum class WSType : uint8_t {
     41    NotInitialized,
     42    // Could be the DOM tree is broken as like crash tests.
     43    UnexpectedError,
     44    // The scanner cannot work in uncomposed tree, but tried to scan in it.
     45    InUncomposedDoc,
     46    // The run is maybe collapsible white-spaces at start of a hard line.
     47    LeadingWhiteSpaces,
     48    // The run is maybe collapsible white-spaces at end of a hard line.
     49    TrailingWhiteSpaces,
     50    // Collapsible, but visible white-spaces.
     51    CollapsibleWhiteSpaces,
     52    // Visible characters except collapsible white-spaces.
     53    NonCollapsibleCharacters,
     54    // Special content such as `<img>`, etc.
     55    SpecialContent,
     56    // <br> element.
     57    BRElement,
     58    // A linefeed which is preformatted.
     59    PreformattedLineBreak,
     60    // Other block's boundary (child block of current block, maybe).
     61    OtherBlockBoundary,
     62    // Current block's boundary.
     63    CurrentBlockBoundary,
     64    // Inline editing host boundary.
     65    InlineEditingHostBoundary,
     66  };
     67 
     68  friend std::ostream& operator<<(std::ostream& aStream, const WSType& aType) {
     69    switch (aType) {
     70      case WSType::NotInitialized:
     71        return aStream << "WSType::NotInitialized";
     72      case WSType::UnexpectedError:
     73        return aStream << "WSType::UnexpectedError";
     74      case WSType::InUncomposedDoc:
     75        return aStream << "WSType::InUncomposedDoc";
     76      case WSType::LeadingWhiteSpaces:
     77        return aStream << "WSType::LeadingWhiteSpaces";
     78      case WSType::TrailingWhiteSpaces:
     79        return aStream << "WSType::TrailingWhiteSpaces";
     80      case WSType::CollapsibleWhiteSpaces:
     81        return aStream << "WSType::CollapsibleWhiteSpaces";
     82      case WSType::NonCollapsibleCharacters:
     83        return aStream << "WSType::NonCollapsibleCharacters";
     84      case WSType::SpecialContent:
     85        return aStream << "WSType::SpecialContent";
     86      case WSType::BRElement:
     87        return aStream << "WSType::BRElement";
     88      case WSType::PreformattedLineBreak:
     89        return aStream << "WSType::PreformattedLineBreak";
     90      case WSType::OtherBlockBoundary:
     91        return aStream << "WSType::OtherBlockBoundary";
     92      case WSType::CurrentBlockBoundary:
     93        return aStream << "WSType::CurrentBlockBoundary";
     94      case WSType::InlineEditingHostBoundary:
     95        return aStream << "WSType::InlineEditingHostBoundary";
     96    }
     97    return aStream << "<Illegal value>";
     98  }
     99 
    100  friend class WSRunScanner;  // Because of WSType.
    101 
    102  explicit WSScanResult(WSType aReason) : mReason(aReason) {
    103    MOZ_ASSERT(mReason == WSType::UnexpectedError ||
    104               mReason == WSType::NotInitialized);
    105  }
    106 
    107 public:
    108  WSScanResult() = delete;
    109  enum class ScanDirection : bool { Backward, Forward };
    110  WSScanResult(const WSRunScanner& aScanner, ScanDirection aScanDirection,
    111               nsIContent& aContent, WSType aReason)
    112      : mContent(&aContent), mReason(aReason), mDirection(aScanDirection) {
    113    MOZ_ASSERT(aReason != WSType::CollapsibleWhiteSpaces &&
    114               aReason != WSType::NonCollapsibleCharacters &&
    115               aReason != WSType::PreformattedLineBreak);
    116    AssertIfInvalidData(aScanner);
    117  }
    118  WSScanResult(const WSRunScanner& aScanner, ScanDirection aScanDirection,
    119               const EditorDOMPoint& aPoint, WSType aReason)
    120      : mContent(aPoint.GetContainerAs<nsIContent>()),
    121        mOffset(Some(aPoint.Offset())),
    122        mReason(aReason),
    123        mDirection(aScanDirection) {
    124    AssertIfInvalidData(aScanner);
    125  }
    126 
    127  static WSScanResult Error() { return WSScanResult(WSType::UnexpectedError); }
    128 
    129  void AssertIfInvalidData(const WSRunScanner& aScanner) const;
    130 
    131  bool Failed() const {
    132    return mReason == WSType::NotInitialized ||
    133           mReason == WSType::UnexpectedError;
    134  }
    135 
    136  /**
    137   * GetContent() returns found visible and editable content/element.
    138   * See MOZ_ASSERT_IF()s in AssertIfInvalidData() for the detail.
    139   */
    140  nsIContent* GetContent() const { return mContent; }
    141 
    142  [[nodiscard]] bool ContentIsElement() const {
    143    return mContent && mContent->IsElement();
    144  }
    145 
    146  [[nodiscard]] bool ContentIsText() const {
    147    return mContent && mContent->IsText();
    148  }
    149 
    150  /**
    151   * The following accessors makes it easier to understand each callers.
    152   */
    153  MOZ_NEVER_INLINE_DEBUG Element* ElementPtr() const {
    154    MOZ_DIAGNOSTIC_ASSERT(mContent->IsElement());
    155    return mContent->AsElement();
    156  }
    157  MOZ_NEVER_INLINE_DEBUG HTMLBRElement* BRElementPtr() const {
    158    MOZ_DIAGNOSTIC_ASSERT(mContent->IsHTMLElement(nsGkAtoms::br));
    159    return static_cast<HTMLBRElement*>(mContent.get());
    160  }
    161  MOZ_NEVER_INLINE_DEBUG Text* TextPtr() const {
    162    MOZ_DIAGNOSTIC_ASSERT(mContent->IsText());
    163    return mContent->AsText();
    164  }
    165 
    166  template <typename EditorLineBreakType>
    167  MOZ_NEVER_INLINE_DEBUG EditorLineBreakType CreateEditorLineBreak() const {
    168    if (ReachedBRElement()) {
    169      return EditorLineBreakType(*BRElementPtr());
    170    }
    171    if (ReachedPreformattedLineBreak()) {
    172      MOZ_ASSERT_IF(mDirection == ScanDirection::Backward, *mOffset > 0);
    173      return EditorLineBreakType(*TextPtr(),
    174                                 mDirection == ScanDirection::Forward
    175                                     ? mOffset.valueOr(0)
    176                                     : std::max(mOffset.valueOr(1), 1u) - 1);
    177    }
    178    MOZ_CRASH("Didn't reach a line break");
    179    return EditorLineBreakType(*BRElementPtr());
    180  }
    181 
    182  /**
    183   * Returns true if found or reached content is editable.
    184   */
    185  bool IsContentEditable() const { return mContent && mContent->IsEditable(); }
    186 
    187  [[nodiscard]] bool IsContentEditableRoot() const {
    188    return mContent && mContent->IsElement() &&
    189           HTMLEditUtils::ElementIsEditableRoot(*mContent->AsElement());
    190  }
    191 
    192  /**
    193   * Offset_Deprecated() returns meaningful value only when
    194   * InVisibleOrCollapsibleCharacters() returns true or the scanner reached to
    195   * start or end of its scanning range and that is same as start or end
    196   * container which are specified when the scanner is initialized.  If it's
    197   * result of scanning backward, this offset means the point of the found
    198   * point. Otherwise, i.e., scanning forward, this offset means next point
    199   * of the found point.  E.g., if it reaches a collapsible white-space, this
    200   * offset is at the first non-collapsible character after it.
    201   */
    202  MOZ_NEVER_INLINE_DEBUG uint32_t Offset_Deprecated() const {
    203    NS_ASSERTION(mOffset.isSome(), "Retrieved non-meaningful offset");
    204    return mOffset.valueOr(0);
    205  }
    206 
    207  /**
    208   * Point_Deprecated() returns the position in found visible node or reached
    209   * block boundary.  So, this returns meaningful point only when
    210   * Offset_Deprecated() returns meaningful value.
    211   */
    212  template <typename EditorDOMPointType>
    213  EditorDOMPointType Point_Deprecated() const {
    214    NS_ASSERTION(mOffset.isSome(), "Retrieved non-meaningful point");
    215    return EditorDOMPointType(mContent, mOffset.valueOr(0));
    216  }
    217 
    218  /**
    219   * PointAtReachedContent() returns the position of found visible content or
    220   * reached block element.
    221   */
    222  template <typename EditorDOMPointType>
    223  EditorDOMPointType PointAtReachedContent() const {
    224    MOZ_ASSERT(mContent);
    225    switch (mReason) {
    226      case WSType::CollapsibleWhiteSpaces:
    227      case WSType::NonCollapsibleCharacters:
    228      case WSType::PreformattedLineBreak:
    229        MOZ_DIAGNOSTIC_ASSERT(mOffset.isSome());
    230        return mDirection == ScanDirection::Forward
    231                   ? EditorDOMPointType(mContent, mOffset.valueOr(0))
    232                   : EditorDOMPointType(mContent,
    233                                        std::max(mOffset.valueOr(1), 1u) - 1);
    234      default:
    235        return EditorDOMPointType(mContent);
    236    }
    237  }
    238 
    239  /**
    240   * PointAfterReachedContent() returns the next position of found visible
    241   * content or reached block element.
    242   */
    243  template <typename EditorDOMPointType>
    244  EditorDOMPointType PointAfterReachedContent() const {
    245    MOZ_ASSERT(mContent);
    246    return PointAtReachedContent<EditorDOMPointType>()
    247        .template NextPointOrAfterContainer<EditorDOMPointType>();
    248  }
    249 
    250  /**
    251   * Return the next position of found visible content node.  So, this should
    252   * not be used if it reached a visible character middle of a `Text`.
    253   */
    254  template <typename EditorDOMPointType>
    255  EditorDOMPointType PointAfterReachedContentNode() const {
    256    MOZ_ASSERT(mContent);
    257    return EditorDOMPointType::After(*mContent);
    258  }
    259 
    260  /**
    261   * The scanner reached <img> or something which is inline and is not a
    262   * container.
    263   */
    264  bool ReachedSpecialContent() const {
    265    return mReason == WSType::SpecialContent;
    266  }
    267 
    268  /**
    269   * The point is in visible characters or collapsible white-spaces.
    270   */
    271  bool InVisibleOrCollapsibleCharacters() const {
    272    return mReason == WSType::CollapsibleWhiteSpaces ||
    273           mReason == WSType::NonCollapsibleCharacters;
    274  }
    275 
    276  /**
    277   * The point is in collapsible white-spaces.
    278   */
    279  bool InCollapsibleWhiteSpaces() const {
    280    return mReason == WSType::CollapsibleWhiteSpaces;
    281  }
    282 
    283  /**
    284   * The point is in visible non-collapsible characters.
    285   */
    286  bool InNonCollapsibleCharacters() const {
    287    return mReason == WSType::NonCollapsibleCharacters;
    288  }
    289 
    290  /**
    291   * The scanner reached a <br> element.
    292   */
    293  bool ReachedBRElement() const { return mReason == WSType::BRElement; }
    294  bool ReachedVisibleBRElement() const {
    295    return ReachedBRElement() &&
    296           HTMLEditUtils::IsVisibleBRElement(*BRElementPtr());
    297  }
    298  bool ReachedInvisibleBRElement() const {
    299    return ReachedBRElement() &&
    300           HTMLEditUtils::IsInvisibleBRElement(*BRElementPtr());
    301  }
    302 
    303  bool ReachedPreformattedLineBreak() const {
    304    return mReason == WSType::PreformattedLineBreak;
    305  }
    306 
    307  /**
    308   * Return true if reached a <br> element or a preformatted line break.
    309   * Return false when reached a block boundary.  Use ReachedLineBoundary() if
    310   * you want it to return true in the case too.
    311   */
    312  [[nodiscard]] bool ReachedLineBreak() const {
    313    return ReachedBRElement() || ReachedPreformattedLineBreak();
    314  }
    315 
    316  /**
    317   * The scanner reached a <hr> element.
    318   */
    319  bool ReachedHRElement() const {
    320    return mContent && mContent->IsHTMLElement(nsGkAtoms::hr);
    321  }
    322 
    323  /**
    324   * The scanner reached current block boundary or other block element.
    325   */
    326  bool ReachedBlockBoundary() const {
    327    return mReason == WSType::CurrentBlockBoundary ||
    328           mReason == WSType::OtherBlockBoundary;
    329  }
    330 
    331  /**
    332   * The scanner reached current block element boundary.
    333   */
    334  bool ReachedCurrentBlockBoundary() const {
    335    return mReason == WSType::CurrentBlockBoundary;
    336  }
    337 
    338  /**
    339   * The scanner reached other block element.
    340   */
    341  bool ReachedOtherBlockElement() const {
    342    return mReason == WSType::OtherBlockBoundary;
    343  }
    344 
    345  /**
    346   * The scanner reached other block element that isn't editable
    347   */
    348  bool ReachedNonEditableOtherBlockElement() const {
    349    return ReachedOtherBlockElement() && !GetContent()->IsEditable();
    350  }
    351 
    352  /**
    353   * The scanner reached inline editing host boundary.
    354   */
    355  [[nodiscard]] bool ReachedInlineEditingHostBoundary() const {
    356    return mReason == WSType::InlineEditingHostBoundary;
    357  }
    358 
    359  /**
    360   * The scanner reached something non-text node.
    361   */
    362  bool ReachedSomethingNonTextContent() const {
    363    return !InVisibleOrCollapsibleCharacters();
    364  }
    365 
    366  [[nodiscard]] bool ReachedLineBoundary() const {
    367    switch (mReason) {
    368      case WSType::CurrentBlockBoundary:
    369      case WSType::OtherBlockBoundary:
    370      case WSType::BRElement:
    371      case WSType::PreformattedLineBreak:
    372        return true;
    373      default:
    374        return ReachedHRElement();
    375    }
    376  }
    377 
    378  friend std::ostream& operator<<(std::ostream& aStream,
    379                                  const ScanDirection& aDirection) {
    380    return aStream << (aDirection == ScanDirection::Backward
    381                           ? "ScanDirection::Backward"
    382                           : "ScanDirection::Forward");
    383  }
    384 
    385  friend std::ostream& operator<<(std::ostream& aStream,
    386                                  const WSScanResult& aResult) {
    387    aStream << "{ mReason: " << aResult.mReason;
    388    if (aResult.mReason == WSType::NotInitialized ||
    389        aResult.mReason == WSType::InUncomposedDoc) {
    390      return aStream << " }";
    391    }
    392    return aStream << ", mContent: " << aResult.mContent
    393                   << ", mOffset: " << aResult.mOffset
    394                   << ", mDirection: " << aResult.mDirection << " }";
    395  }
    396 
    397 private:
    398  nsCOMPtr<nsIContent> mContent;
    399  Maybe<uint32_t> mOffset;
    400  WSType mReason;
    401  ScanDirection mDirection = ScanDirection::Backward;
    402 };
    403 
    404 class MOZ_STACK_CLASS WSRunScanner final {
    405 private:
    406  using Element = dom::Element;
    407  using HTMLBRElement = dom::HTMLBRElement;
    408  using Text = dom::Text;
    409 
    410 public:
    411  using WSType = WSScanResult::WSType;
    412 
    413  enum class IgnoreNonEditableNodes : bool { No, Yes };
    414  enum class StopAtNonEditableNode : bool { No, Yes };
    415  enum class ReferHTMLDefaultStyle : bool { No, Yes };
    416  enum class Option {
    417    // If set, return only editable content or return non-editable content as a
    418    // special content in the closest editing host if the scan start point is
    419    // editable.
    420    OnlyEditableNodes,
    421    // If set, use the HTML default style to consider whether the found one is a
    422    // block or an inline.
    423    ReferHTMLDefaultStyle,
    424    // If set, stop scanning the DOM when it reaches a `Comment` node.
    425    StopAtComment,
    426  };
    427  using Options = EnumSet<Option>;
    428 
    429  [[nodiscard]] constexpr static IgnoreNonEditableNodes
    430  ShouldIgnoreNonEditableSiblingsOrDescendants(
    431      Options aOptions  // NOLINT(performance-unnecessary-value-param)
    432  ) {
    433    return static_cast<IgnoreNonEditableNodes>(
    434        aOptions.contains(Option::OnlyEditableNodes));
    435  }
    436  [[nodiscard]] constexpr static StopAtNonEditableNode
    437  ShouldStopAtNonEditableNode(
    438      Options aOptions  // NOLINT(performance-unnecessary-value-param)
    439  ) {
    440    return static_cast<StopAtNonEditableNode>(
    441        aOptions.contains(Option::OnlyEditableNodes));
    442  }
    443 
    444  [[nodiscard]] constexpr static ReferHTMLDefaultStyle
    445  ShouldReferHTMLDefaultStyle(
    446      Options aOptions  // NOLINT(performance-unnecessary-value-param)
    447  ) {
    448    return static_cast<ReferHTMLDefaultStyle>(
    449        aOptions.contains(Option::ReferHTMLDefaultStyle));
    450  }
    451 
    452  template <typename EditorDOMPointType>
    453  WSRunScanner(Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    454               const EditorDOMPointType& aScanStartPoint,
    455               const Element* aAncestorLimiter = nullptr)
    456      : mScanStartPoint(aScanStartPoint.template To<EditorDOMPoint>()),
    457        mTextFragmentDataAtStart(aOptions, mScanStartPoint, aAncestorLimiter) {}
    458 
    459  // ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom() returns the first visible
    460  // node at or after aPoint.  If there is no visible nodes after aPoint,
    461  // returns topmost editable inline ancestor at end of current block.  See
    462  // comments around WSScanResult for the detail.  When you reach a character,
    463  // this returns WSScanResult both whose Point_Deprecated() and
    464  // PointAtReachedContent() return the found character position.
    465  template <typename PT, typename CT>
    466  WSScanResult ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
    467      const EditorDOMPointBase<PT, CT>& aPoint) const;
    468  template <typename PT, typename CT>
    469  static WSScanResult ScanInclusiveNextVisibleNodeOrBlockBoundary(
    470      Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    471      const EditorDOMPointBase<PT, CT>& aPoint,
    472      const Element* aAncestorLimiter = nullptr) {
    473    return WSRunScanner(aOptions, aPoint, aAncestorLimiter)
    474        .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aPoint);
    475  }
    476 
    477  // ScanPreviousVisibleNodeOrBlockBoundaryFrom() returns the first visible node
    478  // before aPoint. If there is no visible nodes before aPoint, returns topmost
    479  // editable inline ancestor at start of current block.  See comments around
    480  // WSScanResult for the detail.  When you reach a character, this returns
    481  // WSScanResult whose Point_Deprecated() returns next point of the found
    482  // character and PointAtReachedContent() returns the point at found character.
    483  template <typename PT, typename CT>
    484  WSScanResult ScanPreviousVisibleNodeOrBlockBoundaryFrom(
    485      const EditorDOMPointBase<PT, CT>& aPoint) const;
    486  template <typename PT, typename CT>
    487  static WSScanResult ScanPreviousVisibleNodeOrBlockBoundary(
    488      Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    489      const EditorDOMPointBase<PT, CT>& aPoint,
    490      const Element* aAncestorLimiter = nullptr) {
    491    return WSRunScanner(aOptions, aPoint, aAncestorLimiter)
    492        .ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPoint);
    493  }
    494 
    495  /**
    496   * Return a point in a `Text` node which is at current character or next
    497   * character if aPoint does not points a character or end of a `Text` node.
    498   */
    499  template <typename EditorDOMPointType, typename PT, typename CT>
    500  static EditorDOMPointType GetInclusiveNextCharPoint(
    501      Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    502      const EditorDOMPointBase<PT, CT>& aPoint,
    503      const Element* aAncestorLimiter = nullptr) {
    504    if (aPoint.IsInTextNode() && !aPoint.IsEndOfContainer() &&
    505        (!aOptions.contains(Option::OnlyEditableNodes) ||
    506         HTMLEditUtils::IsSimplyEditableNode(
    507             *aPoint.template ContainerAs<Text>()))) {
    508      return EditorDOMPointType(aPoint.template ContainerAs<Text>(),
    509                                aPoint.Offset());
    510    }
    511    return WSRunScanner(aOptions, aPoint, aAncestorLimiter)
    512        .GetInclusiveNextCharPoint<EditorDOMPointType>(aPoint);
    513  }
    514 
    515  /**
    516   * Return a point in a `Text` node which is before aPoint.
    517   */
    518  template <typename EditorDOMPointType, typename PT, typename CT>
    519  static EditorDOMPointType GetPreviousCharPoint(
    520      Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    521      const EditorDOMPointBase<PT, CT>& aPoint,
    522      const Element* aAncestorLimiter = nullptr) {
    523    if (aPoint.IsInTextNode() && !aPoint.IsStartOfContainer() &&
    524        (!aOptions.contains(Option::OnlyEditableNodes) ||
    525         HTMLEditUtils::IsSimplyEditableNode(
    526             *aPoint.template ContainerAs<Text>()))) {
    527      return EditorDOMPointType(aPoint.template ContainerAs<Text>(),
    528                                aPoint.Offset() - 1);
    529    }
    530    return WSRunScanner(aOptions, aPoint, aAncestorLimiter)
    531        .GetPreviousCharPoint<EditorDOMPointType>(aPoint);
    532  }
    533 
    534  /**
    535   * Scan aTextNode from end or start to find last or first visible things.
    536   * I.e., this returns a point immediately before or after invisible
    537   * white-spaces of aTextNode if aTextNode ends or begins with some invisible
    538   * white-spaces.
    539   * Note that the result may not be in different text node if aTextNode has
    540   * only invisible white-spaces and there is previous or next text node.
    541   */
    542  template <typename EditorDOMPointType>
    543  static EditorDOMPointType GetAfterLastVisiblePoint(
    544      Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    545      Text& aTextNode, const Element* aAncestorLimiter = nullptr);
    546  template <typename EditorDOMPointType>
    547  static EditorDOMPointType GetFirstVisiblePoint(
    548      Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    549      Text& aTextNode, const Element* aAncestorLimiter = nullptr);
    550 
    551  /**
    552   * GetRangeInTextNodesToForwardDeleteFrom() returns the range to remove
    553   * text when caret is at aPoint.
    554   */
    555  static Result<EditorDOMRangeInTexts, nsresult>
    556  GetRangeInTextNodesToForwardDeleteFrom(
    557      Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    558      const EditorDOMPoint& aPoint, const Element* aAncestorLimiter = nullptr);
    559 
    560  /**
    561   * GetRangeInTextNodesToBackspaceFrom() returns the range to remove text
    562   * when caret is at aPoint.
    563   */
    564  static Result<EditorDOMRangeInTexts, nsresult>
    565  GetRangeInTextNodesToBackspaceFrom(
    566      Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    567      const EditorDOMPoint& aPoint, const Element* aAncestorLimiter = nullptr);
    568 
    569  /**
    570   * GetRangesForDeletingAtomicContent() returns the range to delete
    571   * aAtomicContent.  If it's followed by invisible white-spaces, they will
    572   * be included into the range.
    573   */
    574  static EditorDOMRange GetRangesForDeletingAtomicContent(
    575      Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    576      const nsIContent& aAtomicContent,
    577      const Element* aAncestorLimiter = nullptr);
    578 
    579  /**
    580   * GetRangeForDeleteBlockElementBoundaries() returns a range starting from end
    581   * of aLeftBlockElement to start of aRightBlockElement and extend invisible
    582   * white-spaces around them.
    583   *
    584   * @param aLeftBlockElement   The block element which will be joined with
    585   *                            aRightBlockElement.
    586   * @param aRightBlockElement  The block element which will be joined with
    587   *                            aLeftBlockElement.  This must be an element
    588   *                            after aLeftBlockElement.
    589   * @param aPointContainingTheOtherBlock
    590   *                            When aRightBlockElement is an ancestor of
    591   *                            aLeftBlockElement, this must be set and the
    592   *                            container must be aRightBlockElement.
    593   *                            When aLeftBlockElement is an ancestor of
    594   *                            aRightBlockElement, this must be set and the
    595   *                            container must be aLeftBlockElement.
    596   *                            Otherwise, must not be set.
    597   */
    598  static EditorDOMRange GetRangeForDeletingBlockElementBoundaries(
    599      Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    600      const Element& aLeftBlockElement, const Element& aRightBlockElement,
    601      const EditorDOMPoint& aPointContainingTheOtherBlock,
    602      const Element* aAncestorLimiter = nullptr);
    603 
    604  /**
    605   * ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() may shrink aRange if it
    606   * starts and/or ends with an atomic content, but the range boundary
    607   * is in adjacent text nodes.  Returns true if this modifies the range.
    608   */
    609  static Result<bool, nsresult> ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
    610      Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    611      nsRange& aRange, const Element* aAncestorLimiter = nullptr);
    612 
    613  /**
    614   * GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries() returns
    615   * extended range if range boundaries of aRange are in invisible white-spaces.
    616   */
    617  static EditorDOMRange GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
    618      Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    619      const EditorDOMRange& aRange, const Element* aAncestorLimiter = nullptr);
    620 
    621  /**
    622   * GetPrecedingBRElementUnlessVisibleContentFound() scans a `<br>` element
    623   * backward, but stops scanning it if the scanner finds visible character
    624   * or something.  In other words, this method ignores only invisible
    625   * white-spaces between `<br>` element and aPoint.
    626   */
    627  template <typename EditorDOMPointType>
    628  MOZ_NEVER_INLINE_DEBUG static HTMLBRElement*
    629  GetPrecedingBRElementUnlessVisibleContentFound(
    630      Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    631      const EditorDOMPointType& aPoint,
    632      const Element* aAncestorLimiter = nullptr) {
    633    MOZ_ASSERT(aPoint.IsSetAndValid());
    634    // XXX This method behaves differently even in similar point.
    635    //     If aPoint is in a text node following `<br>` element, reaches the
    636    //     `<br>` element when all characters between the `<br>` and
    637    //     aPoint are ASCII whitespaces.
    638    //     But if aPoint is not in a text node, e.g., at start of an inline
    639    //     element which is immediately after a `<br>` element, returns the
    640    //     `<br>` element even if there is no invisible white-spaces.
    641    if (aPoint.IsStartOfContainer()) {
    642      return nullptr;
    643    }
    644    // TODO: Scan for end boundary is redundant in this case, we should optimize
    645    //       it.
    646    TextFragmentData textFragmentData(aOptions, aPoint, aAncestorLimiter);
    647    return textFragmentData.StartsFromBRElement()
    648               ? textFragmentData.StartReasonBRElementPtr()
    649               : nullptr;
    650  }
    651 
    652  [[nodiscard]] constexpr Options ScanOptions() const {
    653    return mTextFragmentDataAtStart.ScanOptions();
    654  }
    655  [[nodiscard]] bool ReferredHTMLDefaultStyle() const {
    656    return mTextFragmentDataAtStart.ReferredHTMLDefaultStyle();
    657  }
    658 
    659  const EditorDOMPoint& ScanStartRef() const { return mScanStartPoint; }
    660 
    661 protected:
    662  using EditorType = EditorBase::EditorType;
    663 
    664  class TextFragmentData;
    665 
    666  // VisibleWhiteSpacesData represents 0 or more visible white-spaces.
    667  class MOZ_STACK_CLASS VisibleWhiteSpacesData final {
    668   public:
    669    bool IsInitialized() const {
    670      return mLeftWSType != WSType::NotInitialized ||
    671             mRightWSType != WSType::NotInitialized;
    672    }
    673 
    674    EditorDOMPoint StartRef() const { return mStartPoint; }
    675    EditorDOMPoint EndRef() const { return mEndPoint; }
    676 
    677    /**
    678     * Information why the white-spaces start from (i.e., this indicates the
    679     * previous content type of the fragment).
    680     */
    681    bool StartsFromNonCollapsibleCharacters() const {
    682      return mLeftWSType == WSType::NonCollapsibleCharacters;
    683    }
    684    bool StartsFromSpecialContent() const {
    685      return mLeftWSType == WSType::SpecialContent;
    686    }
    687    bool StartsFromPreformattedLineBreak() const {
    688      return mLeftWSType == WSType::PreformattedLineBreak;
    689    }
    690 
    691    /**
    692     * Information why the white-spaces end by (i.e., this indicates the
    693     * next content type of the fragment).
    694     */
    695    bool EndsByNonCollapsibleCharacters() const {
    696      return mRightWSType == WSType::NonCollapsibleCharacters;
    697    }
    698    bool EndsByTrailingWhiteSpaces() const {
    699      return mRightWSType == WSType::TrailingWhiteSpaces;
    700    }
    701    bool EndsBySpecialContent() const {
    702      return mRightWSType == WSType::SpecialContent;
    703    }
    704    bool EndsByBRElement() const { return mRightWSType == WSType::BRElement; }
    705    bool EndsByPreformattedLineBreak() const {
    706      return mRightWSType == WSType::PreformattedLineBreak;
    707    }
    708    bool EndsByBlockBoundary() const {
    709      return mRightWSType == WSType::CurrentBlockBoundary ||
    710             mRightWSType == WSType::OtherBlockBoundary;
    711    }
    712    bool EndsByInlineEditingHostBoundary() const {
    713      return mRightWSType == WSType::InlineEditingHostBoundary;
    714    }
    715 
    716    /**
    717     * ComparePoint() compares aPoint with the white-spaces.
    718     */
    719    enum class PointPosition {
    720      BeforeStartOfFragment,
    721      StartOfFragment,
    722      MiddleOfFragment,
    723      EndOfFragment,
    724      AfterEndOfFragment,
    725      NotInSameDOMTree,
    726    };
    727    template <typename EditorDOMPointType>
    728    PointPosition ComparePoint(const EditorDOMPointType& aPoint) const {
    729      MOZ_ASSERT(aPoint.IsSetAndValid());
    730      if (StartRef() == aPoint) {
    731        return PointPosition::StartOfFragment;
    732      }
    733      if (EndRef() == aPoint) {
    734        return PointPosition::EndOfFragment;
    735      }
    736      const bool startIsBeforePoint = StartRef().IsBefore(aPoint);
    737      const bool pointIsBeforeEnd = aPoint.IsBefore(EndRef());
    738      if (startIsBeforePoint && pointIsBeforeEnd) {
    739        return PointPosition::MiddleOfFragment;
    740      }
    741      if (startIsBeforePoint) {
    742        return PointPosition::AfterEndOfFragment;
    743      }
    744      if (pointIsBeforeEnd) {
    745        return PointPosition::BeforeStartOfFragment;
    746      }
    747      return PointPosition::NotInSameDOMTree;
    748    }
    749 
    750   private:
    751    // Initializers should be accessible only from `TextFragmentData`.
    752    friend class WSRunScanner::TextFragmentData;
    753    VisibleWhiteSpacesData()
    754        : mLeftWSType(WSType::NotInitialized),
    755          mRightWSType(WSType::NotInitialized) {}
    756 
    757    template <typename EditorDOMPointType>
    758    void SetStartPoint(const EditorDOMPointType& aStartPoint) {
    759      mStartPoint = aStartPoint;
    760    }
    761    template <typename EditorDOMPointType>
    762    void SetEndPoint(const EditorDOMPointType& aEndPoint) {
    763      mEndPoint = aEndPoint;
    764    }
    765    void SetStartFrom(WSType aLeftWSType) { mLeftWSType = aLeftWSType; }
    766    void SetStartFromLeadingWhiteSpaces() {
    767      mLeftWSType = WSType::LeadingWhiteSpaces;
    768    }
    769    void SetEndBy(WSType aRightWSType) { mRightWSType = aRightWSType; }
    770    void SetEndByTrailingWhiteSpaces() {
    771      mRightWSType = WSType::TrailingWhiteSpaces;
    772    }
    773 
    774    EditorDOMPoint mStartPoint;
    775    EditorDOMPoint mEndPoint;
    776    WSType mLeftWSType, mRightWSType;
    777  };
    778 
    779  using PointPosition = VisibleWhiteSpacesData::PointPosition;
    780 
    781  /**
    782   * Return aPoint if it points a character in a `Text` node, or start of next
    783   * `Text` node otherwise.
    784   * FYI: For the performance, this does not check whether given container is
    785   * not after mStart.mReasonContent or not.
    786   */
    787  template <typename EditorDOMPointType, typename PT, typename CT>
    788  EditorDOMPointType GetInclusiveNextCharPoint(
    789      const EditorDOMPointBase<PT, CT>& aPoint) const {
    790    return TextFragmentDataAtStartRef()
    791        .GetInclusiveNextCharPoint<EditorDOMPointType>(
    792            aPoint, ShouldIgnoreNonEditableSiblingsOrDescendants(
    793                        mTextFragmentDataAtStart.ScanOptions()));
    794  }
    795 
    796  /**
    797   * Return the previous editable point in a `Text` node.  Note that this
    798   * returns the last character point when it meets non-empty text node,
    799   * otherwise, returns a point in an empty text node.
    800   * FYI: For the performance, this does not check whether given container is
    801   * not before mEnd.mReasonContent or not.
    802   */
    803  template <typename EditorDOMPointType, typename PT, typename CT>
    804  EditorDOMPointType GetPreviousCharPoint(
    805      const EditorDOMPointBase<PT, CT>& aPoint) const {
    806    return TextFragmentDataAtStartRef()
    807        .GetPreviousCharPoint<EditorDOMPointType>(
    808            aPoint, ShouldIgnoreNonEditableSiblingsOrDescendants(
    809                        mTextFragmentDataAtStart.ScanOptions()));
    810  }
    811 
    812  /**
    813   * GetEndOfCollapsibleASCIIWhiteSpaces() returns the next visible char
    814   * (meaning a character except ASCII white-spaces) point or end of last text
    815   * node scanning from aPointAtASCIIWhiteSpace.
    816   * Note that this may return different text node from the container of
    817   * aPointAtASCIIWhiteSpace.
    818   */
    819  template <typename EditorDOMPointType>
    820  EditorDOMPointType GetEndOfCollapsibleASCIIWhiteSpaces(
    821      const EditorDOMPointInText& aPointAtASCIIWhiteSpace,
    822      nsIEditor::EDirection aDirectionToDelete) const {
    823    MOZ_ASSERT(aDirectionToDelete == nsIEditor::eNone ||
    824               aDirectionToDelete == nsIEditor::eNext ||
    825               aDirectionToDelete == nsIEditor::ePrevious);
    826    return TextFragmentDataAtStartRef()
    827        .GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointType>(
    828            aPointAtASCIIWhiteSpace, aDirectionToDelete);
    829  }
    830 
    831  /**
    832   * GetFirstASCIIWhiteSpacePointCollapsedTo() returns the first ASCII
    833   * white-space which aPointAtASCIIWhiteSpace belongs to.  In other words,
    834   * the white-space at aPointAtASCIIWhiteSpace should be collapsed into
    835   * the result.
    836   * Note that this may return different text node from the container of
    837   * aPointAtASCIIWhiteSpace.
    838   */
    839  template <typename EditorDOMPointType>
    840  EditorDOMPointType GetFirstASCIIWhiteSpacePointCollapsedTo(
    841      const EditorDOMPointInText& aPointAtASCIIWhiteSpace,
    842      nsIEditor::EDirection aDirectionToDelete) const {
    843    MOZ_ASSERT(aDirectionToDelete == nsIEditor::eNone ||
    844               aDirectionToDelete == nsIEditor::eNext ||
    845               aDirectionToDelete == nsIEditor::ePrevious);
    846    return TextFragmentDataAtStartRef()
    847        .GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointType>(
    848            aPointAtASCIIWhiteSpace, aDirectionToDelete);
    849  }
    850 
    851  /**
    852   * TextFragmentData stores the information of white-space sequence which
    853   * contains `aPoint` of the constructor.
    854   */
    855  class MOZ_STACK_CLASS TextFragmentData final {
    856   private:
    857    class NoBreakingSpaceData;
    858    class MOZ_STACK_CLASS BoundaryData final {
    859     public:
    860      using NoBreakingSpaceData =
    861          WSRunScanner::TextFragmentData::NoBreakingSpaceData;
    862 
    863      /**
    864       * ScanCollapsibleWhiteSpaceStartFrom() returns start boundary data of
    865       * white-spaces containing aPoint.  When aPoint is in a text node and
    866       * points a non-white-space character or the text node is preformatted,
    867       * this returns the data at aPoint.
    868       *
    869       * @param aPoint            Scan start point.
    870       * @param aNBSPData         Optional.  If set, this recodes first and last
    871       *                          NBSP positions.
    872       */
    873      template <typename EditorDOMPointType>
    874      static BoundaryData ScanCollapsibleWhiteSpaceStartFrom(
    875          Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    876          const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData,
    877          const Element& aAncestorLimiter);
    878 
    879      /**
    880       * ScanCollapsibleWhiteSpaceEndFrom() returns end boundary data of
    881       * white-spaces containing aPoint.  When aPoint is in a text node and
    882       * points a non-white-space character or the text node is preformatted,
    883       * this returns the data at aPoint.
    884       *
    885       * @param aPoint            Scan start point.
    886       * @param aNBSPData         Optional.  If set, this recodes first and last
    887       *                          NBSP positions.
    888       */
    889      template <typename EditorDOMPointType>
    890      static BoundaryData ScanCollapsibleWhiteSpaceEndFrom(
    891          Options aOptions,  // NOLINT(performance-unnecessary-value-param)
    892          const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData,
    893          const Element& aAncestorLimiter);
    894 
    895      BoundaryData() = default;
    896      template <typename EditorDOMPointType>
    897      BoundaryData(const EditorDOMPointType& aPoint, nsIContent& aReasonContent,
    898                   WSType aReason)
    899          : mReasonContent(&aReasonContent),
    900            mPoint(aPoint.template To<EditorDOMPoint>()),
    901            mReason(aReason) {}
    902      bool Initialized() const { return mReasonContent && mPoint.IsSet(); }
    903 
    904      nsIContent* GetReasonContent() const { return mReasonContent; }
    905      const EditorDOMPoint& PointRef() const { return mPoint; }
    906      WSType RawReason() const { return mReason; }
    907 
    908      bool IsNonCollapsibleCharacters() const {
    909        return mReason == WSType::NonCollapsibleCharacters;
    910      }
    911      bool IsSpecialContent() const {
    912        return mReason == WSType::SpecialContent;
    913      }
    914      bool IsBRElement() const { return mReason == WSType::BRElement; }
    915      bool IsPreformattedLineBreak() const {
    916        return mReason == WSType::PreformattedLineBreak;
    917      }
    918      bool IsCurrentBlockBoundary() const {
    919        return mReason == WSType::CurrentBlockBoundary;
    920      }
    921      bool IsOtherBlockBoundary() const {
    922        return mReason == WSType::OtherBlockBoundary;
    923      }
    924      bool IsBlockBoundary() const {
    925        return mReason == WSType::CurrentBlockBoundary ||
    926               mReason == WSType::OtherBlockBoundary;
    927      }
    928      bool IsInlineEditingHostBoundary() const {
    929        return mReason == WSType::InlineEditingHostBoundary;
    930      }
    931      bool IsHardLineBreak() const {
    932        return mReason == WSType::CurrentBlockBoundary ||
    933               mReason == WSType::OtherBlockBoundary ||
    934               mReason == WSType::BRElement ||
    935               mReason == WSType::PreformattedLineBreak;
    936      }
    937      MOZ_NEVER_INLINE_DEBUG Element* OtherBlockElementPtr() const {
    938        MOZ_DIAGNOSTIC_ASSERT(mReasonContent->IsElement());
    939        return mReasonContent->AsElement();
    940      }
    941      MOZ_NEVER_INLINE_DEBUG HTMLBRElement* BRElementPtr() const {
    942        MOZ_DIAGNOSTIC_ASSERT(mReasonContent->IsHTMLElement(nsGkAtoms::br));
    943        return static_cast<HTMLBRElement*>(mReasonContent.get());
    944      }
    945 
    946     private:
    947      /**
    948       * Helper methods of ScanCollapsibleWhiteSpaceStartFrom() and
    949       * ScanCollapsibleWhiteSpaceEndFrom() when they need to scan in a text
    950       * node.
    951       */
    952      template <typename EditorDOMPointType>
    953      static Maybe<BoundaryData> ScanCollapsibleWhiteSpaceStartInTextNode(
    954          const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData);
    955      template <typename EditorDOMPointType>
    956      static Maybe<BoundaryData> ScanCollapsibleWhiteSpaceEndInTextNode(
    957          const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData);
    958 
    959      nsCOMPtr<nsIContent> mReasonContent;
    960      EditorDOMPoint mPoint;
    961      // Must be one of WSType::NotInitialized,
    962      // WSType::NonCollapsibleCharacters, WSType::SpecialContent,
    963      // WSType::BRElement, WSType::CurrentBlockBoundary,
    964      // WSType::OtherBlockBoundary or WSType::InlineEditingHostBoundary.
    965      WSType mReason = WSType::NotInitialized;
    966    };
    967 
    968    class MOZ_STACK_CLASS NoBreakingSpaceData final {
    969     public:
    970      enum class Scanning { Forward, Backward };
    971      void NotifyNBSP(const EditorDOMPointInText& aPoint,
    972                      Scanning aScanningDirection) {
    973        MOZ_ASSERT(aPoint.IsSetAndValid());
    974        MOZ_ASSERT(aPoint.IsCharNBSP());
    975        if (!mFirst.IsSet() || aScanningDirection == Scanning::Backward) {
    976          mFirst = aPoint;
    977        }
    978        if (!mLast.IsSet() || aScanningDirection == Scanning::Forward) {
    979          mLast = aPoint;
    980        }
    981      }
    982 
    983      const EditorDOMPointInText& FirstPointRef() const { return mFirst; }
    984      const EditorDOMPointInText& LastPointRef() const { return mLast; }
    985 
    986      bool FoundNBSP() const {
    987        MOZ_ASSERT(mFirst.IsSet() == mLast.IsSet());
    988        return mFirst.IsSet();
    989      }
    990 
    991     private:
    992      EditorDOMPointInText mFirst;
    993      EditorDOMPointInText mLast;
    994    };
    995 
    996   public:
    997    TextFragmentData() = delete;
    998 
    999    /**
   1000     * If aScanMode is Scan::EditableNodes and aPoint is in an editable node,
   1001     * this scans only in the editing host.  Therefore, it's same as that
   1002     * aAncestorLimiter is specified to the editing host.
   1003     */
   1004    template <typename EditorDOMPointType>
   1005    TextFragmentData(
   1006        Options aOptions,  // NOLINT(performance-unnecessary-value-param)
   1007        const EditorDOMPointType& aPoint,
   1008        const Element* aAncestorLimiter = nullptr);
   1009 
   1010    bool IsInitialized() const {
   1011      return mStart.Initialized() && mEnd.Initialized();
   1012    }
   1013 
   1014    [[nodiscard]] constexpr Options ScanOptions() const { return mOptions; }
   1015    [[nodiscard]] bool ReferredHTMLDefaultStyle() const {
   1016      return mOptions.contains(Option::ReferHTMLDefaultStyle);
   1017    }
   1018 
   1019    const Element* GetAncestorLimiter() const { return mAncestorLimiter; }
   1020 
   1021    nsIContent* GetStartReasonContent() const {
   1022      return mStart.GetReasonContent();
   1023    }
   1024    nsIContent* GetEndReasonContent() const { return mEnd.GetReasonContent(); }
   1025 
   1026    bool StartsFromNonCollapsibleCharacters() const {
   1027      return mStart.IsNonCollapsibleCharacters();
   1028    }
   1029    bool StartsFromSpecialContent() const { return mStart.IsSpecialContent(); }
   1030    bool StartsFromBRElement() const { return mStart.IsBRElement(); }
   1031    bool StartsFromVisibleBRElement() const {
   1032      return StartsFromBRElement() &&
   1033             HTMLEditUtils::IsVisibleBRElement(*GetStartReasonContent());
   1034    }
   1035    bool StartsFromInvisibleBRElement() const {
   1036      return StartsFromBRElement() &&
   1037             HTMLEditUtils::IsInvisibleBRElement(*GetStartReasonContent());
   1038    }
   1039    bool StartsFromPreformattedLineBreak() const {
   1040      return mStart.IsPreformattedLineBreak();
   1041    }
   1042    bool StartsFromCurrentBlockBoundary() const {
   1043      return mStart.IsCurrentBlockBoundary();
   1044    }
   1045    bool StartsFromOtherBlockElement() const {
   1046      return mStart.IsOtherBlockBoundary();
   1047    }
   1048    bool StartsFromBlockBoundary() const { return mStart.IsBlockBoundary(); }
   1049    bool StartsFromInlineEditingHostBoundary() const {
   1050      return mStart.IsInlineEditingHostBoundary();
   1051    }
   1052    bool StartsFromHardLineBreak() const { return mStart.IsHardLineBreak(); }
   1053    bool EndsByNonCollapsibleCharacters() const {
   1054      return mEnd.IsNonCollapsibleCharacters();
   1055    }
   1056    bool EndsBySpecialContent() const { return mEnd.IsSpecialContent(); }
   1057    bool EndsByBRElement() const { return mEnd.IsBRElement(); }
   1058    bool EndsByVisibleBRElement() const {
   1059      return EndsByBRElement() &&
   1060             HTMLEditUtils::IsVisibleBRElement(*GetEndReasonContent());
   1061    }
   1062    bool EndsByInvisibleBRElement() const {
   1063      return EndsByBRElement() &&
   1064             HTMLEditUtils::IsInvisibleBRElement(*GetEndReasonContent());
   1065    }
   1066    bool EndsByPreformattedLineBreak() const {
   1067      return mEnd.IsPreformattedLineBreak();
   1068    }
   1069    bool EndsByInvisiblePreformattedLineBreak() const {
   1070      return mEnd.IsPreformattedLineBreak() &&
   1071             HTMLEditUtils::IsInvisiblePreformattedNewLine(mEnd.PointRef());
   1072    }
   1073    bool EndsByCurrentBlockBoundary() const {
   1074      return mEnd.IsCurrentBlockBoundary();
   1075    }
   1076    bool EndsByOtherBlockElement() const { return mEnd.IsOtherBlockBoundary(); }
   1077    bool EndsByBlockBoundary() const { return mEnd.IsBlockBoundary(); }
   1078    bool EndsByInlineEditingHostBoundary() const {
   1079      return mEnd.IsInlineEditingHostBoundary();
   1080    }
   1081 
   1082    WSType StartRawReason() const { return mStart.RawReason(); }
   1083    WSType EndRawReason() const { return mEnd.RawReason(); }
   1084 
   1085    MOZ_NEVER_INLINE_DEBUG Element* StartReasonOtherBlockElementPtr() const {
   1086      return mStart.OtherBlockElementPtr();
   1087    }
   1088    MOZ_NEVER_INLINE_DEBUG HTMLBRElement* StartReasonBRElementPtr() const {
   1089      return mStart.BRElementPtr();
   1090    }
   1091    MOZ_NEVER_INLINE_DEBUG Element* EndReasonOtherBlockElementPtr() const {
   1092      return mEnd.OtherBlockElementPtr();
   1093    }
   1094    MOZ_NEVER_INLINE_DEBUG HTMLBRElement* EndReasonBRElementPtr() const {
   1095      return mEnd.BRElementPtr();
   1096    }
   1097 
   1098    const EditorDOMPoint& StartRef() const { return mStart.PointRef(); }
   1099    const EditorDOMPoint& EndRef() const { return mEnd.PointRef(); }
   1100 
   1101    const EditorDOMPoint& ScanStartRef() const { return mScanStartPoint; }
   1102 
   1103    bool FoundNoBreakingWhiteSpaces() const { return mNBSPData.FoundNBSP(); }
   1104    const EditorDOMPointInText& FirstNBSPPointRef() const {
   1105      return mNBSPData.FirstPointRef();
   1106    }
   1107    const EditorDOMPointInText& LastNBSPPointRef() const {
   1108      return mNBSPData.LastPointRef();
   1109    }
   1110 
   1111    /**
   1112     * Return inclusive next point in inclusive next `Text` node from aPoint.
   1113     * So, it may be in a collapsed white-space or invisible white-spaces.
   1114     * NOTE: Option::OnlyEditableNodes is ignored because it's treated as "stop
   1115     * at non-editable content" in the other places, but this "ignores" them.
   1116     */
   1117    template <typename EditorDOMPointType, typename PT, typename CT>
   1118    [[nodiscard]] static EditorDOMPointType GetInclusiveNextCharPoint(
   1119        const EditorDOMPointBase<PT, CT>& aPoint,
   1120        Options aOptions,  // NOLINT(performance-unnecessary-value-param)
   1121        IgnoreNonEditableNodes aIgnoreNonEditableNodes,
   1122        const nsIContent* aFollowingLimiterContent = nullptr);
   1123 
   1124    template <typename EditorDOMPointType, typename PT, typename CT>
   1125    [[nodiscard]] EditorDOMPointType GetInclusiveNextCharPoint(
   1126        const EditorDOMPointBase<PT, CT>& aPoint,
   1127        IgnoreNonEditableNodes aIgnoreNonEditableNodes) const {
   1128      return GetInclusiveNextCharPoint<EditorDOMPointType>(
   1129          aPoint, mOptions, aIgnoreNonEditableNodes, GetEndReasonContent());
   1130    }
   1131 
   1132    /**
   1133     * Return previous point in inclusive previous `Text` node from aPoint.
   1134     * So, it may be in a collapsed white-space or invisible white-spaces.
   1135     * NOTE: Option::OnlyEditableNodes is ignored because it's treated as "stop
   1136     * at non-editable content" in the other places, but this "ignores" them.
   1137     */
   1138    template <typename EditorDOMPointType, typename PT, typename CT>
   1139    [[nodiscard]] static EditorDOMPointType GetPreviousCharPoint(
   1140        const EditorDOMPointBase<PT, CT>& aPoint,
   1141        Options aOptions,  // NOLINT(performance-unnecessary-value-param)
   1142        IgnoreNonEditableNodes aIgnoreNonEditableNodes,
   1143        const nsIContent* aPrecedingLimiterContent = nullptr);
   1144 
   1145    template <typename EditorDOMPointType, typename PT, typename CT>
   1146    [[nodiscard]] EditorDOMPointType GetPreviousCharPoint(
   1147        const EditorDOMPointBase<PT, CT>& aPoint,
   1148        IgnoreNonEditableNodes aIgnoreNonEditableNodes) const {
   1149      return GetPreviousCharPoint<EditorDOMPointType>(
   1150          aPoint, mOptions, aIgnoreNonEditableNodes, GetStartReasonContent());
   1151    }
   1152 
   1153    /**
   1154     * Return end of current collapsible ASCII white-spaces.
   1155     * NOTE: Option::OnlyEditableNodes is ignored because it's treated as "stop
   1156     * at non-editable content" in the other places, but this "ignores" them.
   1157     *
   1158     * @param aPointAtASCIIWhiteSpace   Must be in a sequence of collapsible
   1159     *                                  ASCII white-spaces.
   1160     * @param aDirectionToDelete        The direction to delete.
   1161     */
   1162    template <typename EditorDOMPointType>
   1163    [[nodiscard]] static EditorDOMPointType GetEndOfCollapsibleASCIIWhiteSpaces(
   1164        const EditorDOMPointInText& aPointAtASCIIWhiteSpace,
   1165        nsIEditor::EDirection aDirectionToDelete,
   1166        Options aOptions,  // NOLINT(performance-unnecessary-value-param)
   1167        IgnoreNonEditableNodes aIgnoreNonEditableNodes,
   1168        const nsIContent* aFollowingLimiterContent = nullptr);
   1169 
   1170    template <typename EditorDOMPointType>
   1171    [[nodiscard]] EditorDOMPointType GetEndOfCollapsibleASCIIWhiteSpaces(
   1172        const EditorDOMPointInText& aPointAtASCIIWhiteSpace,
   1173        nsIEditor::EDirection aDirectionToDelete,
   1174        IgnoreNonEditableNodes aIgnoreNonEditableNodes) const {
   1175      return GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointType>(
   1176          aPointAtASCIIWhiteSpace, aDirectionToDelete, mOptions,
   1177          aIgnoreNonEditableNodes, GetEndReasonContent());
   1178    }
   1179 
   1180    /**
   1181     * Return start of current collapsible ASCII white-spaces.
   1182     * NOTE: Option::OnlyEditableNodes is ignored because it's treated as "stop
   1183     * at non-editable content" in the other places, but this "ignores" them.
   1184     *
   1185     * @param aPointAtASCIIWhiteSpace   Must be in a sequence of collapsible
   1186     *                                  ASCII white-spaces.
   1187     * @param aDirectionToDelete        The direction to delete.
   1188     */
   1189    template <typename EditorDOMPointType>
   1190    [[nodiscard]] static EditorDOMPointType
   1191    GetFirstASCIIWhiteSpacePointCollapsedTo(
   1192        const EditorDOMPointInText& aPointAtASCIIWhiteSpace,
   1193        nsIEditor::EDirection aDirectionToDelete,
   1194        Options aOptions,  // NOLINT(performance-unnecessary-value-param)
   1195        IgnoreNonEditableNodes aIgnoreNonEditableNodes,
   1196        const nsIContent* aPrecedingLimiterContent = nullptr);
   1197 
   1198    template <typename EditorDOMPointType>
   1199    [[nodiscard]] EditorDOMPointType GetFirstASCIIWhiteSpacePointCollapsedTo(
   1200        const EditorDOMPointInText& aPointAtASCIIWhiteSpace,
   1201        nsIEditor::EDirection aDirectionToDelete,
   1202        IgnoreNonEditableNodes aIgnoreNonEditableNodes) const {
   1203      return GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointType>(
   1204          aPointAtASCIIWhiteSpace, aDirectionToDelete, mOptions,
   1205          aIgnoreNonEditableNodes, GetStartReasonContent());
   1206    }
   1207 
   1208    /**
   1209     * GetNonCollapsedRangeInTexts() returns non-empty range in texts which
   1210     * is the largest range in aRange if there is some text nodes.
   1211     */
   1212    EditorDOMRangeInTexts GetNonCollapsedRangeInTexts(
   1213        const EditorDOMRange& aRange) const;
   1214 
   1215    /**
   1216     * InvisibleLeadingWhiteSpaceRangeRef() retruns reference to two DOM points,
   1217     * start of the line and first visible point or end of the hard line.  When
   1218     * this returns non-positioned range or positioned but collapsed range,
   1219     * there is no invisible leading white-spaces.
   1220     * Note that if there are only invisible white-spaces in a hard line,
   1221     * this returns all of the white-spaces.
   1222     */
   1223    const EditorDOMRange& InvisibleLeadingWhiteSpaceRangeRef() const;
   1224 
   1225    /**
   1226     * InvisibleTrailingWhiteSpaceRangeRef() returns reference to two DOM
   1227     * points, first invisible white-space and end of the hard line.  When this
   1228     * returns non-positioned range or positioned but collapsed range,
   1229     * there is no invisible trailing white-spaces.
   1230     * Note that if there are only invisible white-spaces in a hard line,
   1231     * this returns all of the white-spaces.
   1232     */
   1233    const EditorDOMRange& InvisibleTrailingWhiteSpaceRangeRef() const;
   1234 
   1235    /**
   1236     * GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt() returns new
   1237     * invisible leading white-space range which should be removed if
   1238     * splitting invisible white-space sequence at aPointToSplit creates
   1239     * new invisible leading white-spaces in the new line.
   1240     * Note that the result may be collapsed range if the point is around
   1241     * invisible white-spaces.
   1242     */
   1243    template <typename EditorDOMPointType>
   1244    EditorDOMRange GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(
   1245        const EditorDOMPointType& aPointToSplit) const {
   1246      // If there are invisible trailing white-spaces and some or all of them
   1247      // become invisible leading white-spaces in the new line, although we
   1248      // don't need to delete them, but for aesthetically and backward
   1249      // compatibility, we should remove them.
   1250      const EditorDOMRange& trailingWhiteSpaceRange =
   1251          InvisibleTrailingWhiteSpaceRangeRef();
   1252      // XXX Why don't we check leading white-spaces too?
   1253      if (!trailingWhiteSpaceRange.IsPositioned()) {
   1254        return trailingWhiteSpaceRange;
   1255      }
   1256      // If the point is before the trailing white-spaces, the new line won't
   1257      // start with leading white-spaces.
   1258      if (aPointToSplit.IsBefore(trailingWhiteSpaceRange.StartRef())) {
   1259        return EditorDOMRange();
   1260      }
   1261      // If the point is in the trailing white-spaces, the new line may
   1262      // start with some leading white-spaces.  Returning collapsed range
   1263      // is intentional because the caller may want to know whether the
   1264      // point is in trailing white-spaces or not.
   1265      if (aPointToSplit.EqualsOrIsBefore(trailingWhiteSpaceRange.EndRef())) {
   1266        return EditorDOMRange(trailingWhiteSpaceRange.StartRef(),
   1267                              aPointToSplit);
   1268      }
   1269      // Otherwise, if the point is after the trailing white-spaces, it may
   1270      // be just outside of the text node.  E.g., end of parent element.
   1271      // This is possible case but the validation cost is not worthwhile
   1272      // due to the runtime cost in the worst case.  Therefore, we should just
   1273      // return collapsed range at the end of trailing white-spaces.  Then,
   1274      // callers can know the point is immediately after the trailing
   1275      // white-spaces.
   1276      return EditorDOMRange(trailingWhiteSpaceRange.EndRef());
   1277    }
   1278 
   1279    /**
   1280     * GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt() returns new
   1281     * invisible trailing white-space range which should be removed if
   1282     * splitting invisible white-space sequence at aPointToSplit creates
   1283     * new invisible trailing white-spaces in the new line.
   1284     * Note that the result may be collapsed range if the point is around
   1285     * invisible white-spaces.
   1286     */
   1287    template <typename EditorDOMPointType>
   1288    EditorDOMRange GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(
   1289        const EditorDOMPointType& aPointToSplit) const {
   1290      // If there are invisible leading white-spaces and some or all of them
   1291      // become end of current line, they will become visible.  Therefore, we
   1292      // need to delete the invisible leading white-spaces before insertion
   1293      // point.
   1294      const EditorDOMRange& leadingWhiteSpaceRange =
   1295          InvisibleLeadingWhiteSpaceRangeRef();
   1296      if (!leadingWhiteSpaceRange.IsPositioned()) {
   1297        return leadingWhiteSpaceRange;
   1298      }
   1299      // If the point equals or is after the leading white-spaces, the line
   1300      // will end without trailing white-spaces.
   1301      if (leadingWhiteSpaceRange.EndRef().IsBefore(aPointToSplit)) {
   1302        return EditorDOMRange();
   1303      }
   1304      // If the point is in the leading white-spaces, the line may
   1305      // end with some trailing white-spaces.  Returning collapsed range
   1306      // is intentional because the caller may want to know whether the
   1307      // point is in leading white-spaces or not.
   1308      if (leadingWhiteSpaceRange.StartRef().EqualsOrIsBefore(aPointToSplit)) {
   1309        return EditorDOMRange(aPointToSplit, leadingWhiteSpaceRange.EndRef());
   1310      }
   1311      // Otherwise, if the point is before the leading white-spaces, it may
   1312      // be just outside of the text node.  E.g., start of parent element.
   1313      // This is possible case but the validation cost is not worthwhile
   1314      // due to the runtime cost in the worst case.  Therefore, we should
   1315      // just return collapsed range at start of the leading white-spaces.
   1316      // Then, callers can know the point is immediately before the leading
   1317      // white-spaces.
   1318      return EditorDOMRange(leadingWhiteSpaceRange.StartRef());
   1319    }
   1320 
   1321    /**
   1322     * FollowingContentMayBecomeFirstVisibleContent() returns true if some
   1323     * content may be first visible content after removing content after aPoint.
   1324     * Note that it's completely broken what this does.  Don't use this method
   1325     * with new code.
   1326     */
   1327    template <typename EditorDOMPointType>
   1328    bool FollowingContentMayBecomeFirstVisibleContent(
   1329        const EditorDOMPointType& aPoint) const {
   1330      MOZ_ASSERT(aPoint.IsSetAndValid());
   1331      if (!mStart.IsHardLineBreak() && !mStart.IsInlineEditingHostBoundary()) {
   1332        return false;
   1333      }
   1334      // If the point is before start of text fragment, that means that the
   1335      // point may be at the block boundary or inline element boundary.
   1336      if (aPoint.EqualsOrIsBefore(mStart.PointRef())) {
   1337        return true;
   1338      }
   1339      // VisibleWhiteSpacesData is marked as start of line only when it
   1340      // represents leading white-spaces.
   1341      const EditorDOMRange& leadingWhiteSpaceRange =
   1342          InvisibleLeadingWhiteSpaceRangeRef();
   1343      if (!leadingWhiteSpaceRange.StartRef().IsSet()) {
   1344        return false;
   1345      }
   1346      if (aPoint.EqualsOrIsBefore(leadingWhiteSpaceRange.StartRef())) {
   1347        return true;
   1348      }
   1349      if (!leadingWhiteSpaceRange.EndRef().IsSet()) {
   1350        return false;
   1351      }
   1352      return aPoint.EqualsOrIsBefore(leadingWhiteSpaceRange.EndRef());
   1353    }
   1354 
   1355    /**
   1356     * PrecedingContentMayBecomeInvisible() returns true if end of preceding
   1357     * content is collapsed (when ends with an ASCII white-space).
   1358     * Note that it's completely broken what this does.  Don't use this method
   1359     * with new code.
   1360     */
   1361    template <typename EditorDOMPointType>
   1362    bool PrecedingContentMayBecomeInvisible(
   1363        const EditorDOMPointType& aPoint) const {
   1364      MOZ_ASSERT(aPoint.IsSetAndValid());
   1365      // If this fragment is ends by block boundary, always the caller needs
   1366      // additional check.
   1367      if (mEnd.IsBlockBoundary() || mEnd.IsInlineEditingHostBoundary()) {
   1368        return true;
   1369      }
   1370 
   1371      // If the point is in visible white-spaces and ends with an ASCII
   1372      // white-space, it may be collapsed even if it won't be end of line.
   1373      const VisibleWhiteSpacesData& visibleWhiteSpaces =
   1374          VisibleWhiteSpacesDataRef();
   1375      if (!visibleWhiteSpaces.IsInitialized()) {
   1376        return false;
   1377      }
   1378      // XXX Odd case, but keep traditional behavior of `FindNearestRun()`.
   1379      if (!visibleWhiteSpaces.StartRef().IsSet()) {
   1380        return true;
   1381      }
   1382      if (!visibleWhiteSpaces.StartRef().EqualsOrIsBefore(aPoint)) {
   1383        return false;
   1384      }
   1385      // XXX Odd case, but keep traditional behavior of `FindNearestRun()`.
   1386      if (visibleWhiteSpaces.EndsByTrailingWhiteSpaces()) {
   1387        return true;
   1388      }
   1389      // XXX Must be a bug.  This claims that the caller needs additional
   1390      // check even when there is no white-spaces.
   1391      if (visibleWhiteSpaces.StartRef() == visibleWhiteSpaces.EndRef()) {
   1392        return true;
   1393      }
   1394      return aPoint.IsBefore(visibleWhiteSpaces.EndRef());
   1395    }
   1396 
   1397    /**
   1398     * GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace() may return an
   1399     * NBSP point which should be replaced with an ASCII white-space when we're
   1400     * inserting text into aPointToInsert. Note that this is a helper method for
   1401     * the traditional white-space normalizer.  Don't use this with the new
   1402     * white-space normalizer.
   1403     * Must be called only when VisibleWhiteSpacesDataRef() returns initialized
   1404     * instance and previous character of aPointToInsert is in the range.
   1405     */
   1406    EditorDOMPointInText GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
   1407        const EditorDOMPoint& aPointToInsert) const;
   1408 
   1409    /**
   1410     * GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace() may return
   1411     * an NBSP point which should be replaced with an ASCII white-space when
   1412     * the caller inserts text into aPointToInsert.
   1413     * Note that this is a helper method for the traditional white-space
   1414     * normalizer.  Don't use this with the new white-space normalizer.
   1415     * Must be called only when VisibleWhiteSpacesDataRef() returns initialized
   1416     * instance, and inclusive next char of aPointToInsert is in the range.
   1417     */
   1418    EditorDOMPointInText
   1419    GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
   1420        const EditorDOMPoint& aPointToInsert) const;
   1421 
   1422    /**
   1423     * GetReplaceRangeDataAtEndOfDeletionRange() and
   1424     * GetReplaceRangeDataAtStartOfDeletionRange() return delete range if
   1425     * end or start of deleting range splits invisible trailing/leading
   1426     * white-spaces and it may become visible, or return replace range if
   1427     * end or start of deleting range splits visible white-spaces and it
   1428     * causes some ASCII white-spaces become invisible unless replacing
   1429     * with an NBSP.
   1430     */
   1431    ReplaceRangeData GetReplaceRangeDataAtEndOfDeletionRange(
   1432        const TextFragmentData& aTextFragmentDataAtStartToDelete) const;
   1433    ReplaceRangeData GetReplaceRangeDataAtStartOfDeletionRange(
   1434        const TextFragmentData& aTextFragmentDataAtEndToDelete) const;
   1435 
   1436    /**
   1437     * VisibleWhiteSpacesDataRef() returns reference to visible white-spaces
   1438     * data. That is zero or more white-spaces which are visible.
   1439     * Note that when there is no visible content, it's not initialized.
   1440     * Otherwise, even if there is no white-spaces, it's initialized and
   1441     * the range is collapsed in such case.
   1442     */
   1443    const VisibleWhiteSpacesData& VisibleWhiteSpacesDataRef() const;
   1444 
   1445   private:
   1446    EditorDOMPoint mScanStartPoint;
   1447    RefPtr<const Element> mAncestorLimiter;
   1448    BoundaryData mStart;
   1449    BoundaryData mEnd;
   1450    NoBreakingSpaceData mNBSPData;
   1451    mutable Maybe<EditorDOMRange> mLeadingWhiteSpaceRange;
   1452    mutable Maybe<EditorDOMRange> mTrailingWhiteSpaceRange;
   1453    mutable Maybe<VisibleWhiteSpacesData> mVisibleWhiteSpacesData;
   1454    const Options mOptions;
   1455  };
   1456 
   1457  const TextFragmentData& TextFragmentDataAtStartRef() const {
   1458    return mTextFragmentDataAtStart;
   1459  }
   1460 
   1461  // The node passed to our constructor.
   1462  EditorDOMPoint mScanStartPoint;
   1463  // Together, the above represent the point at which we are building up ws
   1464  // info.
   1465 
   1466 private:
   1467  /**
   1468   * ComputeRangeInTextNodesContainingInvisibleWhiteSpaces() returns range
   1469   * containing invisible white-spaces if deleting between aStart and aEnd
   1470   * causes them become visible.
   1471   *
   1472   * @param aStart      TextFragmentData at start of deleting range.
   1473   *                    This must be initialized with DOM point in a text node.
   1474   * @param aEnd        TextFragmentData at end of deleting range.
   1475   *                    This must be initialized with DOM point in a text node.
   1476   */
   1477  static EditorDOMRangeInTexts
   1478  ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
   1479      const TextFragmentData& aStart, const TextFragmentData& aEnd);
   1480 
   1481  TextFragmentData mTextFragmentDataAtStart;
   1482 
   1483  friend class WhiteSpaceVisibilityKeeper;
   1484  friend class WSScanResult;
   1485 };
   1486 
   1487 }  // namespace mozilla
   1488 
   1489 #endif  // #ifndef WSRunScanner_h