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/. */
9 #include "EditAction.h"
10 #include "EditorBase.h"
11 #include "EditorForwards.h"
12 #include "EditorDOMPoint.h" // for EditorDOMPoint
13 #include "EditorUtils.h" // for CaretPoint
14 #include "HTMLEditHelpers.h"
15 #include "HTMLEditor.h"
16 #include "HTMLEditUtils.h"
18 #include "mozilla/Assertions.h"
19 #include "mozilla/Maybe.h"
20 #include "mozilla/Result.h"
21 #include "mozilla/dom/Element.h"
22 #include "mozilla/dom/HTMLBRElement.h"
23 #include "mozilla/dom/Text.h"
25 #include "nsIContent.h"
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
38 class MOZ_STACK_CLASS WSScanResult final
{
40 enum class WSType
: uint8_t {
42 // Could be the DOM tree is broken as like crash tests.
44 // The run is maybe collapsible white-spaces at start of a hard line.
46 // The run is maybe collapsible white-spaces at end of a hard line.
48 // Collapsible, but visible white-spaces.
49 CollapsibleWhiteSpaces
,
50 // Visible characters except collapsible white-spaces.
51 NonCollapsibleCharacters
,
52 // Special content such as `<img>`, etc.
56 // A linefeed which is preformatted.
57 PreformattedLineBreak
,
58 // Other block's boundary (child block of current block, maybe).
60 // Current block's boundary.
64 friend std::ostream
& operator<<(std::ostream
& aStream
, const WSType
& aType
) {
66 case WSType::NotInitialized
:
67 return aStream
<< "WSType::NotInitialized";
68 case WSType::UnexpectedError
:
69 return aStream
<< "WSType::UnexpectedError";
70 case WSType::LeadingWhiteSpaces
:
71 return aStream
<< "WSType::LeadingWhiteSpaces";
72 case WSType::TrailingWhiteSpaces
:
73 return aStream
<< "WSType::TrailingWhiteSpaces";
74 case WSType::CollapsibleWhiteSpaces
:
75 return aStream
<< "WSType::CollapsibleWhiteSpaces";
76 case WSType::NonCollapsibleCharacters
:
77 return aStream
<< "WSType::NonCollapsibleCharacters";
78 case WSType::SpecialContent
:
79 return aStream
<< "WSType::SpecialContent";
80 case WSType::BRElement
:
81 return aStream
<< "WSType::BRElement";
82 case WSType::PreformattedLineBreak
:
83 return aStream
<< "WSType::PreformattedLineBreak";
84 case WSType::OtherBlockBoundary
:
85 return aStream
<< "WSType::OtherBlockBoundary";
86 case WSType::CurrentBlockBoundary
:
87 return aStream
<< "WSType::CurrentBlockBoundary";
89 return aStream
<< "<Illegal value>";
92 friend class WSRunScanner
; // Because of WSType.
95 WSScanResult() = delete;
96 MOZ_NEVER_INLINE_DEBUG
WSScanResult(nsIContent
* aContent
, WSType aReason
,
97 BlockInlineCheck aBlockInlineCheck
)
98 : mContent(aContent
), mReason(aReason
) {
99 AssertIfInvalidData(aBlockInlineCheck
);
101 MOZ_NEVER_INLINE_DEBUG
WSScanResult(const EditorDOMPoint
& aPoint
,
103 BlockInlineCheck aBlockInlineCheck
)
104 : mContent(aPoint
.GetContainerAs
<nsIContent
>()),
105 mOffset(Some(aPoint
.Offset())),
107 AssertIfInvalidData(aBlockInlineCheck
);
110 MOZ_NEVER_INLINE_DEBUG
void AssertIfInvalidData(
111 BlockInlineCheck aBlockInlineCheck
) const {
113 MOZ_ASSERT(mReason
== WSType::UnexpectedError
||
114 mReason
== WSType::NonCollapsibleCharacters
||
115 mReason
== WSType::CollapsibleWhiteSpaces
||
116 mReason
== WSType::BRElement
||
117 mReason
== WSType::PreformattedLineBreak
||
118 mReason
== WSType::SpecialContent
||
119 mReason
== WSType::CurrentBlockBoundary
||
120 mReason
== WSType::OtherBlockBoundary
);
121 MOZ_ASSERT_IF(mReason
== WSType::UnexpectedError
, !mContent
);
122 MOZ_ASSERT_IF(mReason
== WSType::NonCollapsibleCharacters
||
123 mReason
== WSType::CollapsibleWhiteSpaces
,
124 mContent
&& mContent
->IsText());
125 MOZ_ASSERT_IF(mReason
== WSType::BRElement
,
126 mContent
&& mContent
->IsHTMLElement(nsGkAtoms::br
));
127 MOZ_ASSERT_IF(mReason
== WSType::PreformattedLineBreak
,
128 mContent
&& mContent
->IsText() &&
129 EditorUtils::IsNewLinePreformatted(*mContent
));
131 mReason
== WSType::SpecialContent
,
133 ((mContent
->IsText() && !mContent
->IsEditable()) ||
134 (!mContent
->IsHTMLElement(nsGkAtoms::br
) &&
135 !HTMLEditUtils::IsBlockElement(*mContent
, aBlockInlineCheck
))));
136 MOZ_ASSERT_IF(mReason
== WSType::OtherBlockBoundary
,
137 mContent
&& HTMLEditUtils::IsBlockElement(*mContent
,
139 // If mReason is WSType::CurrentBlockBoundary, mContent can be any content.
140 // In most cases, it's current block element which is editable. However, if
141 // there is no editable block parent, this is topmost editable inline
142 // content. Additionally, if there is no editable content, this is the
143 // container start of scanner and is not editable.
144 if (mReason
== WSType::CurrentBlockBoundary
) {
146 // Although not expected that scanning in orphan document fragment,
148 !mContent
->IsInComposedDoc() ||
149 // This is what the most preferred result is mContent itself is a
151 HTMLEditUtils::IsBlockElement(*mContent
, aBlockInlineCheck
) ||
152 // If mContent is not editable, we cannot check whether there is no
153 // block ancestor in the limiter which we don't have. Therefore,
154 // let's skip the ancestor check.
155 !mContent
->IsEditable()) {
158 const DebugOnly
<Element
*> closestAncestorEditableBlockElement
=
159 HTMLEditUtils::GetAncestorElement(
160 *mContent
, HTMLEditUtils::ClosestEditableBlockElement
,
163 mReason
== WSType::CurrentBlockBoundary
,
164 // There is no editable block ancestor, it's fine.
165 !closestAncestorEditableBlockElement
||
166 // If we found an editable block, but mContent can be inline if
167 // it's an editing host (root or its parent is not editable).
168 !closestAncestorEditableBlockElement
->GetParentElement() ||
169 !closestAncestorEditableBlockElement
->GetParentElement()
172 #endif // #ifdef DEBUG
175 bool Failed() const {
176 return mReason
== WSType::NotInitialized
||
177 mReason
== WSType::UnexpectedError
;
181 * GetContent() returns found visible and editable content/element.
182 * See MOZ_ASSERT_IF()s in AssertIfInvalidData() for the detail.
184 nsIContent
* GetContent() const { return mContent
; }
186 [[nodiscard
]] bool ContentIsElement() const {
187 return mContent
&& mContent
->IsElement();
191 * The following accessors makes it easier to understand each callers.
193 MOZ_NEVER_INLINE_DEBUG Element
* ElementPtr() const {
194 MOZ_DIAGNOSTIC_ASSERT(mContent
->IsElement());
195 return mContent
->AsElement();
197 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* BRElementPtr() const {
198 MOZ_DIAGNOSTIC_ASSERT(mContent
->IsHTMLElement(nsGkAtoms::br
));
199 return static_cast<HTMLBRElement
*>(mContent
.get());
201 MOZ_NEVER_INLINE_DEBUG Text
* TextPtr() const {
202 MOZ_DIAGNOSTIC_ASSERT(mContent
->IsText());
203 return mContent
->AsText();
207 * Returns true if found or reached content is ediable.
209 bool IsContentEditable() const { return mContent
&& mContent
->IsEditable(); }
212 * Offset() returns meaningful value only when
213 * InVisibleOrCollapsibleCharacters() returns true or the scanner
214 * reached to start or end of its scanning range and that is same as start or
215 * end container which are specified when the scanner is initialized. If it's
216 * result of scanning backward, this offset means before the found point.
217 * Otherwise, i.e., scanning forward, this offset means after the found point.
219 MOZ_NEVER_INLINE_DEBUG
uint32_t Offset() const {
220 NS_ASSERTION(mOffset
.isSome(), "Retrieved non-meaningful offset");
221 return mOffset
.valueOr(0);
225 * Point() and RawPoint() return the position in found visible node or
226 * reached block boundary. So, they return meaningful point only when
227 * Offset() returns meaningful value.
229 template <typename EditorDOMPointType
>
230 EditorDOMPointType
Point() const {
231 NS_ASSERTION(mOffset
.isSome(), "Retrieved non-meaningful point");
232 return EditorDOMPointType(mContent
, mOffset
.valueOr(0));
236 * PointAtContent() and RawPointAtContent() return the position of found
237 * visible content or reached block element.
239 template <typename EditorDOMPointType
>
240 EditorDOMPointType
PointAtContent() const {
241 MOZ_ASSERT(mContent
);
242 return EditorDOMPointType(mContent
);
246 * PointAfterContent() and RawPointAfterContent() retrun the position after
247 * found visible content or reached block element.
249 template <typename EditorDOMPointType
>
250 EditorDOMPointType
PointAfterContent() const {
251 MOZ_ASSERT(mContent
);
252 return mContent
? EditorDOMPointType::After(mContent
)
253 : EditorDOMPointType();
257 * The scanner reached <img> or something which is inline and is not a
260 bool ReachedSpecialContent() const {
261 return mReason
== WSType::SpecialContent
;
265 * The point is in visible characters or collapsible white-spaces.
267 bool InVisibleOrCollapsibleCharacters() const {
268 return mReason
== WSType::CollapsibleWhiteSpaces
||
269 mReason
== WSType::NonCollapsibleCharacters
;
273 * The point is in collapsible white-spaces.
275 bool InCollapsibleWhiteSpaces() const {
276 return mReason
== WSType::CollapsibleWhiteSpaces
;
280 * The point is in visible non-collapsible characters.
282 bool InNonCollapsibleCharacters() const {
283 return mReason
== WSType::NonCollapsibleCharacters
;
287 * The scanner reached a <br> element.
289 bool ReachedBRElement() const { return mReason
== WSType::BRElement
; }
290 bool ReachedVisibleBRElement() const {
291 return ReachedBRElement() &&
292 HTMLEditUtils::IsVisibleBRElement(*BRElementPtr());
294 bool ReachedInvisibleBRElement() const {
295 return ReachedBRElement() &&
296 HTMLEditUtils::IsInvisibleBRElement(*BRElementPtr());
299 bool ReachedPreformattedLineBreak() const {
300 return mReason
== WSType::PreformattedLineBreak
;
304 * The scanner reached a <hr> element.
306 bool ReachedHRElement() const {
307 return mContent
&& mContent
->IsHTMLElement(nsGkAtoms::hr
);
311 * The scanner reached current block boundary or other block element.
313 bool ReachedBlockBoundary() const {
314 return mReason
== WSType::CurrentBlockBoundary
||
315 mReason
== WSType::OtherBlockBoundary
;
319 * The scanner reached current block element boundary.
321 bool ReachedCurrentBlockBoundary() const {
322 return mReason
== WSType::CurrentBlockBoundary
;
326 * The scanner reached other block element.
328 bool ReachedOtherBlockElement() const {
329 return mReason
== WSType::OtherBlockBoundary
;
333 * The scanner reached other block element that isn't editable
335 bool ReachedNonEditableOtherBlockElement() const {
336 return ReachedOtherBlockElement() && !GetContent()->IsEditable();
340 * The scanner reached something non-text node.
342 bool ReachedSomethingNonTextContent() const {
343 return !InVisibleOrCollapsibleCharacters();
347 nsCOMPtr
<nsIContent
> mContent
;
348 Maybe
<uint32_t> mOffset
;
352 class MOZ_STACK_CLASS WSRunScanner final
{
354 using WSType
= WSScanResult::WSType
;
356 template <typename EditorDOMPointType
>
357 WSRunScanner(const Element
* aEditingHost
,
358 const EditorDOMPointType
& aScanStartPoint
,
359 BlockInlineCheck aBlockInlineCheck
)
360 : mScanStartPoint(aScanStartPoint
.template To
<EditorDOMPoint
>()),
361 mEditingHost(const_cast<Element
*>(aEditingHost
)),
362 mTextFragmentDataAtStart(mScanStartPoint
, mEditingHost
,
364 mBlockInlineCheck(aBlockInlineCheck
) {}
366 // ScanNextVisibleNodeOrBlockBoundaryForwardFrom() returns the first visible
367 // node after aPoint. If there is no visible nodes after aPoint, returns
368 // topmost editable inline ancestor at end of current block. See comments
369 // around WSScanResult for the detail.
370 template <typename PT
, typename CT
>
371 WSScanResult
ScanNextVisibleNodeOrBlockBoundaryFrom(
372 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const;
373 template <typename PT
, typename CT
>
374 static WSScanResult
ScanNextVisibleNodeOrBlockBoundary(
375 const Element
* aEditingHost
, const EditorDOMPointBase
<PT
, CT
>& aPoint
,
376 BlockInlineCheck aBlockInlineCheck
) {
377 return WSRunScanner(aEditingHost
, aPoint
, aBlockInlineCheck
)
378 .ScanNextVisibleNodeOrBlockBoundaryFrom(aPoint
);
381 // ScanPreviousVisibleNodeOrBlockBoundaryFrom() returns the first visible node
382 // before aPoint. If there is no visible nodes before aPoint, returns topmost
383 // editable inline ancestor at start of current block. See comments around
384 // WSScanResult for the detail.
385 template <typename PT
, typename CT
>
386 WSScanResult
ScanPreviousVisibleNodeOrBlockBoundaryFrom(
387 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const;
388 template <typename PT
, typename CT
>
389 static WSScanResult
ScanPreviousVisibleNodeOrBlockBoundary(
390 const Element
* aEditingHost
, const EditorDOMPointBase
<PT
, CT
>& aPoint
,
391 BlockInlineCheck aBlockInlineCheck
) {
392 return WSRunScanner(aEditingHost
, aPoint
, aBlockInlineCheck
)
393 .ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPoint
);
397 * GetInclusiveNextEditableCharPoint() returns a point in a text node which
398 * is at current editable character or next editable character if aPoint
399 * does not points an editable character.
401 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
403 static EditorDOMPointType
GetInclusiveNextEditableCharPoint(
404 Element
* aEditingHost
, const EditorDOMPointBase
<PT
, CT
>& aPoint
,
405 BlockInlineCheck aBlockInlineCheck
) {
406 if (aPoint
.IsInTextNode() && !aPoint
.IsEndOfContainer() &&
407 HTMLEditUtils::IsSimplyEditableNode(
408 *aPoint
.template ContainerAs
<Text
>())) {
409 return EditorDOMPointType(aPoint
.template ContainerAs
<Text
>(),
412 return WSRunScanner(aEditingHost
, aPoint
, aBlockInlineCheck
)
413 .GetInclusiveNextEditableCharPoint
<EditorDOMPointType
>(aPoint
);
417 * GetPreviousEditableCharPoint() returns a point in a text node which
418 * is at previous editable character.
420 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
422 static EditorDOMPointType
GetPreviousEditableCharPoint(
423 Element
* aEditingHost
, const EditorDOMPointBase
<PT
, CT
>& aPoint
,
424 BlockInlineCheck aBlockInlineCheck
) {
425 if (aPoint
.IsInTextNode() && !aPoint
.IsStartOfContainer() &&
426 HTMLEditUtils::IsSimplyEditableNode(
427 *aPoint
.template ContainerAs
<Text
>())) {
428 return EditorDOMPointType(aPoint
.template ContainerAs
<Text
>(),
429 aPoint
.Offset() - 1);
431 return WSRunScanner(aEditingHost
, aPoint
, aBlockInlineCheck
)
432 .GetPreviousEditableCharPoint
<EditorDOMPointType
>(aPoint
);
436 * Scan aTextNode from end or start to find last or first visible things.
437 * I.e., this returns a point immediately before or after invisible
438 * white-spaces of aTextNode if aTextNode ends or begins with some invisible
440 * Note that the result may not be in different text node if aTextNode has
441 * only invisible white-spaces and there is previous or next text node.
443 template <typename EditorDOMPointType
>
444 static EditorDOMPointType
GetAfterLastVisiblePoint(
445 Text
& aTextNode
, const Element
* aAncestorLimiter
);
446 template <typename EditorDOMPointType
>
447 static EditorDOMPointType
GetFirstVisiblePoint(
448 Text
& aTextNode
, const Element
* aAncestorLimiter
);
451 * GetRangeInTextNodesToForwardDeleteFrom() returns the range to remove
452 * text when caret is at aPoint.
454 static Result
<EditorDOMRangeInTexts
, nsresult
>
455 GetRangeInTextNodesToForwardDeleteFrom(const EditorDOMPoint
& aPoint
,
456 const Element
& aEditingHost
);
459 * GetRangeInTextNodesToBackspaceFrom() returns the range to remove text
460 * when caret is at aPoint.
462 static Result
<EditorDOMRangeInTexts
, nsresult
>
463 GetRangeInTextNodesToBackspaceFrom(const EditorDOMPoint
& aPoint
,
464 const Element
& aEditingHost
);
467 * GetRangesForDeletingAtomicContent() returns the range to delete
468 * aAtomicContent. If it's followed by invisible white-spaces, they will
469 * be included into the range.
471 static EditorDOMRange
GetRangesForDeletingAtomicContent(
472 Element
* aEditingHost
, const nsIContent
& aAtomicContent
);
475 * GetRangeForDeleteBlockElementBoundaries() returns a range starting from end
476 * of aLeftBlockElement to start of aRightBlockElement and extend invisible
477 * white-spaces around them.
479 * @param aHTMLEditor The HTML editor.
480 * @param aLeftBlockElement The block element which will be joined with
481 * aRightBlockElement.
482 * @param aRightBlockElement The block element which will be joined with
483 * aLeftBlockElement. This must be an element
484 * after aLeftBlockElement.
485 * @param aPointContainingTheOtherBlock
486 * When aRightBlockElement is an ancestor of
487 * aLeftBlockElement, this must be set and the
488 * container must be aRightBlockElement.
489 * When aLeftBlockElement is an ancestor of
490 * aRightBlockElement, this must be set and the
491 * container must be aLeftBlockElement.
492 * Otherwise, must not be set.
494 static EditorDOMRange
GetRangeForDeletingBlockElementBoundaries(
495 const HTMLEditor
& aHTMLEditor
, const Element
& aLeftBlockElement
,
496 const Element
& aRightBlockElement
,
497 const EditorDOMPoint
& aPointContainingTheOtherBlock
);
500 * ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() may shrink aRange if it
501 * starts and/or ends with an atomic content, but the range boundary
502 * is in adjacent text nodes. Returns true if this modifies the range.
504 static Result
<bool, nsresult
> ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
505 const HTMLEditor
& aHTMLEditor
, nsRange
& aRange
,
506 const Element
* aEditingHost
);
509 * GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries() returns
510 * extended range if range boundaries of aRange are in invisible white-spaces.
512 static EditorDOMRange
GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
513 Element
* aEditingHost
, const EditorDOMRange
& aRange
);
516 * GetPrecedingBRElementUnlessVisibleContentFound() scans a `<br>` element
517 * backward, but stops scanning it if the scanner finds visible character
518 * or something. In other words, this method ignores only invisible
519 * white-spaces between `<br>` element and aPoint.
521 template <typename EditorDOMPointType
>
522 MOZ_NEVER_INLINE_DEBUG
static HTMLBRElement
*
523 GetPrecedingBRElementUnlessVisibleContentFound(
524 Element
* aEditingHost
, const EditorDOMPointType
& aPoint
,
525 BlockInlineCheck aBlockInlineCheck
) {
526 MOZ_ASSERT(aPoint
.IsSetAndValid());
527 // XXX This method behaves differently even in similar point.
528 // If aPoint is in a text node following `<br>` element, reaches the
529 // `<br>` element when all characters between the `<br>` and
530 // aPoint are ASCII whitespaces.
531 // But if aPoint is not in a text node, e.g., at start of an inline
532 // element which is immediately after a `<br>` element, returns the
533 // `<br>` element even if there is no invisible white-spaces.
534 if (aPoint
.IsStartOfContainer()) {
537 // TODO: Scan for end boundary is redundant in this case, we should optimize
539 TextFragmentData
textFragmentData(aPoint
, aEditingHost
, aBlockInlineCheck
);
540 return textFragmentData
.StartsFromBRElement()
541 ? textFragmentData
.StartReasonBRElementPtr()
545 const EditorDOMPoint
& ScanStartRef() const { return mScanStartPoint
; }
548 * GetStartReasonContent() and GetEndReasonContent() return a node which
549 * was found by scanning from mScanStartPoint backward or forward. If there
550 * was white-spaces or text from the point, returns the text node. Otherwise,
551 * returns an element which is explained by the following methods. Note that
552 * when the reason is WSType::CurrentBlockBoundary, In most cases, it's
553 * current block element which is editable, but also may be non-element and/or
554 * non-editable. See MOZ_ASSERT_IF()s in WSScanResult::AssertIfInvalidData()
557 nsIContent
* GetStartReasonContent() const {
558 return TextFragmentDataAtStartRef().GetStartReasonContent();
560 nsIContent
* GetEndReasonContent() const {
561 return TextFragmentDataAtStartRef().GetEndReasonContent();
564 bool StartsFromNonCollapsibleCharacters() const {
565 return TextFragmentDataAtStartRef().StartsFromNonCollapsibleCharacters();
567 bool StartsFromSpecialContent() const {
568 return TextFragmentDataAtStartRef().StartsFromSpecialContent();
570 bool StartsFromBRElement() const {
571 return TextFragmentDataAtStartRef().StartsFromBRElement();
573 bool StartsFromVisibleBRElement() const {
574 return TextFragmentDataAtStartRef().StartsFromVisibleBRElement();
576 bool StartsFromInvisibleBRElement() const {
577 return TextFragmentDataAtStartRef().StartsFromInvisibleBRElement();
579 bool StartsFromPreformattedLineBreak() const {
580 return TextFragmentDataAtStartRef().StartsFromPreformattedLineBreak();
582 bool StartsFromCurrentBlockBoundary() const {
583 return TextFragmentDataAtStartRef().StartsFromCurrentBlockBoundary();
585 bool StartsFromOtherBlockElement() const {
586 return TextFragmentDataAtStartRef().StartsFromOtherBlockElement();
588 bool StartsFromBlockBoundary() const {
589 return TextFragmentDataAtStartRef().StartsFromBlockBoundary();
591 bool StartsFromHardLineBreak() const {
592 return TextFragmentDataAtStartRef().StartsFromHardLineBreak();
594 bool EndsByNonCollapsibleCharacters() const {
595 return TextFragmentDataAtStartRef().EndsByNonCollapsibleCharacters();
597 bool EndsBySpecialContent() const {
598 return TextFragmentDataAtStartRef().EndsBySpecialContent();
600 bool EndsByBRElement() const {
601 return TextFragmentDataAtStartRef().EndsByBRElement();
603 bool EndsByVisibleBRElement() const {
604 return TextFragmentDataAtStartRef().EndsByVisibleBRElement();
606 bool EndsByInvisibleBRElement() const {
607 return TextFragmentDataAtStartRef().EndsByInvisibleBRElement();
609 bool EndsByPreformattedLineBreak() const {
610 return TextFragmentDataAtStartRef().EndsByPreformattedLineBreak();
612 bool EndsByCurrentBlockBoundary() const {
613 return TextFragmentDataAtStartRef().EndsByCurrentBlockBoundary();
615 bool EndsByOtherBlockElement() const {
616 return TextFragmentDataAtStartRef().EndsByOtherBlockElement();
618 bool EndsByBlockBoundary() const {
619 return TextFragmentDataAtStartRef().EndsByBlockBoundary();
622 MOZ_NEVER_INLINE_DEBUG Element
* StartReasonOtherBlockElementPtr() const {
623 return TextFragmentDataAtStartRef().StartReasonOtherBlockElementPtr();
625 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* StartReasonBRElementPtr() const {
626 return TextFragmentDataAtStartRef().StartReasonBRElementPtr();
628 MOZ_NEVER_INLINE_DEBUG Element
* EndReasonOtherBlockElementPtr() const {
629 return TextFragmentDataAtStartRef().EndReasonOtherBlockElementPtr();
631 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* EndReasonBRElementPtr() const {
632 return TextFragmentDataAtStartRef().EndReasonBRElementPtr();
636 * Active editing host when this instance is created.
638 Element
* GetEditingHost() const { return mEditingHost
; }
641 using EditorType
= EditorBase::EditorType
;
643 class TextFragmentData
;
645 // VisibleWhiteSpacesData represents 0 or more visible white-spaces.
646 class MOZ_STACK_CLASS VisibleWhiteSpacesData final
{
648 bool IsInitialized() const {
649 return mLeftWSType
!= WSType::NotInitialized
||
650 mRightWSType
!= WSType::NotInitialized
;
653 EditorDOMPoint
StartRef() const { return mStartPoint
; }
654 EditorDOMPoint
EndRef() const { return mEndPoint
; }
657 * Information why the white-spaces start from (i.e., this indicates the
658 * previous content type of the fragment).
660 bool StartsFromNonCollapsibleCharacters() const {
661 return mLeftWSType
== WSType::NonCollapsibleCharacters
;
663 bool StartsFromSpecialContent() const {
664 return mLeftWSType
== WSType::SpecialContent
;
666 bool StartsFromPreformattedLineBreak() const {
667 return mLeftWSType
== WSType::PreformattedLineBreak
;
671 * Information why the white-spaces end by (i.e., this indicates the
672 * next content type of the fragment).
674 bool EndsByNonCollapsibleCharacters() const {
675 return mRightWSType
== WSType::NonCollapsibleCharacters
;
677 bool EndsByTrailingWhiteSpaces() const {
678 return mRightWSType
== WSType::TrailingWhiteSpaces
;
680 bool EndsBySpecialContent() const {
681 return mRightWSType
== WSType::SpecialContent
;
683 bool EndsByBRElement() const { return mRightWSType
== WSType::BRElement
; }
684 bool EndsByPreformattedLineBreak() const {
685 return mRightWSType
== WSType::PreformattedLineBreak
;
687 bool EndsByBlockBoundary() const {
688 return mRightWSType
== WSType::CurrentBlockBoundary
||
689 mRightWSType
== WSType::OtherBlockBoundary
;
693 * ComparePoint() compares aPoint with the white-spaces.
695 enum class PointPosition
{
696 BeforeStartOfFragment
,
703 template <typename EditorDOMPointType
>
704 PointPosition
ComparePoint(const EditorDOMPointType
& aPoint
) const {
705 MOZ_ASSERT(aPoint
.IsSetAndValid());
706 if (StartRef() == aPoint
) {
707 return PointPosition::StartOfFragment
;
709 if (EndRef() == aPoint
) {
710 return PointPosition::EndOfFragment
;
712 const bool startIsBeforePoint
= StartRef().IsBefore(aPoint
);
713 const bool pointIsBeforeEnd
= aPoint
.IsBefore(EndRef());
714 if (startIsBeforePoint
&& pointIsBeforeEnd
) {
715 return PointPosition::MiddleOfFragment
;
717 if (startIsBeforePoint
) {
718 return PointPosition::AfterEndOfFragment
;
720 if (pointIsBeforeEnd
) {
721 return PointPosition::BeforeStartOfFragment
;
723 return PointPosition::NotInSameDOMTree
;
727 // Initializers should be accessible only from `TextFragmentData`.
728 friend class WSRunScanner::TextFragmentData
;
729 VisibleWhiteSpacesData()
730 : mLeftWSType(WSType::NotInitialized
),
731 mRightWSType(WSType::NotInitialized
) {}
733 template <typename EditorDOMPointType
>
734 void SetStartPoint(const EditorDOMPointType
& aStartPoint
) {
735 mStartPoint
= aStartPoint
;
737 template <typename EditorDOMPointType
>
738 void SetEndPoint(const EditorDOMPointType
& aEndPoint
) {
739 mEndPoint
= aEndPoint
;
741 void SetStartFrom(WSType aLeftWSType
) { mLeftWSType
= aLeftWSType
; }
742 void SetStartFromLeadingWhiteSpaces() {
743 mLeftWSType
= WSType::LeadingWhiteSpaces
;
745 void SetEndBy(WSType aRightWSType
) { mRightWSType
= aRightWSType
; }
746 void SetEndByTrailingWhiteSpaces() {
747 mRightWSType
= WSType::TrailingWhiteSpaces
;
750 EditorDOMPoint mStartPoint
;
751 EditorDOMPoint mEndPoint
;
752 WSType mLeftWSType
, mRightWSType
;
755 using PointPosition
= VisibleWhiteSpacesData::PointPosition
;
758 * GetInclusiveNextEditableCharPoint() returns aPoint if it points a character
759 * in an editable text node, or start of next editable text node otherwise.
760 * FYI: For the performance, this does not check whether given container
761 * is not after mStart.mReasonContent or not.
763 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
765 EditorDOMPointType
GetInclusiveNextEditableCharPoint(
766 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
767 return TextFragmentDataAtStartRef()
768 .GetInclusiveNextEditableCharPoint
<EditorDOMPointType
>(aPoint
);
772 * GetPreviousEditableCharPoint() returns previous editable point in a
773 * text node. Note that this returns last character point when it meets
774 * non-empty text node, otherwise, returns a point in an empty text node.
775 * FYI: For the performance, this does not check whether given container
776 * is not before mEnd.mReasonContent or not.
778 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
780 EditorDOMPointType
GetPreviousEditableCharPoint(
781 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
782 return TextFragmentDataAtStartRef()
783 .GetPreviousEditableCharPoint
<EditorDOMPointType
>(aPoint
);
787 * GetEndOfCollapsibleASCIIWhiteSpaces() returns the next visible char
788 * (meaning a character except ASCII white-spaces) point or end of last text
789 * node scanning from aPointAtASCIIWhiteSpace.
790 * Note that this may return different text node from the container of
791 * aPointAtASCIIWhiteSpace.
793 template <typename EditorDOMPointType
= EditorDOMPointInText
>
794 EditorDOMPointType
GetEndOfCollapsibleASCIIWhiteSpaces(
795 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
796 nsIEditor::EDirection aDirectionToDelete
) const {
797 MOZ_ASSERT(aDirectionToDelete
== nsIEditor::eNone
||
798 aDirectionToDelete
== nsIEditor::eNext
||
799 aDirectionToDelete
== nsIEditor::ePrevious
);
800 return TextFragmentDataAtStartRef()
801 .GetEndOfCollapsibleASCIIWhiteSpaces
<EditorDOMPointType
>(
802 aPointAtASCIIWhiteSpace
, aDirectionToDelete
);
806 * GetFirstASCIIWhiteSpacePointCollapsedTo() returns the first ASCII
807 * white-space which aPointAtASCIIWhiteSpace belongs to. In other words,
808 * the white-space at aPointAtASCIIWhiteSpace should be collapsed into
810 * Note that this may return different text node from the container of
811 * aPointAtASCIIWhiteSpace.
813 template <typename EditorDOMPointType
= EditorDOMPointInText
>
814 EditorDOMPointType
GetFirstASCIIWhiteSpacePointCollapsedTo(
815 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
816 nsIEditor::EDirection aDirectionToDelete
) const {
817 MOZ_ASSERT(aDirectionToDelete
== nsIEditor::eNone
||
818 aDirectionToDelete
== nsIEditor::eNext
||
819 aDirectionToDelete
== nsIEditor::ePrevious
);
820 return TextFragmentDataAtStartRef()
821 .GetFirstASCIIWhiteSpacePointCollapsedTo
<EditorDOMPointType
>(
822 aPointAtASCIIWhiteSpace
, aDirectionToDelete
);
825 EditorDOMPointInText
GetPreviousCharPointFromPointInText(
826 const EditorDOMPointInText
& aPoint
) const;
828 char16_t
GetCharAt(Text
* aTextNode
, uint32_t aOffset
) const;
831 * TextFragmentData stores the information of white-space sequence which
832 * contains `aPoint` of the constructor.
834 class MOZ_STACK_CLASS TextFragmentData final
{
836 class NoBreakingSpaceData
;
837 class MOZ_STACK_CLASS BoundaryData final
{
839 using NoBreakingSpaceData
=
840 WSRunScanner::TextFragmentData::NoBreakingSpaceData
;
843 * ScanCollapsibleWhiteSpaceStartFrom() returns start boundary data of
844 * white-spaces containing aPoint. When aPoint is in a text node and
845 * points a non-white-space character or the text node is preformatted,
846 * this returns the data at aPoint.
848 * @param aPoint Scan start point.
849 * @param aEditableBlockParentOrTopmostEditableInlineElement
850 * Nearest editable block parent element of
851 * aPoint if there is. Otherwise, inline editing
853 * @param aEditingHost Active editing host.
854 * @param aNBSPData Optional. If set, this recodes first and last
857 template <typename EditorDOMPointType
>
858 static BoundaryData
ScanCollapsibleWhiteSpaceStartFrom(
859 const EditorDOMPointType
& aPoint
,
860 const Element
& aEditableBlockParentOrTopmostEditableInlineElement
,
861 const Element
* aEditingHost
, NoBreakingSpaceData
* aNBSPData
,
862 BlockInlineCheck aBlockInlineCheck
);
865 * ScanCollapsibleWhiteSpaceEndFrom() returns end boundary data of
866 * white-spaces containing aPoint. When aPoint is in a text node and
867 * points a non-white-space character or the text node is preformatted,
868 * this returns the data at aPoint.
870 * @param aPoint Scan start point.
871 * @param aEditableBlockParentOrTopmostEditableInlineElement
872 * Nearest editable block parent element of
873 * aPoint if there is. Otherwise, inline editing
875 * @param aEditingHost Active editing host.
876 * @param aNBSPData Optional. If set, this recodes first and last
879 template <typename EditorDOMPointType
>
880 static BoundaryData
ScanCollapsibleWhiteSpaceEndFrom(
881 const EditorDOMPointType
& aPoint
,
882 const Element
& aEditableBlockParentOrTopmostEditableInlineElement
,
883 const Element
* aEditingHost
, NoBreakingSpaceData
* aNBSPData
,
884 BlockInlineCheck aBlockInlineCheck
);
886 BoundaryData() = default;
887 template <typename EditorDOMPointType
>
888 BoundaryData(const EditorDOMPointType
& aPoint
, nsIContent
& aReasonContent
,
890 : mReasonContent(&aReasonContent
),
891 mPoint(aPoint
.template To
<EditorDOMPoint
>()),
893 bool Initialized() const { return mReasonContent
&& mPoint
.IsSet(); }
895 nsIContent
* GetReasonContent() const { return mReasonContent
; }
896 const EditorDOMPoint
& PointRef() const { return mPoint
; }
897 WSType
RawReason() const { return mReason
; }
899 bool IsNonCollapsibleCharacters() const {
900 return mReason
== WSType::NonCollapsibleCharacters
;
902 bool IsSpecialContent() const {
903 return mReason
== WSType::SpecialContent
;
905 bool IsBRElement() const { return mReason
== WSType::BRElement
; }
906 bool IsPreformattedLineBreak() const {
907 return mReason
== WSType::PreformattedLineBreak
;
909 bool IsCurrentBlockBoundary() const {
910 return mReason
== WSType::CurrentBlockBoundary
;
912 bool IsOtherBlockBoundary() const {
913 return mReason
== WSType::OtherBlockBoundary
;
915 bool IsBlockBoundary() const {
916 return mReason
== WSType::CurrentBlockBoundary
||
917 mReason
== WSType::OtherBlockBoundary
;
919 bool IsHardLineBreak() const {
920 return mReason
== WSType::CurrentBlockBoundary
||
921 mReason
== WSType::OtherBlockBoundary
||
922 mReason
== WSType::BRElement
||
923 mReason
== WSType::PreformattedLineBreak
;
925 MOZ_NEVER_INLINE_DEBUG Element
* OtherBlockElementPtr() const {
926 MOZ_DIAGNOSTIC_ASSERT(mReasonContent
->IsElement());
927 return mReasonContent
->AsElement();
929 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* BRElementPtr() const {
930 MOZ_DIAGNOSTIC_ASSERT(mReasonContent
->IsHTMLElement(nsGkAtoms::br
));
931 return static_cast<HTMLBRElement
*>(mReasonContent
.get());
936 * Helper methods of ScanCollapsibleWhiteSpaceStartFrom() and
937 * ScanCollapsibleWhiteSpaceEndFrom() when they need to scan in a text
940 template <typename EditorDOMPointType
>
941 static Maybe
<BoundaryData
> ScanCollapsibleWhiteSpaceStartInTextNode(
942 const EditorDOMPointType
& aPoint
, NoBreakingSpaceData
* aNBSPData
,
943 BlockInlineCheck aBlockInlineCheck
);
944 template <typename EditorDOMPointType
>
945 static Maybe
<BoundaryData
> ScanCollapsibleWhiteSpaceEndInTextNode(
946 const EditorDOMPointType
& aPoint
, NoBreakingSpaceData
* aNBSPData
,
947 BlockInlineCheck aBlockInlineCheck
);
949 nsCOMPtr
<nsIContent
> mReasonContent
;
950 EditorDOMPoint mPoint
;
951 // Must be one of WSType::NotInitialized,
952 // WSType::NonCollapsibleCharacters, WSType::SpecialContent,
953 // WSType::BRElement, WSType::CurrentBlockBoundary or
954 // WSType::OtherBlockBoundary.
955 WSType mReason
= WSType::NotInitialized
;
958 class MOZ_STACK_CLASS NoBreakingSpaceData final
{
960 enum class Scanning
{ Forward
, Backward
};
961 void NotifyNBSP(const EditorDOMPointInText
& aPoint
,
962 Scanning aScanningDirection
) {
963 MOZ_ASSERT(aPoint
.IsSetAndValid());
964 MOZ_ASSERT(aPoint
.IsCharNBSP());
965 if (!mFirst
.IsSet() || aScanningDirection
== Scanning::Backward
) {
968 if (!mLast
.IsSet() || aScanningDirection
== Scanning::Forward
) {
973 const EditorDOMPointInText
& FirstPointRef() const { return mFirst
; }
974 const EditorDOMPointInText
& LastPointRef() const { return mLast
; }
976 bool FoundNBSP() const {
977 MOZ_ASSERT(mFirst
.IsSet() == mLast
.IsSet());
978 return mFirst
.IsSet();
982 EditorDOMPointInText mFirst
;
983 EditorDOMPointInText mLast
;
987 TextFragmentData() = delete;
988 template <typename EditorDOMPointType
>
989 TextFragmentData(const WSRunScanner
& aWSRunScanner
,
990 const EditorDOMPointType
& aPoint
)
991 : TextFragmentData(aPoint
, aWSRunScanner
.mEditingHost
,
992 aWSRunScanner
.mBlockInlineCheck
) {}
993 template <typename EditorDOMPointType
>
994 TextFragmentData(const EditorDOMPointType
& aPoint
,
995 const Element
* aEditingHost
,
996 BlockInlineCheck aBlockInlineCheck
);
998 bool IsInitialized() const {
999 return mStart
.Initialized() && mEnd
.Initialized();
1002 nsIContent
* GetStartReasonContent() const {
1003 return mStart
.GetReasonContent();
1005 nsIContent
* GetEndReasonContent() const { return mEnd
.GetReasonContent(); }
1007 bool StartsFromNonCollapsibleCharacters() const {
1008 return mStart
.IsNonCollapsibleCharacters();
1010 bool StartsFromSpecialContent() const { return mStart
.IsSpecialContent(); }
1011 bool StartsFromBRElement() const { return mStart
.IsBRElement(); }
1012 bool StartsFromVisibleBRElement() const {
1013 return StartsFromBRElement() &&
1014 HTMLEditUtils::IsVisibleBRElement(*GetStartReasonContent());
1016 bool StartsFromInvisibleBRElement() const {
1017 return StartsFromBRElement() &&
1018 HTMLEditUtils::IsInvisibleBRElement(*GetStartReasonContent());
1020 bool StartsFromPreformattedLineBreak() const {
1021 return mStart
.IsPreformattedLineBreak();
1023 bool StartsFromCurrentBlockBoundary() const {
1024 return mStart
.IsCurrentBlockBoundary();
1026 bool StartsFromOtherBlockElement() const {
1027 return mStart
.IsOtherBlockBoundary();
1029 bool StartsFromBlockBoundary() const { return mStart
.IsBlockBoundary(); }
1030 bool StartsFromHardLineBreak() const { return mStart
.IsHardLineBreak(); }
1031 bool EndsByNonCollapsibleCharacters() const {
1032 return mEnd
.IsNonCollapsibleCharacters();
1034 bool EndsBySpecialContent() const { return mEnd
.IsSpecialContent(); }
1035 bool EndsByBRElement() const { return mEnd
.IsBRElement(); }
1036 bool EndsByVisibleBRElement() const {
1037 return EndsByBRElement() &&
1038 HTMLEditUtils::IsVisibleBRElement(*GetEndReasonContent());
1040 bool EndsByInvisibleBRElement() const {
1041 return EndsByBRElement() &&
1042 HTMLEditUtils::IsInvisibleBRElement(*GetEndReasonContent());
1044 bool EndsByPreformattedLineBreak() const {
1045 return mEnd
.IsPreformattedLineBreak();
1047 bool EndsByInvisiblePreformattedLineBreak() const {
1048 return mEnd
.IsPreformattedLineBreak() &&
1049 HTMLEditUtils::IsInvisiblePreformattedNewLine(mEnd
.PointRef());
1051 bool EndsByCurrentBlockBoundary() const {
1052 return mEnd
.IsCurrentBlockBoundary();
1054 bool EndsByOtherBlockElement() const { return mEnd
.IsOtherBlockBoundary(); }
1055 bool EndsByBlockBoundary() const { return mEnd
.IsBlockBoundary(); }
1057 WSType
StartRawReason() const { return mStart
.RawReason(); }
1058 WSType
EndRawReason() const { return mEnd
.RawReason(); }
1060 MOZ_NEVER_INLINE_DEBUG Element
* StartReasonOtherBlockElementPtr() const {
1061 return mStart
.OtherBlockElementPtr();
1063 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* StartReasonBRElementPtr() const {
1064 return mStart
.BRElementPtr();
1066 MOZ_NEVER_INLINE_DEBUG Element
* EndReasonOtherBlockElementPtr() const {
1067 return mEnd
.OtherBlockElementPtr();
1069 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* EndReasonBRElementPtr() const {
1070 return mEnd
.BRElementPtr();
1073 const EditorDOMPoint
& StartRef() const { return mStart
.PointRef(); }
1074 const EditorDOMPoint
& EndRef() const { return mEnd
.PointRef(); }
1076 const EditorDOMPoint
& ScanStartRef() const { return mScanStartPoint
; }
1078 bool FoundNoBreakingWhiteSpaces() const { return mNBSPData
.FoundNBSP(); }
1079 const EditorDOMPointInText
& FirstNBSPPointRef() const {
1080 return mNBSPData
.FirstPointRef();
1082 const EditorDOMPointInText
& LastNBSPPointRef() const {
1083 return mNBSPData
.LastPointRef();
1086 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
1088 EditorDOMPointType
GetInclusiveNextEditableCharPoint(
1089 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const;
1090 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
1092 EditorDOMPointType
GetPreviousEditableCharPoint(
1093 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const;
1095 template <typename EditorDOMPointType
= EditorDOMPointInText
>
1096 EditorDOMPointType
GetEndOfCollapsibleASCIIWhiteSpaces(
1097 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
1098 nsIEditor::EDirection aDirectionToDelete
) const;
1099 template <typename EditorDOMPointType
= EditorDOMPointInText
>
1100 EditorDOMPointType
GetFirstASCIIWhiteSpacePointCollapsedTo(
1101 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
1102 nsIEditor::EDirection aDirectionToDelete
) const;
1105 * GetNonCollapsedRangeInTexts() returns non-empty range in texts which
1106 * is the largest range in aRange if there is some text nodes.
1108 EditorDOMRangeInTexts
GetNonCollapsedRangeInTexts(
1109 const EditorDOMRange
& aRange
) const;
1112 * InvisibleLeadingWhiteSpaceRangeRef() retruns reference to two DOM points,
1113 * start of the line and first visible point or end of the hard line. When
1114 * this returns non-positioned range or positioned but collapsed range,
1115 * there is no invisible leading white-spaces.
1116 * Note that if there are only invisible white-spaces in a hard line,
1117 * this returns all of the white-spaces.
1119 const EditorDOMRange
& InvisibleLeadingWhiteSpaceRangeRef() const;
1122 * InvisibleTrailingWhiteSpaceRangeRef() returns reference to two DOM
1123 * points, first invisible white-space and end of the hard line. When this
1124 * returns non-positioned range or positioned but collapsed range,
1125 * there is no invisible trailing white-spaces.
1126 * Note that if there are only invisible white-spaces in a hard line,
1127 * this returns all of the white-spaces.
1129 const EditorDOMRange
& InvisibleTrailingWhiteSpaceRangeRef() const;
1132 * GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt() returns new
1133 * invisible leading white-space range which should be removed if
1134 * splitting invisible white-space sequence at aPointToSplit creates
1135 * new invisible leading white-spaces in the new line.
1136 * Note that the result may be collapsed range if the point is around
1137 * invisible white-spaces.
1139 template <typename EditorDOMPointType
>
1140 EditorDOMRange
GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(
1141 const EditorDOMPointType
& aPointToSplit
) const {
1142 // If there are invisible trailing white-spaces and some or all of them
1143 // become invisible leading white-spaces in the new line, although we
1144 // don't need to delete them, but for aesthetically and backward
1145 // compatibility, we should remove them.
1146 const EditorDOMRange
& trailingWhiteSpaceRange
=
1147 InvisibleTrailingWhiteSpaceRangeRef();
1148 // XXX Why don't we check leading white-spaces too?
1149 if (!trailingWhiteSpaceRange
.IsPositioned()) {
1150 return trailingWhiteSpaceRange
;
1152 // If the point is before the trailing white-spaces, the new line won't
1153 // start with leading white-spaces.
1154 if (aPointToSplit
.IsBefore(trailingWhiteSpaceRange
.StartRef())) {
1155 return EditorDOMRange();
1157 // If the point is in the trailing white-spaces, the new line may
1158 // start with some leading white-spaces. Returning collapsed range
1159 // is intentional because the caller may want to know whether the
1160 // point is in trailing white-spaces or not.
1161 if (aPointToSplit
.EqualsOrIsBefore(trailingWhiteSpaceRange
.EndRef())) {
1162 return EditorDOMRange(trailingWhiteSpaceRange
.StartRef(),
1165 // Otherwise, if the point is after the trailing white-spaces, it may
1166 // be just outside of the text node. E.g., end of parent element.
1167 // This is possible case but the validation cost is not worthwhile
1168 // due to the runtime cost in the worst case. Therefore, we should just
1169 // return collapsed range at the end of trailing white-spaces. Then,
1170 // callers can know the point is immediately after the trailing
1172 return EditorDOMRange(trailingWhiteSpaceRange
.EndRef());
1176 * GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt() returns new
1177 * invisible trailing white-space range which should be removed if
1178 * splitting invisible white-space sequence at aPointToSplit creates
1179 * new invisible trailing white-spaces in the new line.
1180 * Note that the result may be collapsed range if the point is around
1181 * invisible white-spaces.
1183 template <typename EditorDOMPointType
>
1184 EditorDOMRange
GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(
1185 const EditorDOMPointType
& aPointToSplit
) const {
1186 // If there are invisible leading white-spaces and some or all of them
1187 // become end of current line, they will become visible. Therefore, we
1188 // need to delete the invisible leading white-spaces before insertion
1190 const EditorDOMRange
& leadingWhiteSpaceRange
=
1191 InvisibleLeadingWhiteSpaceRangeRef();
1192 if (!leadingWhiteSpaceRange
.IsPositioned()) {
1193 return leadingWhiteSpaceRange
;
1195 // If the point equals or is after the leading white-spaces, the line
1196 // will end without trailing white-spaces.
1197 if (leadingWhiteSpaceRange
.EndRef().IsBefore(aPointToSplit
)) {
1198 return EditorDOMRange();
1200 // If the point is in the leading white-spaces, the line may
1201 // end with some trailing white-spaces. Returning collapsed range
1202 // is intentional because the caller may want to know whether the
1203 // point is in leading white-spaces or not.
1204 if (leadingWhiteSpaceRange
.StartRef().EqualsOrIsBefore(aPointToSplit
)) {
1205 return EditorDOMRange(aPointToSplit
, leadingWhiteSpaceRange
.EndRef());
1207 // Otherwise, if the point is before the leading white-spaces, it may
1208 // be just outside of the text node. E.g., start of parent element.
1209 // This is possible case but the validation cost is not worthwhile
1210 // due to the runtime cost in the worst case. Therefore, we should
1211 // just return collapsed range at start of the leading white-spaces.
1212 // Then, callers can know the point is immediately before the leading
1214 return EditorDOMRange(leadingWhiteSpaceRange
.StartRef());
1218 * FollowingContentMayBecomeFirstVisibleContent() returns true if some
1219 * content may be first visible content after removing content after aPoint.
1220 * Note that it's completely broken what this does. Don't use this method
1223 template <typename EditorDOMPointType
>
1224 bool FollowingContentMayBecomeFirstVisibleContent(
1225 const EditorDOMPointType
& aPoint
) const {
1226 MOZ_ASSERT(aPoint
.IsSetAndValid());
1227 if (!mStart
.IsHardLineBreak()) {
1230 // If the point is before start of text fragment, that means that the
1231 // point may be at the block boundary or inline element boundary.
1232 if (aPoint
.EqualsOrIsBefore(mStart
.PointRef())) {
1235 // VisibleWhiteSpacesData is marked as start of line only when it
1236 // represents leading white-spaces.
1237 const EditorDOMRange
& leadingWhiteSpaceRange
=
1238 InvisibleLeadingWhiteSpaceRangeRef();
1239 if (!leadingWhiteSpaceRange
.StartRef().IsSet()) {
1242 if (aPoint
.EqualsOrIsBefore(leadingWhiteSpaceRange
.StartRef())) {
1245 if (!leadingWhiteSpaceRange
.EndRef().IsSet()) {
1248 return aPoint
.EqualsOrIsBefore(leadingWhiteSpaceRange
.EndRef());
1252 * PrecedingContentMayBecomeInvisible() returns true if end of preceding
1253 * content is collapsed (when ends with an ASCII white-space).
1254 * Note that it's completely broken what this does. Don't use this method
1257 template <typename EditorDOMPointType
>
1258 bool PrecedingContentMayBecomeInvisible(
1259 const EditorDOMPointType
& aPoint
) const {
1260 MOZ_ASSERT(aPoint
.IsSetAndValid());
1261 // If this fragment is ends by block boundary, always the caller needs
1262 // additional check.
1263 if (mEnd
.IsBlockBoundary()) {
1267 // If the point is in visible white-spaces and ends with an ASCII
1268 // white-space, it may be collapsed even if it won't be end of line.
1269 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
1270 VisibleWhiteSpacesDataRef();
1271 if (!visibleWhiteSpaces
.IsInitialized()) {
1274 // XXX Odd case, but keep traditional behavior of `FindNearestRun()`.
1275 if (!visibleWhiteSpaces
.StartRef().IsSet()) {
1278 if (!visibleWhiteSpaces
.StartRef().EqualsOrIsBefore(aPoint
)) {
1281 // XXX Odd case, but keep traditional behavior of `FindNearestRun()`.
1282 if (visibleWhiteSpaces
.EndsByTrailingWhiteSpaces()) {
1285 // XXX Must be a bug. This claims that the caller needs additional
1286 // check even when there is no white-spaces.
1287 if (visibleWhiteSpaces
.StartRef() == visibleWhiteSpaces
.EndRef()) {
1290 return aPoint
.IsBefore(visibleWhiteSpaces
.EndRef());
1294 * GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace() may return an
1295 * NBSP point which should be replaced with an ASCII white-space when we're
1296 * inserting text into aPointToInsert. Note that this is a helper method for
1297 * the traditional white-space normalizer. Don't use this with the new
1298 * white-space normalizer.
1299 * Must be called only when VisibleWhiteSpacesDataRef() returns initialized
1300 * instance and previous character of aPointToInsert is in the range.
1302 EditorDOMPointInText
GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
1303 const EditorDOMPoint
& aPointToInsert
) const;
1306 * GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace() may return
1307 * an NBSP point which should be replaced with an ASCII white-space when
1308 * the caller inserts text into aPointToInsert.
1309 * Note that this is a helper method for the traditional white-space
1310 * normalizer. Don't use this with the new white-space normalizer.
1311 * Must be called only when VisibleWhiteSpacesDataRef() returns initialized
1312 * instance, and inclusive next char of aPointToInsert is in the range.
1314 EditorDOMPointInText
1315 GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
1316 const EditorDOMPoint
& aPointToInsert
) const;
1319 * GetReplaceRangeDataAtEndOfDeletionRange() and
1320 * GetReplaceRangeDataAtStartOfDeletionRange() return delete range if
1321 * end or start of deleting range splits invisible trailing/leading
1322 * white-spaces and it may become visible, or return replace range if
1323 * end or start of deleting range splits visible white-spaces and it
1324 * causes some ASCII white-spaces become invisible unless replacing
1327 ReplaceRangeData
GetReplaceRangeDataAtEndOfDeletionRange(
1328 const TextFragmentData
& aTextFragmentDataAtStartToDelete
) const;
1329 ReplaceRangeData
GetReplaceRangeDataAtStartOfDeletionRange(
1330 const TextFragmentData
& aTextFragmentDataAtEndToDelete
) const;
1333 * VisibleWhiteSpacesDataRef() returns reference to visible white-spaces
1334 * data. That is zero or more white-spaces which are visible.
1335 * Note that when there is no visible content, it's not initialized.
1336 * Otherwise, even if there is no white-spaces, it's initialized and
1337 * the range is collapsed in such case.
1339 const VisibleWhiteSpacesData
& VisibleWhiteSpacesDataRef() const;
1342 EditorDOMPoint mScanStartPoint
;
1343 BoundaryData mStart
;
1345 NoBreakingSpaceData mNBSPData
;
1346 RefPtr
<const Element
> mEditingHost
;
1347 mutable Maybe
<EditorDOMRange
> mLeadingWhiteSpaceRange
;
1348 mutable Maybe
<EditorDOMRange
> mTrailingWhiteSpaceRange
;
1349 mutable Maybe
<VisibleWhiteSpacesData
> mVisibleWhiteSpacesData
;
1350 BlockInlineCheck mBlockInlineCheck
;
1353 const TextFragmentData
& TextFragmentDataAtStartRef() const {
1354 return mTextFragmentDataAtStart
;
1357 // The node passed to our constructor.
1358 EditorDOMPoint mScanStartPoint
;
1359 // Together, the above represent the point at which we are building up ws
1362 // The editing host when the instance is created.
1363 RefPtr
<Element
> mEditingHost
;
1367 * ComputeRangeInTextNodesContainingInvisibleWhiteSpaces() returns range
1368 * containing invisible white-spaces if deleting between aStart and aEnd
1369 * causes them become visible.
1371 * @param aStart TextFragmentData at start of deleting range.
1372 * This must be initialized with DOM point in a text node.
1373 * @param aEnd TextFragmentData at end of deleting range.
1374 * This must be initialized with DOM point in a text node.
1376 static EditorDOMRangeInTexts
1377 ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
1378 const TextFragmentData
& aStart
, const TextFragmentData
& aEnd
);
1380 TextFragmentData mTextFragmentDataAtStart
;
1382 const BlockInlineCheck mBlockInlineCheck
;
1384 friend class WhiteSpaceVisibilityKeeper
;
1388 * WhiteSpaceVisibilityKeeper class helps `HTMLEditor` modifying the DOM tree
1389 * with keeps white-space sequence visibility automatically. E.g., invisible
1390 * leading/trailing white-spaces becomes visible, this class members delete
1391 * them. E.g., when splitting visible-white-space sequence, this class may
1392 * replace ASCII white-spaces at split edges with NBSPs.
1394 class WhiteSpaceVisibilityKeeper final
{
1396 using AutoTransactionsConserveSelection
=
1397 EditorBase::AutoTransactionsConserveSelection
;
1398 using EditorType
= EditorBase::EditorType
;
1399 using PointPosition
= WSRunScanner::PointPosition
;
1400 using TextFragmentData
= WSRunScanner::TextFragmentData
;
1401 using VisibleWhiteSpacesData
= WSRunScanner::VisibleWhiteSpacesData
;
1404 WhiteSpaceVisibilityKeeper() = delete;
1405 explicit WhiteSpaceVisibilityKeeper(
1406 const WhiteSpaceVisibilityKeeper
& aOther
) = delete;
1407 WhiteSpaceVisibilityKeeper(WhiteSpaceVisibilityKeeper
&& aOther
) = delete;
1410 * Remove invisible leading white-spaces and trailing white-spaces if there
1411 * are around aPoint.
1413 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
1414 DeleteInvisibleASCIIWhiteSpaces(HTMLEditor
& aHTMLEditor
,
1415 const EditorDOMPoint
& aPoint
);
1418 * Fix up white-spaces before aStartPoint and after aEndPoint in preparation
1419 * for content to keep the white-spaces visibility after the range is deleted.
1420 * Note that the nodes and offsets are adjusted in response to any dom changes
1421 * we make while adjusting white-spaces.
1423 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
1424 PrepareToDeleteRangeAndTrackPoints(HTMLEditor
& aHTMLEditor
,
1425 EditorDOMPoint
* aStartPoint
,
1426 EditorDOMPoint
* aEndPoint
,
1427 const Element
& aEditingHost
) {
1428 MOZ_ASSERT(aStartPoint
->IsSetAndValid());
1429 MOZ_ASSERT(aEndPoint
->IsSetAndValid());
1430 AutoTrackDOMPoint
trackerStart(aHTMLEditor
.RangeUpdaterRef(), aStartPoint
);
1431 AutoTrackDOMPoint
trackerEnd(aHTMLEditor
.RangeUpdaterRef(), aEndPoint
);
1432 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1433 WhiteSpaceVisibilityKeeper::PrepareToDeleteRange(
1434 aHTMLEditor
, EditorDOMRange(*aStartPoint
, *aEndPoint
),
1436 NS_WARNING_ASSERTION(
1437 caretPointOrError
.isOk(),
1438 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() failed");
1439 return caretPointOrError
;
1441 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
1442 PrepareToDeleteRange(HTMLEditor
& aHTMLEditor
,
1443 const EditorDOMPoint
& aStartPoint
,
1444 const EditorDOMPoint
& aEndPoint
,
1445 const Element
& aEditingHost
) {
1446 MOZ_ASSERT(aStartPoint
.IsSetAndValid());
1447 MOZ_ASSERT(aEndPoint
.IsSetAndValid());
1448 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1449 WhiteSpaceVisibilityKeeper::PrepareToDeleteRange(
1450 aHTMLEditor
, EditorDOMRange(aStartPoint
, aEndPoint
), aEditingHost
);
1451 NS_WARNING_ASSERTION(
1452 caretPointOrError
.isOk(),
1453 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() failed");
1454 return caretPointOrError
;
1456 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
1457 PrepareToDeleteRange(HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRange
,
1458 const Element
& aEditingHost
) {
1459 MOZ_ASSERT(aRange
.IsPositionedAndValid());
1460 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1461 WhiteSpaceVisibilityKeeper::
1462 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
1463 aHTMLEditor
, aRange
, aEditingHost
);
1464 NS_WARNING_ASSERTION(
1465 caretPointOrError
.isOk(),
1466 "WhiteSpaceVisibilityKeeper::"
1467 "MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() failed");
1468 return caretPointOrError
;
1472 * PrepareToSplitBlockElement() makes sure that the invisible white-spaces
1473 * not to become visible and returns splittable point.
1475 * @param aHTMLEditor The HTML editor.
1476 * @param aPointToSplit The splitting point in aSplittingBlockElement.
1477 * @param aSplittingBlockElement A block element which will be split.
1479 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<EditorDOMPoint
, nsresult
>
1480 PrepareToSplitBlockElement(HTMLEditor
& aHTMLEditor
,
1481 const EditorDOMPoint
& aPointToSplit
,
1482 const Element
& aSplittingBlockElement
);
1485 * MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement() merges
1486 * first line in aRightBlockElement into end of aLeftBlockElement which
1487 * is a descendant of aRightBlockElement.
1489 * @param aHTMLEditor The HTML editor.
1490 * @param aLeftBlockElement The content will be merged into end of
1492 * @param aRightBlockElement The first line in this element will be
1493 * moved to aLeftBlockElement.
1494 * @param aAtRightBlockChild At a child of aRightBlockElement and inclusive
1495 * ancestor of aLeftBlockElement.
1496 * @param aListElementTagName Set some if aRightBlockElement is a list
1497 * element and it'll be merged with another
1499 * @param aEditingHost The editing host.
1501 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<EditActionResult
, nsresult
>
1502 MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
1503 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
1504 Element
& aRightBlockElement
, const EditorDOMPoint
& aAtRightBlockChild
,
1505 const Maybe
<nsAtom
*>& aListElementTagName
,
1506 const HTMLBRElement
* aPrecedingInvisibleBRElement
,
1507 const Element
& aEditingHost
);
1510 * MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement() merges
1511 * first line in aRightBlockElement into end of aLeftBlockElement which
1512 * is an ancestor of aRightBlockElement, then, removes aRightBlockElement
1513 * if it becomes empty.
1515 * @param aHTMLEditor The HTML editor.
1516 * @param aLeftBlockElement The content will be merged into end of
1518 * @param aRightBlockElement The first line in this element will be
1519 * moved to aLeftBlockElement and maybe
1520 * removed when this becomes empty.
1521 * @param aAtLeftBlockChild At a child of aLeftBlockElement and inclusive
1522 * ancestor of aRightBlockElement.
1523 * @param aLeftContentInBlock The content whose inclusive ancestor is
1524 * aLeftBlockElement.
1525 * @param aListElementTagName Set some if aRightBlockElement is a list
1526 * element and it'll be merged with another
1528 * @param aEditingHost The editing host.
1530 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<EditActionResult
, nsresult
>
1531 MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
1532 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
1533 Element
& aRightBlockElement
, const EditorDOMPoint
& aAtLeftBlockChild
,
1534 nsIContent
& aLeftContentInBlock
,
1535 const Maybe
<nsAtom
*>& aListElementTagName
,
1536 const HTMLBRElement
* aPrecedingInvisibleBRElement
,
1537 const Element
& aEditingHost
);
1540 * MergeFirstLineOfRightBlockElementIntoLeftBlockElement() merges first
1541 * line in aRightBlockElement into end of aLeftBlockElement and removes
1542 * aRightBlockElement when it has only one line.
1544 * @param aHTMLEditor The HTML editor.
1545 * @param aLeftBlockElement The content will be merged into end of
1547 * @param aRightBlockElement The first line in this element will be
1548 * moved to aLeftBlockElement and maybe
1549 * removed when this becomes empty.
1550 * @param aListElementTagName Set some if aRightBlockElement is a list
1551 * element and its type needs to be changed.
1552 * @param aEditingHost The editing host.
1554 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<EditActionResult
, nsresult
>
1555 MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
1556 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
1557 Element
& aRightBlockElement
, const Maybe
<nsAtom
*>& aListElementTagName
,
1558 const HTMLBRElement
* aPrecedingInvisibleBRElement
,
1559 const Element
& aEditingHost
);
1562 * InsertBRElement() inserts a <br> node at (before) aPointToInsert and delete
1563 * unnecessary white-spaces around there and/or replaces white-spaces with
1564 * non-breaking spaces. Note that if the point is in a text node, the
1565 * text node will be split and insert new <br> node between the left node
1566 * and the right node.
1568 * @param aPointToInsert The point to insert new <br> element. Note that
1569 * it'll be inserted before this point. I.e., the
1570 * point will be the point of new <br>.
1571 * @return If succeeded, returns the new <br> element and
1572 * point to put caret.
1574 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CreateElementResult
, nsresult
>
1575 InsertBRElement(HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToInsert
,
1576 const Element
& aEditingHost
);
1579 * Insert aStringToInsert to aPointToInsert and makes any needed adjustments
1580 * to white-spaces around the insertion point.
1582 * @param aStringToInsert The string to insert.
1583 * @param aRangeToBeReplaced The range to be replaced.
1585 template <typename EditorDOMPointType
>
1586 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<InsertTextResult
, nsresult
>
1587 InsertText(HTMLEditor
& aHTMLEditor
, const nsAString
& aStringToInsert
,
1588 const EditorDOMPointType
& aPointToInsert
,
1589 const Element
& aEditingHost
) {
1590 return WhiteSpaceVisibilityKeeper::ReplaceText(
1591 aHTMLEditor
, aStringToInsert
, EditorDOMRange(aPointToInsert
),
1596 * Replace aRangeToReplace with aStringToInsert and makes any needed
1597 * adjustments to white-spaces around both start of the range and end of the
1600 * @param aStringToInsert The string to insert.
1601 * @param aRangeToBeReplaced The range to be replaced.
1603 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<InsertTextResult
, nsresult
>
1604 ReplaceText(HTMLEditor
& aHTMLEditor
, const nsAString
& aStringToInsert
,
1605 const EditorDOMRange
& aRangeToBeReplaced
,
1606 const Element
& aEditingHost
);
1609 * Delete previous white-space of aPoint. This automatically keeps visibility
1610 * of white-spaces around aPoint. E.g., may remove invisible leading
1613 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
1614 DeletePreviousWhiteSpace(HTMLEditor
& aHTMLEditor
,
1615 const EditorDOMPoint
& aPoint
,
1616 const Element
& aEditingHost
);
1619 * Delete inclusive next white-space of aPoint. This automatically keeps
1620 * visiblity of white-spaces around aPoint. E.g., may remove invisible
1621 * trailing white-spaces.
1623 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
1624 DeleteInclusiveNextWhiteSpace(HTMLEditor
& aHTMLEditor
,
1625 const EditorDOMPoint
& aPoint
,
1626 const Element
& aEditingHost
);
1629 * Delete aContentToDelete and may remove/replace white-spaces around it.
1630 * Then, if deleting content makes 2 text nodes around it are adjacent
1631 * siblings, this joins them and put selection at the joined point.
1633 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
1634 DeleteContentNodeAndJoinTextNodesAroundIt(HTMLEditor
& aHTMLEditor
,
1635 nsIContent
& aContentToDelete
,
1636 const EditorDOMPoint
& aCaretPoint
,
1637 const Element
& aEditingHost
);
1640 * Try to normalize visible white-space sequence around aPoint.
1641 * This may collapse `Selection` after replaced text. Therefore, the callers
1642 * of this need to restore `Selection` by themselves (this does not do it for
1643 * performance reason of multiple calls).
1645 template <typename EditorDOMPointType
>
1646 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
1647 NormalizeVisibleWhiteSpacesAt(HTMLEditor
& aHTMLEditor
,
1648 const EditorDOMPointType
& aPoint
);
1652 * Maybe delete invisible white-spaces for keeping make them invisible and/or
1653 * may replace ASCII white-spaces with NBSPs for making visible white-spaces
1656 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
1657 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
1658 HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRangeToDelete
,
1659 const Element
& aEditingHost
);
1662 * MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit() replaces ASCII white-
1663 * spaces which becomes invisible after split with NBSPs.
1665 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
1666 MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
1667 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToSplit
);
1670 * ReplaceTextAndRemoveEmptyTextNodes() replaces the range between
1671 * aRangeToReplace with aReplaceString simply. Additionally, removes
1672 * empty text nodes in the range.
1674 * @param aRangeToReplace Range to replace text.
1675 * @param aReplaceString The new string. Empty string is allowed.
1677 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
1678 ReplaceTextAndRemoveEmptyTextNodes(
1679 HTMLEditor
& aHTMLEditor
, const EditorDOMRangeInTexts
& aRangeToReplace
,
1680 const nsAString
& aReplaceString
);
1683 } // namespace mozilla
1685 #endif // #ifndef WSRunObject_h