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 "EditorBase.h"
10 #include "EditorForwards.h"
11 #include "EditorDOMPoint.h" // for EditorDOMPoint
12 #include "HTMLEditor.h"
13 #include "HTMLEditUtils.h"
15 #include "mozilla/Assertions.h"
16 #include "mozilla/Maybe.h"
17 #include "mozilla/Result.h"
18 #include "mozilla/dom/Element.h"
19 #include "mozilla/dom/HTMLBRElement.h"
20 #include "mozilla/dom/Text.h"
22 #include "nsIContent.h"
27 * WSScanResult is result of ScanNextVisibleNodeOrBlockBoundaryFrom(),
28 * ScanPreviousVisibleNodeOrBlockBoundaryFrom(), and their static wrapper
29 * methods. This will have information of found visible content (and its
30 * position) or reached block element or topmost editable content at the
33 class MOZ_STACK_CLASS WSScanResult final
{
35 using Element
= dom::Element
;
36 using HTMLBRElement
= dom::HTMLBRElement
;
37 using Text
= dom::Text
;
39 enum class WSType
: uint8_t {
41 // Could be the DOM tree is broken as like crash tests.
43 // The scanner cannot work in uncomposed tree, but tried to scan in it.
45 // The run is maybe collapsible white-spaces at start of a hard line.
47 // The run is maybe collapsible white-spaces at end of a hard line.
49 // Collapsible, but visible white-spaces.
50 CollapsibleWhiteSpaces
,
51 // Visible characters except collapsible white-spaces.
52 NonCollapsibleCharacters
,
53 // Special content such as `<img>`, etc.
57 // A linefeed which is preformatted.
58 PreformattedLineBreak
,
59 // Other block's boundary (child block of current block, maybe).
61 // Current block's boundary.
63 // Inline editing host boundary.
64 InlineEditingHostBoundary
,
67 friend std::ostream
& operator<<(std::ostream
& aStream
, const WSType
& aType
) {
69 case WSType::NotInitialized
:
70 return aStream
<< "WSType::NotInitialized";
71 case WSType::UnexpectedError
:
72 return aStream
<< "WSType::UnexpectedError";
73 case WSType::InUncomposedDoc
:
74 return aStream
<< "WSType::InUncomposedDoc";
75 case WSType::LeadingWhiteSpaces
:
76 return aStream
<< "WSType::LeadingWhiteSpaces";
77 case WSType::TrailingWhiteSpaces
:
78 return aStream
<< "WSType::TrailingWhiteSpaces";
79 case WSType::CollapsibleWhiteSpaces
:
80 return aStream
<< "WSType::CollapsibleWhiteSpaces";
81 case WSType::NonCollapsibleCharacters
:
82 return aStream
<< "WSType::NonCollapsibleCharacters";
83 case WSType::SpecialContent
:
84 return aStream
<< "WSType::SpecialContent";
85 case WSType::BRElement
:
86 return aStream
<< "WSType::BRElement";
87 case WSType::PreformattedLineBreak
:
88 return aStream
<< "WSType::PreformattedLineBreak";
89 case WSType::OtherBlockBoundary
:
90 return aStream
<< "WSType::OtherBlockBoundary";
91 case WSType::CurrentBlockBoundary
:
92 return aStream
<< "WSType::CurrentBlockBoundary";
93 case WSType::InlineEditingHostBoundary
:
94 return aStream
<< "WSType::InlineEditingHostBoundary";
96 return aStream
<< "<Illegal value>";
99 friend class WSRunScanner
; // Because of WSType.
101 explicit WSScanResult(WSType aReason
) : mReason(aReason
) {
102 MOZ_ASSERT(mReason
== WSType::UnexpectedError
||
103 mReason
== WSType::NotInitialized
);
107 WSScanResult() = delete;
108 enum class ScanDirection
: bool { Backward
, Forward
};
109 WSScanResult(const WSRunScanner
& aScanner
, ScanDirection aScanDirection
,
110 nsIContent
& aContent
, WSType aReason
)
111 : mContent(&aContent
), mReason(aReason
), mDirection(aScanDirection
) {
112 MOZ_ASSERT(aReason
!= WSType::CollapsibleWhiteSpaces
&&
113 aReason
!= WSType::NonCollapsibleCharacters
&&
114 aReason
!= WSType::PreformattedLineBreak
);
115 AssertIfInvalidData(aScanner
);
117 WSScanResult(const WSRunScanner
& aScanner
, ScanDirection aScanDirection
,
118 const EditorDOMPoint
& aPoint
, WSType aReason
)
119 : mContent(aPoint
.GetContainerAs
<nsIContent
>()),
120 mOffset(Some(aPoint
.Offset())),
122 mDirection(aScanDirection
) {
123 AssertIfInvalidData(aScanner
);
126 static WSScanResult
Error() { return WSScanResult(WSType::UnexpectedError
); }
128 void AssertIfInvalidData(const WSRunScanner
& aScanner
) const;
130 bool Failed() const {
131 return mReason
== WSType::NotInitialized
||
132 mReason
== WSType::UnexpectedError
;
136 * GetContent() returns found visible and editable content/element.
137 * See MOZ_ASSERT_IF()s in AssertIfInvalidData() for the detail.
139 nsIContent
* GetContent() const { return mContent
; }
141 [[nodiscard
]] bool ContentIsElement() const {
142 return mContent
&& mContent
->IsElement();
145 [[nodiscard
]] bool ContentIsText() const {
146 return mContent
&& mContent
->IsText();
150 * The following accessors makes it easier to understand each callers.
152 MOZ_NEVER_INLINE_DEBUG Element
* ElementPtr() const {
153 MOZ_DIAGNOSTIC_ASSERT(mContent
->IsElement());
154 return mContent
->AsElement();
156 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* BRElementPtr() const {
157 MOZ_DIAGNOSTIC_ASSERT(mContent
->IsHTMLElement(nsGkAtoms::br
));
158 return static_cast<HTMLBRElement
*>(mContent
.get());
160 MOZ_NEVER_INLINE_DEBUG Text
* TextPtr() const {
161 MOZ_DIAGNOSTIC_ASSERT(mContent
->IsText());
162 return mContent
->AsText();
166 * Returns true if found or reached content is editable.
168 bool IsContentEditable() const { return mContent
&& mContent
->IsEditable(); }
170 [[nodiscard
]] bool IsContentEditableRoot() const {
171 return mContent
&& mContent
->IsElement() &&
172 HTMLEditUtils::ElementIsEditableRoot(*mContent
->AsElement());
176 * Offset_Deprecated() returns meaningful value only when
177 * InVisibleOrCollapsibleCharacters() returns true or the scanner reached to
178 * start or end of its scanning range and that is same as start or end
179 * container which are specified when the scanner is initialized. If it's
180 * result of scanning backward, this offset means the point of the found
181 * point. Otherwise, i.e., scanning forward, this offset means next point
182 * of the found point. E.g., if it reaches a collapsible white-space, this
183 * offset is at the first non-collapsible character after it.
185 MOZ_NEVER_INLINE_DEBUG
uint32_t Offset_Deprecated() const {
186 NS_ASSERTION(mOffset
.isSome(), "Retrieved non-meaningful offset");
187 return mOffset
.valueOr(0);
191 * Point_Deprecated() returns the position in found visible node or reached
192 * block boundary. So, this returns meaningful point only when
193 * Offset_Deprecated() returns meaningful value.
195 template <typename EditorDOMPointType
>
196 EditorDOMPointType
Point_Deprecated() const {
197 NS_ASSERTION(mOffset
.isSome(), "Retrieved non-meaningful point");
198 return EditorDOMPointType(mContent
, mOffset
.valueOr(0));
202 * PointAtReachedContent() returns the position of found visible content or
203 * reached block element.
205 template <typename EditorDOMPointType
>
206 EditorDOMPointType
PointAtReachedContent() const {
207 MOZ_ASSERT(mContent
);
209 case WSType::CollapsibleWhiteSpaces
:
210 case WSType::NonCollapsibleCharacters
:
211 case WSType::PreformattedLineBreak
:
212 MOZ_DIAGNOSTIC_ASSERT(mOffset
.isSome());
213 return mDirection
== ScanDirection::Forward
214 ? EditorDOMPointType(mContent
, mOffset
.valueOr(0))
215 : EditorDOMPointType(mContent
,
216 std::max(mOffset
.valueOr(1), 1u) - 1);
218 return EditorDOMPointType(mContent
);
223 * PointAfterReachedContent() returns the next position of found visible
224 * content or reached block element.
226 template <typename EditorDOMPointType
>
227 EditorDOMPointType
PointAfterReachedContent() const {
228 MOZ_ASSERT(mContent
);
229 return PointAtReachedContent
<EditorDOMPointType
>()
230 .template NextPointOrAfterContainer
<EditorDOMPointType
>();
234 * The scanner reached <img> or something which is inline and is not a
237 bool ReachedSpecialContent() const {
238 return mReason
== WSType::SpecialContent
;
242 * The point is in visible characters or collapsible white-spaces.
244 bool InVisibleOrCollapsibleCharacters() const {
245 return mReason
== WSType::CollapsibleWhiteSpaces
||
246 mReason
== WSType::NonCollapsibleCharacters
;
250 * The point is in collapsible white-spaces.
252 bool InCollapsibleWhiteSpaces() const {
253 return mReason
== WSType::CollapsibleWhiteSpaces
;
257 * The point is in visible non-collapsible characters.
259 bool InNonCollapsibleCharacters() const {
260 return mReason
== WSType::NonCollapsibleCharacters
;
264 * The scanner reached a <br> element.
266 bool ReachedBRElement() const { return mReason
== WSType::BRElement
; }
267 bool ReachedVisibleBRElement() const {
268 return ReachedBRElement() &&
269 HTMLEditUtils::IsVisibleBRElement(*BRElementPtr());
271 bool ReachedInvisibleBRElement() const {
272 return ReachedBRElement() &&
273 HTMLEditUtils::IsInvisibleBRElement(*BRElementPtr());
276 bool ReachedPreformattedLineBreak() const {
277 return mReason
== WSType::PreformattedLineBreak
;
281 * The scanner reached a <hr> element.
283 bool ReachedHRElement() const {
284 return mContent
&& mContent
->IsHTMLElement(nsGkAtoms::hr
);
288 * The scanner reached current block boundary or other block element.
290 bool ReachedBlockBoundary() const {
291 return mReason
== WSType::CurrentBlockBoundary
||
292 mReason
== WSType::OtherBlockBoundary
;
296 * The scanner reached current block element boundary.
298 bool ReachedCurrentBlockBoundary() const {
299 return mReason
== WSType::CurrentBlockBoundary
;
303 * The scanner reached other block element.
305 bool ReachedOtherBlockElement() const {
306 return mReason
== WSType::OtherBlockBoundary
;
310 * The scanner reached other block element that isn't editable
312 bool ReachedNonEditableOtherBlockElement() const {
313 return ReachedOtherBlockElement() && !GetContent()->IsEditable();
317 * The scanner reached inline editing host boundary.
319 [[nodiscard
]] bool ReachedInlineEditingHostBoundary() const {
320 return mReason
== WSType::InlineEditingHostBoundary
;
324 * The scanner reached something non-text node.
326 bool ReachedSomethingNonTextContent() const {
327 return !InVisibleOrCollapsibleCharacters();
330 [[nodiscard
]] bool ReachedLineBoundary() const {
332 case WSType::CurrentBlockBoundary
:
333 case WSType::OtherBlockBoundary
:
334 case WSType::BRElement
:
335 case WSType::PreformattedLineBreak
:
338 return ReachedHRElement();
343 nsCOMPtr
<nsIContent
> mContent
;
344 Maybe
<uint32_t> mOffset
;
346 ScanDirection mDirection
= ScanDirection::Backward
;
349 class MOZ_STACK_CLASS WSRunScanner final
{
351 using Element
= dom::Element
;
352 using HTMLBRElement
= dom::HTMLBRElement
;
353 using Text
= dom::Text
;
356 using WSType
= WSScanResult::WSType
;
358 enum class IgnoreNonEditableNodes
: bool { No
, Yes
};
359 enum class StopAtNonEditableNode
: bool { No
, Yes
};
360 enum class Scan
: bool { All
, EditableNodes
};
362 [[nodiscard
]] constexpr static IgnoreNonEditableNodes
363 ShouldIgnoreNonEditableSiblingsOrDescendants(Scan aScan
) {
364 return static_cast<IgnoreNonEditableNodes
>(static_cast<bool>(aScan
));
366 [[nodiscard
]] constexpr static StopAtNonEditableNode
367 ShouldStopAtNonEditableNode(Scan aScan
) {
368 return static_cast<StopAtNonEditableNode
>(static_cast<bool>(aScan
));
371 template <typename EditorDOMPointType
>
372 WSRunScanner(Scan aScanMode
, const EditorDOMPointType
& aScanStartPoint
,
373 BlockInlineCheck aBlockInlineCheck
,
374 const Element
* aAncestorLimiter
= nullptr)
375 : mScanStartPoint(aScanStartPoint
.template To
<EditorDOMPoint
>()),
376 mTextFragmentDataAtStart(aScanMode
, mScanStartPoint
, aBlockInlineCheck
,
378 mScanMode(aScanMode
) {}
380 // ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom() returns the first visible
381 // node at or after aPoint. If there is no visible nodes after aPoint,
382 // returns topmost editable inline ancestor at end of current block. See
383 // comments around WSScanResult for the detail. When you reach a character,
384 // this returns WSScanResult both whose Point_Deprecated() and
385 // PointAtReachedContent() return the found character position.
386 template <typename PT
, typename CT
>
387 WSScanResult
ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
388 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const;
389 template <typename PT
, typename CT
>
390 static WSScanResult
ScanInclusiveNextVisibleNodeOrBlockBoundary(
391 Scan aScanMode
, const EditorDOMPointBase
<PT
, CT
>& aPoint
,
392 BlockInlineCheck aBlockInlineCheck
,
393 const Element
* aAncestorLimiter
= nullptr) {
394 return WSRunScanner(aScanMode
, aPoint
, aBlockInlineCheck
, aAncestorLimiter
)
395 .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aPoint
);
398 // ScanPreviousVisibleNodeOrBlockBoundaryFrom() returns the first visible node
399 // before aPoint. If there is no visible nodes before aPoint, returns topmost
400 // editable inline ancestor at start of current block. See comments around
401 // WSScanResult for the detail. When you reach a character, this returns
402 // WSScanResult whose Point_Deprecated() returns next point of the found
403 // character and PointAtReachedContent() returns the point at found character.
404 template <typename PT
, typename CT
>
405 WSScanResult
ScanPreviousVisibleNodeOrBlockBoundaryFrom(
406 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const;
407 template <typename PT
, typename CT
>
408 static WSScanResult
ScanPreviousVisibleNodeOrBlockBoundary(
409 Scan aScanMode
, const EditorDOMPointBase
<PT
, CT
>& aPoint
,
410 BlockInlineCheck aBlockInlineCheck
,
411 const Element
* aAncestorLimiter
= nullptr) {
412 return WSRunScanner(aScanMode
, aPoint
, aBlockInlineCheck
, aAncestorLimiter
)
413 .ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPoint
);
417 * Return a point in a `Text` node which is at current character or next
418 * character if aPoint does not points a character or end of a `Text` node.
420 template <typename EditorDOMPointType
, typename PT
, typename CT
>
421 static EditorDOMPointType
GetInclusiveNextCharPoint(
422 Scan aScanMode
, const EditorDOMPointBase
<PT
, CT
>& aPoint
,
423 BlockInlineCheck aBlockInlineCheck
,
424 const Element
* aAncestorLimiter
= nullptr) {
425 if (aPoint
.IsInTextNode() && !aPoint
.IsEndOfContainer() &&
426 (aScanMode
!= Scan::EditableNodes
||
427 HTMLEditUtils::IsSimplyEditableNode(
428 *aPoint
.template ContainerAs
<Text
>()))) {
429 return EditorDOMPointType(aPoint
.template ContainerAs
<Text
>(),
432 return WSRunScanner(aScanMode
, aPoint
, aBlockInlineCheck
, aAncestorLimiter
)
433 .GetInclusiveNextCharPoint
<EditorDOMPointType
>(aPoint
);
437 * Return a point in a `Text` node which is before aPoint.
439 template <typename EditorDOMPointType
, typename PT
, typename CT
>
440 static EditorDOMPointType
GetPreviousCharPoint(
441 Scan aScanMode
, const EditorDOMPointBase
<PT
, CT
>& aPoint
,
442 BlockInlineCheck aBlockInlineCheck
,
443 const Element
* aAncestorLimiter
= nullptr) {
444 if (aPoint
.IsInTextNode() && !aPoint
.IsStartOfContainer() &&
445 (aScanMode
!= Scan::EditableNodes
||
446 HTMLEditUtils::IsSimplyEditableNode(
447 *aPoint
.template ContainerAs
<Text
>()))) {
448 return EditorDOMPointType(aPoint
.template ContainerAs
<Text
>(),
449 aPoint
.Offset() - 1);
451 return WSRunScanner(aScanMode
, aPoint
, aBlockInlineCheck
, aAncestorLimiter
)
452 .GetPreviousCharPoint
<EditorDOMPointType
>(aPoint
);
456 * Scan aTextNode from end or start to find last or first visible things.
457 * I.e., this returns a point immediately before or after invisible
458 * white-spaces of aTextNode if aTextNode ends or begins with some invisible
460 * Note that the result may not be in different text node if aTextNode has
461 * only invisible white-spaces and there is previous or next text node.
463 template <typename EditorDOMPointType
>
464 static EditorDOMPointType
GetAfterLastVisiblePoint(
465 Scan aScanMode
, Text
& aTextNode
,
466 const Element
* aAncestorLimiter
= nullptr);
467 template <typename EditorDOMPointType
>
468 static EditorDOMPointType
GetFirstVisiblePoint(
469 Scan aScanMode
, Text
& aTextNode
,
470 const Element
* aAncestorLimiter
= nullptr);
473 * GetRangeInTextNodesToForwardDeleteFrom() returns the range to remove
474 * text when caret is at aPoint.
476 static Result
<EditorDOMRangeInTexts
, nsresult
>
477 GetRangeInTextNodesToForwardDeleteFrom(
478 Scan aScanMode
, const EditorDOMPoint
& aPoint
,
479 const Element
* aAncestorLimiter
= nullptr);
482 * GetRangeInTextNodesToBackspaceFrom() returns the range to remove text
483 * when caret is at aPoint.
485 static Result
<EditorDOMRangeInTexts
, nsresult
>
486 GetRangeInTextNodesToBackspaceFrom(Scan aScanMode
,
487 const EditorDOMPoint
& aPoint
,
488 const Element
* aAncestorLimiter
= nullptr);
491 * GetRangesForDeletingAtomicContent() returns the range to delete
492 * aAtomicContent. If it's followed by invisible white-spaces, they will
493 * be included into the range.
495 static EditorDOMRange
GetRangesForDeletingAtomicContent(
496 Scan aScanMode
, const nsIContent
& aAtomicContent
,
497 const Element
* aAncestorLimiter
= nullptr);
500 * GetRangeForDeleteBlockElementBoundaries() returns a range starting from end
501 * of aLeftBlockElement to start of aRightBlockElement and extend invisible
502 * white-spaces around them.
504 * @param aLeftBlockElement The block element which will be joined with
505 * aRightBlockElement.
506 * @param aRightBlockElement The block element which will be joined with
507 * aLeftBlockElement. This must be an element
508 * after aLeftBlockElement.
509 * @param aPointContainingTheOtherBlock
510 * When aRightBlockElement is an ancestor of
511 * aLeftBlockElement, this must be set and the
512 * container must be aRightBlockElement.
513 * When aLeftBlockElement is an ancestor of
514 * aRightBlockElement, this must be set and the
515 * container must be aLeftBlockElement.
516 * Otherwise, must not be set.
518 static EditorDOMRange
GetRangeForDeletingBlockElementBoundaries(
519 Scan aScanMode
, const Element
& aLeftBlockElement
,
520 const Element
& aRightBlockElement
,
521 const EditorDOMPoint
& aPointContainingTheOtherBlock
,
522 const Element
* aAncestorLimiter
= nullptr);
525 * ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() may shrink aRange if it
526 * starts and/or ends with an atomic content, but the range boundary
527 * is in adjacent text nodes. Returns true if this modifies the range.
529 static Result
<bool, nsresult
> ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
530 Scan aScanMode
, nsRange
& aRange
,
531 const Element
* aAncestorLimiter
= nullptr);
534 * GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries() returns
535 * extended range if range boundaries of aRange are in invisible white-spaces.
537 static EditorDOMRange
GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
538 Scan aScanMode
, const EditorDOMRange
& aRange
,
539 const Element
* aAncestorLimiter
= nullptr);
542 * GetPrecedingBRElementUnlessVisibleContentFound() scans a `<br>` element
543 * backward, but stops scanning it if the scanner finds visible character
544 * or something. In other words, this method ignores only invisible
545 * white-spaces between `<br>` element and aPoint.
547 template <typename EditorDOMPointType
>
548 MOZ_NEVER_INLINE_DEBUG
static HTMLBRElement
*
549 GetPrecedingBRElementUnlessVisibleContentFound(
550 Scan aScanMode
, const EditorDOMPointType
& aPoint
,
551 BlockInlineCheck aBlockInlineCheck
,
552 const Element
* aAncestorLimiter
= nullptr) {
553 MOZ_ASSERT(aPoint
.IsSetAndValid());
554 // XXX This method behaves differently even in similar point.
555 // If aPoint is in a text node following `<br>` element, reaches the
556 // `<br>` element when all characters between the `<br>` and
557 // aPoint are ASCII whitespaces.
558 // But if aPoint is not in a text node, e.g., at start of an inline
559 // element which is immediately after a `<br>` element, returns the
560 // `<br>` element even if there is no invisible white-spaces.
561 if (aPoint
.IsStartOfContainer()) {
564 // TODO: Scan for end boundary is redundant in this case, we should optimize
566 TextFragmentData
textFragmentData(aScanMode
, aPoint
, aBlockInlineCheck
,
568 return textFragmentData
.StartsFromBRElement()
569 ? textFragmentData
.StartReasonBRElementPtr()
573 constexpr BlockInlineCheck
BlockInlineCheckMode() const {
574 return mTextFragmentDataAtStart
.BlockInlineCheckMode();
577 const EditorDOMPoint
& ScanStartRef() const { return mScanStartPoint
; }
580 * GetStartReasonContent() and GetEndReasonContent() return a node which
581 * was found by scanning from mScanStartPoint backward or forward. If there
582 * was white-spaces or text from the point, returns the text node. Otherwise,
583 * returns an element which is explained by the following methods. Note that
584 * when the reason is WSType::CurrentBlockBoundary, In most cases, it's
585 * current block element which is editable, but also may be non-element and/or
586 * non-editable. See MOZ_ASSERT_IF()s in WSScanResult::AssertIfInvalidData()
589 nsIContent
* GetStartReasonContent() const {
590 return TextFragmentDataAtStartRef().GetStartReasonContent();
592 nsIContent
* GetEndReasonContent() const {
593 return TextFragmentDataAtStartRef().GetEndReasonContent();
596 bool StartsFromNonCollapsibleCharacters() const {
597 return TextFragmentDataAtStartRef().StartsFromNonCollapsibleCharacters();
599 bool StartsFromSpecialContent() const {
600 return TextFragmentDataAtStartRef().StartsFromSpecialContent();
602 bool StartsFromBRElement() const {
603 return TextFragmentDataAtStartRef().StartsFromBRElement();
605 bool StartsFromVisibleBRElement() const {
606 return TextFragmentDataAtStartRef().StartsFromVisibleBRElement();
608 bool StartsFromInvisibleBRElement() const {
609 return TextFragmentDataAtStartRef().StartsFromInvisibleBRElement();
611 bool StartsFromPreformattedLineBreak() const {
612 return TextFragmentDataAtStartRef().StartsFromPreformattedLineBreak();
614 bool StartsFromCurrentBlockBoundary() const {
615 return TextFragmentDataAtStartRef().StartsFromCurrentBlockBoundary();
617 bool StartsFromOtherBlockElement() const {
618 return TextFragmentDataAtStartRef().StartsFromOtherBlockElement();
620 bool StartsFromBlockBoundary() const {
621 return TextFragmentDataAtStartRef().StartsFromBlockBoundary();
623 bool StartsFromInlineEditingHostBoundary() const {
624 return TextFragmentDataAtStartRef().StartsFromInlineEditingHostBoundary();
626 bool StartsFromHardLineBreak() const {
627 return TextFragmentDataAtStartRef().StartsFromHardLineBreak();
629 bool EndsByNonCollapsibleCharacters() const {
630 return TextFragmentDataAtStartRef().EndsByNonCollapsibleCharacters();
632 bool EndsBySpecialContent() const {
633 return TextFragmentDataAtStartRef().EndsBySpecialContent();
635 bool EndsByBRElement() const {
636 return TextFragmentDataAtStartRef().EndsByBRElement();
638 bool EndsByVisibleBRElement() const {
639 return TextFragmentDataAtStartRef().EndsByVisibleBRElement();
641 bool EndsByInvisibleBRElement() const {
642 return TextFragmentDataAtStartRef().EndsByInvisibleBRElement();
644 bool EndsByPreformattedLineBreak() const {
645 return TextFragmentDataAtStartRef().EndsByPreformattedLineBreak();
647 bool EndsByCurrentBlockBoundary() const {
648 return TextFragmentDataAtStartRef().EndsByCurrentBlockBoundary();
650 bool EndsByOtherBlockElement() const {
651 return TextFragmentDataAtStartRef().EndsByOtherBlockElement();
653 bool EndsByBlockBoundary() const {
654 return TextFragmentDataAtStartRef().EndsByBlockBoundary();
656 bool EndsByInlineEditingHostBoundary() const {
657 return TextFragmentDataAtStartRef().EndsByInlineEditingHostBoundary();
660 MOZ_NEVER_INLINE_DEBUG Element
* StartReasonOtherBlockElementPtr() const {
661 return TextFragmentDataAtStartRef().StartReasonOtherBlockElementPtr();
663 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* StartReasonBRElementPtr() const {
664 return TextFragmentDataAtStartRef().StartReasonBRElementPtr();
666 MOZ_NEVER_INLINE_DEBUG Element
* EndReasonOtherBlockElementPtr() const {
667 return TextFragmentDataAtStartRef().EndReasonOtherBlockElementPtr();
669 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* EndReasonBRElementPtr() const {
670 return TextFragmentDataAtStartRef().EndReasonBRElementPtr();
674 using EditorType
= EditorBase::EditorType
;
676 class TextFragmentData
;
678 // VisibleWhiteSpacesData represents 0 or more visible white-spaces.
679 class MOZ_STACK_CLASS VisibleWhiteSpacesData final
{
681 bool IsInitialized() const {
682 return mLeftWSType
!= WSType::NotInitialized
||
683 mRightWSType
!= WSType::NotInitialized
;
686 EditorDOMPoint
StartRef() const { return mStartPoint
; }
687 EditorDOMPoint
EndRef() const { return mEndPoint
; }
690 * Information why the white-spaces start from (i.e., this indicates the
691 * previous content type of the fragment).
693 bool StartsFromNonCollapsibleCharacters() const {
694 return mLeftWSType
== WSType::NonCollapsibleCharacters
;
696 bool StartsFromSpecialContent() const {
697 return mLeftWSType
== WSType::SpecialContent
;
699 bool StartsFromPreformattedLineBreak() const {
700 return mLeftWSType
== WSType::PreformattedLineBreak
;
704 * Information why the white-spaces end by (i.e., this indicates the
705 * next content type of the fragment).
707 bool EndsByNonCollapsibleCharacters() const {
708 return mRightWSType
== WSType::NonCollapsibleCharacters
;
710 bool EndsByTrailingWhiteSpaces() const {
711 return mRightWSType
== WSType::TrailingWhiteSpaces
;
713 bool EndsBySpecialContent() const {
714 return mRightWSType
== WSType::SpecialContent
;
716 bool EndsByBRElement() const { return mRightWSType
== WSType::BRElement
; }
717 bool EndsByPreformattedLineBreak() const {
718 return mRightWSType
== WSType::PreformattedLineBreak
;
720 bool EndsByBlockBoundary() const {
721 return mRightWSType
== WSType::CurrentBlockBoundary
||
722 mRightWSType
== WSType::OtherBlockBoundary
;
724 bool EndsByInlineEditingHostBoundary() const {
725 return mRightWSType
== WSType::InlineEditingHostBoundary
;
729 * ComparePoint() compares aPoint with the white-spaces.
731 enum class PointPosition
{
732 BeforeStartOfFragment
,
739 template <typename EditorDOMPointType
>
740 PointPosition
ComparePoint(const EditorDOMPointType
& aPoint
) const {
741 MOZ_ASSERT(aPoint
.IsSetAndValid());
742 if (StartRef() == aPoint
) {
743 return PointPosition::StartOfFragment
;
745 if (EndRef() == aPoint
) {
746 return PointPosition::EndOfFragment
;
748 const bool startIsBeforePoint
= StartRef().IsBefore(aPoint
);
749 const bool pointIsBeforeEnd
= aPoint
.IsBefore(EndRef());
750 if (startIsBeforePoint
&& pointIsBeforeEnd
) {
751 return PointPosition::MiddleOfFragment
;
753 if (startIsBeforePoint
) {
754 return PointPosition::AfterEndOfFragment
;
756 if (pointIsBeforeEnd
) {
757 return PointPosition::BeforeStartOfFragment
;
759 return PointPosition::NotInSameDOMTree
;
763 // Initializers should be accessible only from `TextFragmentData`.
764 friend class WSRunScanner::TextFragmentData
;
765 VisibleWhiteSpacesData()
766 : mLeftWSType(WSType::NotInitialized
),
767 mRightWSType(WSType::NotInitialized
) {}
769 template <typename EditorDOMPointType
>
770 void SetStartPoint(const EditorDOMPointType
& aStartPoint
) {
771 mStartPoint
= aStartPoint
;
773 template <typename EditorDOMPointType
>
774 void SetEndPoint(const EditorDOMPointType
& aEndPoint
) {
775 mEndPoint
= aEndPoint
;
777 void SetStartFrom(WSType aLeftWSType
) { mLeftWSType
= aLeftWSType
; }
778 void SetStartFromLeadingWhiteSpaces() {
779 mLeftWSType
= WSType::LeadingWhiteSpaces
;
781 void SetEndBy(WSType aRightWSType
) { mRightWSType
= aRightWSType
; }
782 void SetEndByTrailingWhiteSpaces() {
783 mRightWSType
= WSType::TrailingWhiteSpaces
;
786 EditorDOMPoint mStartPoint
;
787 EditorDOMPoint mEndPoint
;
788 WSType mLeftWSType
, mRightWSType
;
791 using PointPosition
= VisibleWhiteSpacesData::PointPosition
;
794 * Return aPoint if it points a character in a `Text` node, or start of next
795 * `Text` node otherwise.
796 * FYI: For the performance, this does not check whether given container is
797 * not after mStart.mReasonContent or not.
799 template <typename EditorDOMPointType
, typename PT
, typename CT
>
800 EditorDOMPointType
GetInclusiveNextCharPoint(
801 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
802 return TextFragmentDataAtStartRef()
803 .GetInclusiveNextCharPoint
<EditorDOMPointType
>(
804 aPoint
, ShouldIgnoreNonEditableSiblingsOrDescendants(mScanMode
));
808 * Return the previous editable point in a `Text` node. Note that this
809 * returns the last character point when it meets non-empty text node,
810 * otherwise, returns a point in an empty text node.
811 * FYI: For the performance, this does not check whether given container is
812 * not before mEnd.mReasonContent or not.
814 template <typename EditorDOMPointType
, typename PT
, typename CT
>
815 EditorDOMPointType
GetPreviousCharPoint(
816 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
817 return TextFragmentDataAtStartRef()
818 .GetPreviousCharPoint
<EditorDOMPointType
>(
819 aPoint
, ShouldIgnoreNonEditableSiblingsOrDescendants(mScanMode
));
823 * GetEndOfCollapsibleASCIIWhiteSpaces() returns the next visible char
824 * (meaning a character except ASCII white-spaces) point or end of last text
825 * node scanning from aPointAtASCIIWhiteSpace.
826 * Note that this may return different text node from the container of
827 * aPointAtASCIIWhiteSpace.
829 template <typename EditorDOMPointType
>
830 EditorDOMPointType
GetEndOfCollapsibleASCIIWhiteSpaces(
831 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
832 nsIEditor::EDirection aDirectionToDelete
) const {
833 MOZ_ASSERT(aDirectionToDelete
== nsIEditor::eNone
||
834 aDirectionToDelete
== nsIEditor::eNext
||
835 aDirectionToDelete
== nsIEditor::ePrevious
);
836 return TextFragmentDataAtStartRef()
837 .GetEndOfCollapsibleASCIIWhiteSpaces
<EditorDOMPointType
>(
838 aPointAtASCIIWhiteSpace
, aDirectionToDelete
);
842 * GetFirstASCIIWhiteSpacePointCollapsedTo() returns the first ASCII
843 * white-space which aPointAtASCIIWhiteSpace belongs to. In other words,
844 * the white-space at aPointAtASCIIWhiteSpace should be collapsed into
846 * Note that this may return different text node from the container of
847 * aPointAtASCIIWhiteSpace.
849 template <typename EditorDOMPointType
>
850 EditorDOMPointType
GetFirstASCIIWhiteSpacePointCollapsedTo(
851 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
852 nsIEditor::EDirection aDirectionToDelete
) const {
853 MOZ_ASSERT(aDirectionToDelete
== nsIEditor::eNone
||
854 aDirectionToDelete
== nsIEditor::eNext
||
855 aDirectionToDelete
== nsIEditor::ePrevious
);
856 return TextFragmentDataAtStartRef()
857 .GetFirstASCIIWhiteSpacePointCollapsedTo
<EditorDOMPointType
>(
858 aPointAtASCIIWhiteSpace
, aDirectionToDelete
);
862 * TextFragmentData stores the information of white-space sequence which
863 * contains `aPoint` of the constructor.
865 class MOZ_STACK_CLASS TextFragmentData final
{
867 class NoBreakingSpaceData
;
868 class MOZ_STACK_CLASS BoundaryData final
{
870 using NoBreakingSpaceData
=
871 WSRunScanner::TextFragmentData::NoBreakingSpaceData
;
874 * ScanCollapsibleWhiteSpaceStartFrom() returns start boundary data of
875 * white-spaces containing aPoint. When aPoint is in a text node and
876 * points a non-white-space character or the text node is preformatted,
877 * this returns the data at aPoint.
879 * @param aPoint Scan start point.
880 * @param aNBSPData Optional. If set, this recodes first and last
883 template <typename EditorDOMPointType
>
884 static BoundaryData
ScanCollapsibleWhiteSpaceStartFrom(
885 Scan aScanMode
, const EditorDOMPointType
& aPoint
,
886 NoBreakingSpaceData
* aNBSPData
, BlockInlineCheck aBlockInlineCheck
,
887 StopAtNonEditableNode aStopAtNonEditableNode
,
888 const Element
& aAncestorLimiter
);
891 * ScanCollapsibleWhiteSpaceEndFrom() returns end boundary data of
892 * white-spaces containing aPoint. When aPoint is in a text node and
893 * points a non-white-space character or the text node is preformatted,
894 * this returns the data at aPoint.
896 * @param aPoint Scan start point.
897 * @param aNBSPData Optional. If set, this recodes first and last
900 template <typename EditorDOMPointType
>
901 static BoundaryData
ScanCollapsibleWhiteSpaceEndFrom(
902 Scan aScanMode
, const EditorDOMPointType
& aPoint
,
903 NoBreakingSpaceData
* aNBSPData
, BlockInlineCheck aBlockInlineCheck
,
904 StopAtNonEditableNode aStopAtNonEditableNode
,
905 const Element
& aAncestorLimiter
);
907 BoundaryData() = default;
908 template <typename EditorDOMPointType
>
909 BoundaryData(const EditorDOMPointType
& aPoint
, nsIContent
& aReasonContent
,
911 : mReasonContent(&aReasonContent
),
912 mPoint(aPoint
.template To
<EditorDOMPoint
>()),
914 bool Initialized() const { return mReasonContent
&& mPoint
.IsSet(); }
916 nsIContent
* GetReasonContent() const { return mReasonContent
; }
917 const EditorDOMPoint
& PointRef() const { return mPoint
; }
918 WSType
RawReason() const { return mReason
; }
920 bool IsNonCollapsibleCharacters() const {
921 return mReason
== WSType::NonCollapsibleCharacters
;
923 bool IsSpecialContent() const {
924 return mReason
== WSType::SpecialContent
;
926 bool IsBRElement() const { return mReason
== WSType::BRElement
; }
927 bool IsPreformattedLineBreak() const {
928 return mReason
== WSType::PreformattedLineBreak
;
930 bool IsCurrentBlockBoundary() const {
931 return mReason
== WSType::CurrentBlockBoundary
;
933 bool IsOtherBlockBoundary() const {
934 return mReason
== WSType::OtherBlockBoundary
;
936 bool IsBlockBoundary() const {
937 return mReason
== WSType::CurrentBlockBoundary
||
938 mReason
== WSType::OtherBlockBoundary
;
940 bool IsInlineEditingHostBoundary() const {
941 return mReason
== WSType::InlineEditingHostBoundary
;
943 bool IsHardLineBreak() const {
944 return mReason
== WSType::CurrentBlockBoundary
||
945 mReason
== WSType::OtherBlockBoundary
||
946 mReason
== WSType::BRElement
||
947 mReason
== WSType::PreformattedLineBreak
;
949 MOZ_NEVER_INLINE_DEBUG Element
* OtherBlockElementPtr() const {
950 MOZ_DIAGNOSTIC_ASSERT(mReasonContent
->IsElement());
951 return mReasonContent
->AsElement();
953 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* BRElementPtr() const {
954 MOZ_DIAGNOSTIC_ASSERT(mReasonContent
->IsHTMLElement(nsGkAtoms::br
));
955 return static_cast<HTMLBRElement
*>(mReasonContent
.get());
960 * Helper methods of ScanCollapsibleWhiteSpaceStartFrom() and
961 * ScanCollapsibleWhiteSpaceEndFrom() when they need to scan in a text
964 template <typename EditorDOMPointType
>
965 static Maybe
<BoundaryData
> ScanCollapsibleWhiteSpaceStartInTextNode(
966 const EditorDOMPointType
& aPoint
, NoBreakingSpaceData
* aNBSPData
,
967 BlockInlineCheck aBlockInlineCheck
);
968 template <typename EditorDOMPointType
>
969 static Maybe
<BoundaryData
> ScanCollapsibleWhiteSpaceEndInTextNode(
970 const EditorDOMPointType
& aPoint
, NoBreakingSpaceData
* aNBSPData
,
971 BlockInlineCheck aBlockInlineCheck
);
973 nsCOMPtr
<nsIContent
> mReasonContent
;
974 EditorDOMPoint mPoint
;
975 // Must be one of WSType::NotInitialized,
976 // WSType::NonCollapsibleCharacters, WSType::SpecialContent,
977 // WSType::BRElement, WSType::CurrentBlockBoundary,
978 // WSType::OtherBlockBoundary or WSType::InlineEditingHostBoundary.
979 WSType mReason
= WSType::NotInitialized
;
982 class MOZ_STACK_CLASS NoBreakingSpaceData final
{
984 enum class Scanning
{ Forward
, Backward
};
985 void NotifyNBSP(const EditorDOMPointInText
& aPoint
,
986 Scanning aScanningDirection
) {
987 MOZ_ASSERT(aPoint
.IsSetAndValid());
988 MOZ_ASSERT(aPoint
.IsCharNBSP());
989 if (!mFirst
.IsSet() || aScanningDirection
== Scanning::Backward
) {
992 if (!mLast
.IsSet() || aScanningDirection
== Scanning::Forward
) {
997 const EditorDOMPointInText
& FirstPointRef() const { return mFirst
; }
998 const EditorDOMPointInText
& LastPointRef() const { return mLast
; }
1000 bool FoundNBSP() const {
1001 MOZ_ASSERT(mFirst
.IsSet() == mLast
.IsSet());
1002 return mFirst
.IsSet();
1006 EditorDOMPointInText mFirst
;
1007 EditorDOMPointInText mLast
;
1011 TextFragmentData() = delete;
1014 * If aScanMode is Scan::EditableNodes and aPoint is in an editable node,
1015 * this scans only in the editing host. Therefore, it's same as that
1016 * aAncestorLimiter is specified to the editing host.
1018 template <typename EditorDOMPointType
>
1019 TextFragmentData(Scan aScanMode
, const EditorDOMPointType
& aPoint
,
1020 BlockInlineCheck aBlockInlineCheck
,
1021 const Element
* aAncestorLimiter
= nullptr);
1023 bool IsInitialized() const {
1024 return mStart
.Initialized() && mEnd
.Initialized();
1027 constexpr Scan
ScanMode() const { return mScanMode
; }
1029 constexpr BlockInlineCheck
BlockInlineCheckMode() const {
1030 return mBlockInlineCheck
;
1033 nsIContent
* GetStartReasonContent() const {
1034 return mStart
.GetReasonContent();
1036 nsIContent
* GetEndReasonContent() const { return mEnd
.GetReasonContent(); }
1038 bool StartsFromNonCollapsibleCharacters() const {
1039 return mStart
.IsNonCollapsibleCharacters();
1041 bool StartsFromSpecialContent() const { return mStart
.IsSpecialContent(); }
1042 bool StartsFromBRElement() const { return mStart
.IsBRElement(); }
1043 bool StartsFromVisibleBRElement() const {
1044 return StartsFromBRElement() &&
1045 HTMLEditUtils::IsVisibleBRElement(*GetStartReasonContent());
1047 bool StartsFromInvisibleBRElement() const {
1048 return StartsFromBRElement() &&
1049 HTMLEditUtils::IsInvisibleBRElement(*GetStartReasonContent());
1051 bool StartsFromPreformattedLineBreak() const {
1052 return mStart
.IsPreformattedLineBreak();
1054 bool StartsFromCurrentBlockBoundary() const {
1055 return mStart
.IsCurrentBlockBoundary();
1057 bool StartsFromOtherBlockElement() const {
1058 return mStart
.IsOtherBlockBoundary();
1060 bool StartsFromBlockBoundary() const { return mStart
.IsBlockBoundary(); }
1061 bool StartsFromInlineEditingHostBoundary() const {
1062 return mStart
.IsInlineEditingHostBoundary();
1064 bool StartsFromHardLineBreak() const { return mStart
.IsHardLineBreak(); }
1065 bool EndsByNonCollapsibleCharacters() const {
1066 return mEnd
.IsNonCollapsibleCharacters();
1068 bool EndsBySpecialContent() const { return mEnd
.IsSpecialContent(); }
1069 bool EndsByBRElement() const { return mEnd
.IsBRElement(); }
1070 bool EndsByVisibleBRElement() const {
1071 return EndsByBRElement() &&
1072 HTMLEditUtils::IsVisibleBRElement(*GetEndReasonContent());
1074 bool EndsByInvisibleBRElement() const {
1075 return EndsByBRElement() &&
1076 HTMLEditUtils::IsInvisibleBRElement(*GetEndReasonContent());
1078 bool EndsByPreformattedLineBreak() const {
1079 return mEnd
.IsPreformattedLineBreak();
1081 bool EndsByInvisiblePreformattedLineBreak() const {
1082 return mEnd
.IsPreformattedLineBreak() &&
1083 HTMLEditUtils::IsInvisiblePreformattedNewLine(mEnd
.PointRef());
1085 bool EndsByCurrentBlockBoundary() const {
1086 return mEnd
.IsCurrentBlockBoundary();
1088 bool EndsByOtherBlockElement() const { return mEnd
.IsOtherBlockBoundary(); }
1089 bool EndsByBlockBoundary() const { return mEnd
.IsBlockBoundary(); }
1090 bool EndsByInlineEditingHostBoundary() const {
1091 return mEnd
.IsInlineEditingHostBoundary();
1094 WSType
StartRawReason() const { return mStart
.RawReason(); }
1095 WSType
EndRawReason() const { return mEnd
.RawReason(); }
1097 MOZ_NEVER_INLINE_DEBUG Element
* StartReasonOtherBlockElementPtr() const {
1098 return mStart
.OtherBlockElementPtr();
1100 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* StartReasonBRElementPtr() const {
1101 return mStart
.BRElementPtr();
1103 MOZ_NEVER_INLINE_DEBUG Element
* EndReasonOtherBlockElementPtr() const {
1104 return mEnd
.OtherBlockElementPtr();
1106 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* EndReasonBRElementPtr() const {
1107 return mEnd
.BRElementPtr();
1110 const EditorDOMPoint
& StartRef() const { return mStart
.PointRef(); }
1111 const EditorDOMPoint
& EndRef() const { return mEnd
.PointRef(); }
1113 const EditorDOMPoint
& ScanStartRef() const { return mScanStartPoint
; }
1115 bool FoundNoBreakingWhiteSpaces() const { return mNBSPData
.FoundNBSP(); }
1116 const EditorDOMPointInText
& FirstNBSPPointRef() const {
1117 return mNBSPData
.FirstPointRef();
1119 const EditorDOMPointInText
& LastNBSPPointRef() const {
1120 return mNBSPData
.LastPointRef();
1124 * Return inclusive next point in inclusive next `Text` node from aPoint.
1125 * So, it may be in a collapsed white-space or invisible white-spaces.
1127 template <typename EditorDOMPointType
, typename PT
, typename CT
>
1128 [[nodiscard
]] static EditorDOMPointType
GetInclusiveNextCharPoint(
1129 const EditorDOMPointBase
<PT
, CT
>& aPoint
,
1130 BlockInlineCheck aBlockInlineCheck
,
1131 IgnoreNonEditableNodes aIgnoreNonEditableNodes
,
1132 const nsIContent
* aFollowingLimiterContent
= nullptr);
1134 template <typename EditorDOMPointType
, typename PT
, typename CT
>
1135 [[nodiscard
]] EditorDOMPointType
GetInclusiveNextCharPoint(
1136 const EditorDOMPointBase
<PT
, CT
>& aPoint
,
1137 IgnoreNonEditableNodes aIgnoreNonEditableNodes
) const {
1138 return GetInclusiveNextCharPoint
<EditorDOMPointType
>(
1139 aPoint
, mBlockInlineCheck
, aIgnoreNonEditableNodes
,
1140 GetEndReasonContent());
1144 * Return previous point in inclusive previous `Text` node from aPoint.
1145 * So, it may be in a collapsed white-space or invisible white-spaces.
1147 template <typename EditorDOMPointType
, typename PT
, typename CT
>
1148 [[nodiscard
]] static EditorDOMPointType
GetPreviousCharPoint(
1149 const EditorDOMPointBase
<PT
, CT
>& aPoint
,
1150 BlockInlineCheck aBlockInlineCheck
,
1151 IgnoreNonEditableNodes aIgnoreNonEditableNodes
,
1152 const nsIContent
* aPrecedingLimiterContent
= nullptr);
1154 template <typename EditorDOMPointType
, typename PT
, typename CT
>
1155 [[nodiscard
]] EditorDOMPointType
GetPreviousCharPoint(
1156 const EditorDOMPointBase
<PT
, CT
>& aPoint
,
1157 IgnoreNonEditableNodes aIgnoreNonEditableNodes
) const {
1158 return GetPreviousCharPoint
<EditorDOMPointType
>(aPoint
, mBlockInlineCheck
,
1159 aIgnoreNonEditableNodes
,
1160 GetStartReasonContent());
1164 * Return end of current collapsible ASCII white-spaces.
1166 * @param aPointAtASCIIWhiteSpace Must be in a sequence of collapsible
1167 * ASCII white-spaces.
1168 * @param aDirectionToDelete The direction to delete.
1170 template <typename EditorDOMPointType
>
1171 [[nodiscard
]] static EditorDOMPointType
GetEndOfCollapsibleASCIIWhiteSpaces(
1172 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
1173 nsIEditor::EDirection aDirectionToDelete
,
1174 BlockInlineCheck aBlockInlineCheck
,
1175 IgnoreNonEditableNodes aIgnoreNonEditableNodes
,
1176 const nsIContent
* aFollowingLimiterContent
= nullptr);
1178 template <typename EditorDOMPointType
>
1179 [[nodiscard
]] EditorDOMPointType
GetEndOfCollapsibleASCIIWhiteSpaces(
1180 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
1181 nsIEditor::EDirection aDirectionToDelete
,
1182 IgnoreNonEditableNodes aIgnoreNonEditableNodes
) const {
1183 return GetEndOfCollapsibleASCIIWhiteSpaces
<EditorDOMPointType
>(
1184 aPointAtASCIIWhiteSpace
, aDirectionToDelete
, mBlockInlineCheck
,
1185 aIgnoreNonEditableNodes
, GetEndReasonContent());
1189 * Return start of current collapsible ASCII white-spaces.
1191 * @param aPointAtASCIIWhiteSpace Must be in a sequence of collapsible
1192 * ASCII white-spaces.
1193 * @param aDirectionToDelete The direction to delete.
1195 template <typename EditorDOMPointType
>
1196 [[nodiscard
]] static EditorDOMPointType
1197 GetFirstASCIIWhiteSpacePointCollapsedTo(
1198 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
1199 nsIEditor::EDirection aDirectionToDelete
,
1200 BlockInlineCheck aBlockInlineCheck
,
1201 IgnoreNonEditableNodes aIgnoreNonEditableNodes
,
1202 const nsIContent
* aPrecedingLimiterContent
= nullptr);
1204 template <typename EditorDOMPointType
>
1205 [[nodiscard
]] EditorDOMPointType
GetFirstASCIIWhiteSpacePointCollapsedTo(
1206 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
1207 nsIEditor::EDirection aDirectionToDelete
,
1208 IgnoreNonEditableNodes aIgnoreNonEditableNodes
) const {
1209 return GetFirstASCIIWhiteSpacePointCollapsedTo
<EditorDOMPointType
>(
1210 aPointAtASCIIWhiteSpace
, aDirectionToDelete
, mBlockInlineCheck
,
1211 aIgnoreNonEditableNodes
, GetStartReasonContent());
1215 * GetNonCollapsedRangeInTexts() returns non-empty range in texts which
1216 * is the largest range in aRange if there is some text nodes.
1218 EditorDOMRangeInTexts
GetNonCollapsedRangeInTexts(
1219 const EditorDOMRange
& aRange
) const;
1222 * InvisibleLeadingWhiteSpaceRangeRef() retruns reference to two DOM points,
1223 * start of the line and first visible point or end of the hard line. When
1224 * this returns non-positioned range or positioned but collapsed range,
1225 * there is no invisible leading white-spaces.
1226 * Note that if there are only invisible white-spaces in a hard line,
1227 * this returns all of the white-spaces.
1229 const EditorDOMRange
& InvisibleLeadingWhiteSpaceRangeRef() const;
1232 * InvisibleTrailingWhiteSpaceRangeRef() returns reference to two DOM
1233 * points, first invisible white-space and end of the hard line. When this
1234 * returns non-positioned range or positioned but collapsed range,
1235 * there is no invisible trailing white-spaces.
1236 * Note that if there are only invisible white-spaces in a hard line,
1237 * this returns all of the white-spaces.
1239 const EditorDOMRange
& InvisibleTrailingWhiteSpaceRangeRef() const;
1242 * GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt() returns new
1243 * invisible leading white-space range which should be removed if
1244 * splitting invisible white-space sequence at aPointToSplit creates
1245 * new invisible leading white-spaces in the new line.
1246 * Note that the result may be collapsed range if the point is around
1247 * invisible white-spaces.
1249 template <typename EditorDOMPointType
>
1250 EditorDOMRange
GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(
1251 const EditorDOMPointType
& aPointToSplit
) const {
1252 // If there are invisible trailing white-spaces and some or all of them
1253 // become invisible leading white-spaces in the new line, although we
1254 // don't need to delete them, but for aesthetically and backward
1255 // compatibility, we should remove them.
1256 const EditorDOMRange
& trailingWhiteSpaceRange
=
1257 InvisibleTrailingWhiteSpaceRangeRef();
1258 // XXX Why don't we check leading white-spaces too?
1259 if (!trailingWhiteSpaceRange
.IsPositioned()) {
1260 return trailingWhiteSpaceRange
;
1262 // If the point is before the trailing white-spaces, the new line won't
1263 // start with leading white-spaces.
1264 if (aPointToSplit
.IsBefore(trailingWhiteSpaceRange
.StartRef())) {
1265 return EditorDOMRange();
1267 // If the point is in the trailing white-spaces, the new line may
1268 // start with some leading white-spaces. Returning collapsed range
1269 // is intentional because the caller may want to know whether the
1270 // point is in trailing white-spaces or not.
1271 if (aPointToSplit
.EqualsOrIsBefore(trailingWhiteSpaceRange
.EndRef())) {
1272 return EditorDOMRange(trailingWhiteSpaceRange
.StartRef(),
1275 // Otherwise, if the point is after the trailing white-spaces, it may
1276 // be just outside of the text node. E.g., end of parent element.
1277 // This is possible case but the validation cost is not worthwhile
1278 // due to the runtime cost in the worst case. Therefore, we should just
1279 // return collapsed range at the end of trailing white-spaces. Then,
1280 // callers can know the point is immediately after the trailing
1282 return EditorDOMRange(trailingWhiteSpaceRange
.EndRef());
1286 * GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt() returns new
1287 * invisible trailing white-space range which should be removed if
1288 * splitting invisible white-space sequence at aPointToSplit creates
1289 * new invisible trailing white-spaces in the new line.
1290 * Note that the result may be collapsed range if the point is around
1291 * invisible white-spaces.
1293 template <typename EditorDOMPointType
>
1294 EditorDOMRange
GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(
1295 const EditorDOMPointType
& aPointToSplit
) const {
1296 // If there are invisible leading white-spaces and some or all of them
1297 // become end of current line, they will become visible. Therefore, we
1298 // need to delete the invisible leading white-spaces before insertion
1300 const EditorDOMRange
& leadingWhiteSpaceRange
=
1301 InvisibleLeadingWhiteSpaceRangeRef();
1302 if (!leadingWhiteSpaceRange
.IsPositioned()) {
1303 return leadingWhiteSpaceRange
;
1305 // If the point equals or is after the leading white-spaces, the line
1306 // will end without trailing white-spaces.
1307 if (leadingWhiteSpaceRange
.EndRef().IsBefore(aPointToSplit
)) {
1308 return EditorDOMRange();
1310 // If the point is in the leading white-spaces, the line may
1311 // end with some trailing white-spaces. Returning collapsed range
1312 // is intentional because the caller may want to know whether the
1313 // point is in leading white-spaces or not.
1314 if (leadingWhiteSpaceRange
.StartRef().EqualsOrIsBefore(aPointToSplit
)) {
1315 return EditorDOMRange(aPointToSplit
, leadingWhiteSpaceRange
.EndRef());
1317 // Otherwise, if the point is before the leading white-spaces, it may
1318 // be just outside of the text node. E.g., start of parent element.
1319 // This is possible case but the validation cost is not worthwhile
1320 // due to the runtime cost in the worst case. Therefore, we should
1321 // just return collapsed range at start of the leading white-spaces.
1322 // Then, callers can know the point is immediately before the leading
1324 return EditorDOMRange(leadingWhiteSpaceRange
.StartRef());
1328 * FollowingContentMayBecomeFirstVisibleContent() returns true if some
1329 * content may be first visible content after removing content after aPoint.
1330 * Note that it's completely broken what this does. Don't use this method
1333 template <typename EditorDOMPointType
>
1334 bool FollowingContentMayBecomeFirstVisibleContent(
1335 const EditorDOMPointType
& aPoint
) const {
1336 MOZ_ASSERT(aPoint
.IsSetAndValid());
1337 if (!mStart
.IsHardLineBreak() && !mStart
.IsInlineEditingHostBoundary()) {
1340 // If the point is before start of text fragment, that means that the
1341 // point may be at the block boundary or inline element boundary.
1342 if (aPoint
.EqualsOrIsBefore(mStart
.PointRef())) {
1345 // VisibleWhiteSpacesData is marked as start of line only when it
1346 // represents leading white-spaces.
1347 const EditorDOMRange
& leadingWhiteSpaceRange
=
1348 InvisibleLeadingWhiteSpaceRangeRef();
1349 if (!leadingWhiteSpaceRange
.StartRef().IsSet()) {
1352 if (aPoint
.EqualsOrIsBefore(leadingWhiteSpaceRange
.StartRef())) {
1355 if (!leadingWhiteSpaceRange
.EndRef().IsSet()) {
1358 return aPoint
.EqualsOrIsBefore(leadingWhiteSpaceRange
.EndRef());
1362 * PrecedingContentMayBecomeInvisible() returns true if end of preceding
1363 * content is collapsed (when ends with an ASCII white-space).
1364 * Note that it's completely broken what this does. Don't use this method
1367 template <typename EditorDOMPointType
>
1368 bool PrecedingContentMayBecomeInvisible(
1369 const EditorDOMPointType
& aPoint
) const {
1370 MOZ_ASSERT(aPoint
.IsSetAndValid());
1371 // If this fragment is ends by block boundary, always the caller needs
1372 // additional check.
1373 if (mEnd
.IsBlockBoundary() || mEnd
.IsInlineEditingHostBoundary()) {
1377 // If the point is in visible white-spaces and ends with an ASCII
1378 // white-space, it may be collapsed even if it won't be end of line.
1379 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
1380 VisibleWhiteSpacesDataRef();
1381 if (!visibleWhiteSpaces
.IsInitialized()) {
1384 // XXX Odd case, but keep traditional behavior of `FindNearestRun()`.
1385 if (!visibleWhiteSpaces
.StartRef().IsSet()) {
1388 if (!visibleWhiteSpaces
.StartRef().EqualsOrIsBefore(aPoint
)) {
1391 // XXX Odd case, but keep traditional behavior of `FindNearestRun()`.
1392 if (visibleWhiteSpaces
.EndsByTrailingWhiteSpaces()) {
1395 // XXX Must be a bug. This claims that the caller needs additional
1396 // check even when there is no white-spaces.
1397 if (visibleWhiteSpaces
.StartRef() == visibleWhiteSpaces
.EndRef()) {
1400 return aPoint
.IsBefore(visibleWhiteSpaces
.EndRef());
1404 * GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace() may return an
1405 * NBSP point which should be replaced with an ASCII white-space when we're
1406 * inserting text into aPointToInsert. Note that this is a helper method for
1407 * the traditional white-space normalizer. Don't use this with the new
1408 * white-space normalizer.
1409 * Must be called only when VisibleWhiteSpacesDataRef() returns initialized
1410 * instance and previous character of aPointToInsert is in the range.
1412 EditorDOMPointInText
GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
1413 const EditorDOMPoint
& aPointToInsert
) const;
1416 * GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace() may return
1417 * an NBSP point which should be replaced with an ASCII white-space when
1418 * the caller inserts text into aPointToInsert.
1419 * Note that this is a helper method for the traditional white-space
1420 * normalizer. Don't use this with the new white-space normalizer.
1421 * Must be called only when VisibleWhiteSpacesDataRef() returns initialized
1422 * instance, and inclusive next char of aPointToInsert is in the range.
1424 EditorDOMPointInText
1425 GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
1426 const EditorDOMPoint
& aPointToInsert
) const;
1429 * GetReplaceRangeDataAtEndOfDeletionRange() and
1430 * GetReplaceRangeDataAtStartOfDeletionRange() return delete range if
1431 * end or start of deleting range splits invisible trailing/leading
1432 * white-spaces and it may become visible, or return replace range if
1433 * end or start of deleting range splits visible white-spaces and it
1434 * causes some ASCII white-spaces become invisible unless replacing
1437 ReplaceRangeData
GetReplaceRangeDataAtEndOfDeletionRange(
1438 const TextFragmentData
& aTextFragmentDataAtStartToDelete
) const;
1439 ReplaceRangeData
GetReplaceRangeDataAtStartOfDeletionRange(
1440 const TextFragmentData
& aTextFragmentDataAtEndToDelete
) const;
1443 * VisibleWhiteSpacesDataRef() returns reference to visible white-spaces
1444 * data. That is zero or more white-spaces which are visible.
1445 * Note that when there is no visible content, it's not initialized.
1446 * Otherwise, even if there is no white-spaces, it's initialized and
1447 * the range is collapsed in such case.
1449 const VisibleWhiteSpacesData
& VisibleWhiteSpacesDataRef() const;
1452 EditorDOMPoint mScanStartPoint
;
1453 BoundaryData mStart
;
1455 NoBreakingSpaceData mNBSPData
;
1456 mutable Maybe
<EditorDOMRange
> mLeadingWhiteSpaceRange
;
1457 mutable Maybe
<EditorDOMRange
> mTrailingWhiteSpaceRange
;
1458 mutable Maybe
<VisibleWhiteSpacesData
> mVisibleWhiteSpacesData
;
1459 BlockInlineCheck mBlockInlineCheck
;
1463 const TextFragmentData
& TextFragmentDataAtStartRef() const {
1464 return mTextFragmentDataAtStart
;
1467 // The node passed to our constructor.
1468 EditorDOMPoint mScanStartPoint
;
1469 // Together, the above represent the point at which we are building up ws
1474 * ComputeRangeInTextNodesContainingInvisibleWhiteSpaces() returns range
1475 * containing invisible white-spaces if deleting between aStart and aEnd
1476 * causes them become visible.
1478 * @param aStart TextFragmentData at start of deleting range.
1479 * This must be initialized with DOM point in a text node.
1480 * @param aEnd TextFragmentData at end of deleting range.
1481 * This must be initialized with DOM point in a text node.
1483 static EditorDOMRangeInTexts
1484 ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
1485 const TextFragmentData
& aStart
, const TextFragmentData
& aEnd
);
1487 TextFragmentData mTextFragmentDataAtStart
;
1489 const Scan mScanMode
;
1491 friend class WhiteSpaceVisibilityKeeper
;
1492 friend class WSScanResult
;
1495 } // namespace mozilla
1497 #endif // #ifndef WSRunScanner_h