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