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