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 "HTMLEditor.h"
15 #include "HTMLEditUtils.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/Maybe.h"
19 #include "mozilla/Result.h"
20 #include "mozilla/dom/Element.h"
21 #include "mozilla/dom/HTMLBRElement.h"
22 #include "mozilla/dom/Text.h"
24 #include "nsIContent.h"
31 * WSScanResult is result of ScanNextVisibleNodeOrBlockBoundaryFrom(),
32 * ScanPreviousVisibleNodeOrBlockBoundaryFrom(), and their static wrapper
33 * methods. This will have information of found visible content (and its
34 * position) or reached block element or topmost editable content at the
37 class MOZ_STACK_CLASS WSScanResult final
{
39 enum class WSType
: uint8_t {
41 // Could be the DOM tree is broken as like crash tests.
43 // The run is maybe collapsible white-spaces at start of a hard line.
45 // The run is maybe collapsible white-spaces at end of a hard line.
47 // Collapsible, but visible white-spaces.
48 CollapsibleWhiteSpaces
,
49 // Visible characters except collapsible white-spaces.
50 NonCollapsibleCharacters
,
51 // Special content such as `<img>`, etc.
55 // A linefeed which is preformatted.
56 PreformattedLineBreak
,
57 // Other block's boundary (child block of current block, maybe).
59 // Current block's boundary.
63 friend class WSRunScanner
; // Because of WSType.
66 WSScanResult() = delete;
67 MOZ_NEVER_INLINE_DEBUG
WSScanResult(nsIContent
* aContent
, WSType aReason
)
68 : mContent(aContent
), mReason(aReason
) {
69 AssertIfInvalidData();
71 MOZ_NEVER_INLINE_DEBUG
WSScanResult(const EditorDOMPoint
& aPoint
,
73 : mContent(aPoint
.GetContainerAsContent()),
74 mOffset(Some(aPoint
.Offset())),
76 AssertIfInvalidData();
79 MOZ_NEVER_INLINE_DEBUG
void AssertIfInvalidData() const {
81 MOZ_ASSERT(mReason
== WSType::UnexpectedError
||
82 mReason
== WSType::NonCollapsibleCharacters
||
83 mReason
== WSType::CollapsibleWhiteSpaces
||
84 mReason
== WSType::BRElement
||
85 mReason
== WSType::PreformattedLineBreak
||
86 mReason
== WSType::SpecialContent
||
87 mReason
== WSType::CurrentBlockBoundary
||
88 mReason
== WSType::OtherBlockBoundary
);
89 MOZ_ASSERT_IF(mReason
== WSType::UnexpectedError
, !mContent
);
90 MOZ_ASSERT_IF(mReason
== WSType::NonCollapsibleCharacters
||
91 mReason
== WSType::CollapsibleWhiteSpaces
,
92 mContent
&& mContent
->IsText());
93 MOZ_ASSERT_IF(mReason
== WSType::BRElement
,
94 mContent
&& mContent
->IsHTMLElement(nsGkAtoms::br
));
95 MOZ_ASSERT_IF(mReason
== WSType::PreformattedLineBreak
,
96 mContent
&& mContent
->IsText() &&
97 EditorUtils::IsNewLinePreformatted(*mContent
));
99 mReason
== WSType::SpecialContent
,
100 mContent
&& ((mContent
->IsText() && !mContent
->IsEditable()) ||
101 (!mContent
->IsHTMLElement(nsGkAtoms::br
) &&
102 !HTMLEditUtils::IsBlockElement(*mContent
))));
103 MOZ_ASSERT_IF(mReason
== WSType::OtherBlockBoundary
,
104 mContent
&& HTMLEditUtils::IsBlockElement(*mContent
));
105 // If mReason is WSType::CurrentBlockBoundary, mContent can be any content.
106 // In most cases, it's current block element which is editable. However, if
107 // there is no editable block parent, this is topmost editable inline
108 // content. Additionally, if there is no editable content, this is the
109 // container start of scanner and is not editable.
111 mReason
== WSType::CurrentBlockBoundary
,
112 !mContent
|| !mContent
->GetParentElement() ||
113 HTMLEditUtils::IsBlockElement(*mContent
) ||
114 HTMLEditUtils::IsBlockElement(*mContent
->GetParentElement()) ||
115 !mContent
->GetParentElement()->IsEditable());
116 #endif // #ifdef DEBUG
119 bool Failed() const {
120 return mReason
== WSType::NotInitialized
||
121 mReason
== WSType::UnexpectedError
;
125 * GetContent() returns found visible and editable content/element.
126 * See MOZ_ASSERT_IF()s in AssertIfInvalidData() for the detail.
128 nsIContent
* GetContent() const { return mContent
; }
131 * The following accessors makes it easier to understand each callers.
133 MOZ_NEVER_INLINE_DEBUG Element
* ElementPtr() const {
134 MOZ_DIAGNOSTIC_ASSERT(mContent
->IsElement());
135 return mContent
->AsElement();
137 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* BRElementPtr() const {
138 MOZ_DIAGNOSTIC_ASSERT(mContent
->IsHTMLElement(nsGkAtoms::br
));
139 return static_cast<HTMLBRElement
*>(mContent
.get());
141 MOZ_NEVER_INLINE_DEBUG Text
* TextPtr() const {
142 MOZ_DIAGNOSTIC_ASSERT(mContent
->IsText());
143 return mContent
->AsText();
147 * Returns true if found or reached content is ediable.
149 bool IsContentEditable() const { return mContent
&& mContent
->IsEditable(); }
152 * Offset() returns meaningful value only when
153 * InVisibleOrCollapsibleCharacters() returns true or the scanner
154 * reached to start or end of its scanning range and that is same as start or
155 * end container which are specified when the scanner is initialized. If it's
156 * result of scanning backward, this offset means before the found point.
157 * Otherwise, i.e., scanning forward, this offset means after the found point.
159 MOZ_NEVER_INLINE_DEBUG
uint32_t Offset() const {
160 NS_ASSERTION(mOffset
.isSome(), "Retrieved non-meaningful offset");
161 return mOffset
.valueOr(0);
165 * Point() and RawPoint() return the position in found visible node or
166 * reached block boundary. So, they return meaningful point only when
167 * Offset() returns meaningful value.
169 template <typename EditorDOMPointType
>
170 EditorDOMPointType
Point() const {
171 NS_ASSERTION(mOffset
.isSome(), "Retrieved non-meaningful point");
172 return EditorDOMPointType(mContent
, mOffset
.valueOr(0));
176 * PointAtContent() and RawPointAtContent() return the position of found
177 * visible content or reached block element.
179 template <typename EditorDOMPointType
>
180 EditorDOMPointType
PointAtContent() const {
181 MOZ_ASSERT(mContent
);
182 return EditorDOMPointType(mContent
);
186 * PointAfterContent() and RawPointAfterContent() retrun the position after
187 * found visible content or reached block element.
189 template <typename EditorDOMPointType
>
190 EditorDOMPointType
PointAfterContent() const {
191 MOZ_ASSERT(mContent
);
192 return mContent
? EditorDOMPointType::After(mContent
)
193 : EditorDOMPointType();
197 * The scanner reached <img> or something which is inline and is not a
200 bool ReachedSpecialContent() const {
201 return mReason
== WSType::SpecialContent
;
205 * The point is in visible characters or collapsible white-spaces.
207 bool InVisibleOrCollapsibleCharacters() const {
208 return mReason
== WSType::CollapsibleWhiteSpaces
||
209 mReason
== WSType::NonCollapsibleCharacters
;
213 * The point is in collapsible white-spaces.
215 bool InCollapsibleWhiteSpaces() const {
216 return mReason
== WSType::CollapsibleWhiteSpaces
;
220 * The point is in visible non-collapsible characters.
222 bool InNonCollapsibleCharacters() const {
223 return mReason
== WSType::NonCollapsibleCharacters
;
227 * The scanner reached a <br> element.
229 bool ReachedBRElement() const { return mReason
== WSType::BRElement
; }
230 bool ReachedVisibleBRElement() const {
231 return ReachedBRElement() &&
232 HTMLEditUtils::IsVisibleBRElement(*BRElementPtr());
234 bool ReachedInvisibleBRElement() const {
235 return ReachedBRElement() &&
236 HTMLEditUtils::IsInvisibleBRElement(*BRElementPtr());
239 bool ReachedPreformattedLineBreak() const {
240 return mReason
== WSType::PreformattedLineBreak
;
244 * The scanner reached a <hr> element.
246 bool ReachedHRElement() const {
247 return mContent
&& mContent
->IsHTMLElement(nsGkAtoms::hr
);
251 * The scanner reached current block boundary or other block element.
253 bool ReachedBlockBoundary() const {
254 return mReason
== WSType::CurrentBlockBoundary
||
255 mReason
== WSType::OtherBlockBoundary
;
259 * The scanner reached current block element boundary.
261 bool ReachedCurrentBlockBoundary() const {
262 return mReason
== WSType::CurrentBlockBoundary
;
266 * The scanner reached other block element.
268 bool ReachedOtherBlockElement() const {
269 return mReason
== WSType::OtherBlockBoundary
;
273 * The scanner reached other block element that isn't editable
275 bool ReachedNonEditableOtherBlockElement() const {
276 return ReachedOtherBlockElement() && !GetContent()->IsEditable();
280 * The scanner reached something non-text node.
282 bool ReachedSomethingNonTextContent() const {
283 return !InVisibleOrCollapsibleCharacters();
287 nsCOMPtr
<nsIContent
> mContent
;
288 Maybe
<uint32_t> mOffset
;
292 class MOZ_STACK_CLASS WSRunScanner final
{
294 using WSType
= WSScanResult::WSType
;
296 template <typename EditorDOMPointType
>
297 WSRunScanner(const Element
* aEditingHost
,
298 const EditorDOMPointType
& aScanStartPoint
)
299 : mScanStartPoint(aScanStartPoint
.template To
<EditorDOMPoint
>()),
300 mEditingHost(const_cast<Element
*>(aEditingHost
)),
301 mTextFragmentDataAtStart(mScanStartPoint
, mEditingHost
) {}
303 // ScanNextVisibleNodeOrBlockBoundaryForwardFrom() returns the first visible
304 // node after aPoint. If there is no visible nodes after aPoint, returns
305 // topmost editable inline ancestor at end of current block. See comments
306 // around WSScanResult for the detail.
307 template <typename PT
, typename CT
>
308 WSScanResult
ScanNextVisibleNodeOrBlockBoundaryFrom(
309 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const;
310 template <typename PT
, typename CT
>
311 static WSScanResult
ScanNextVisibleNodeOrBlockBoundary(
312 const Element
* aEditingHost
, const EditorDOMPointBase
<PT
, CT
>& aPoint
) {
313 return WSRunScanner(aEditingHost
, aPoint
)
314 .ScanNextVisibleNodeOrBlockBoundaryFrom(aPoint
);
317 // ScanPreviousVisibleNodeOrBlockBoundaryFrom() returns the first visible node
318 // before aPoint. If there is no visible nodes before aPoint, returns topmost
319 // editable inline ancestor at start of current block. See comments around
320 // WSScanResult for the detail.
321 template <typename PT
, typename CT
>
322 WSScanResult
ScanPreviousVisibleNodeOrBlockBoundaryFrom(
323 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const;
324 template <typename PT
, typename CT
>
325 static WSScanResult
ScanPreviousVisibleNodeOrBlockBoundary(
326 const Element
* aEditingHost
, const EditorDOMPointBase
<PT
, CT
>& aPoint
) {
327 return WSRunScanner(aEditingHost
, aPoint
)
328 .ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPoint
);
332 * GetInclusiveNextEditableCharPoint() returns a point in a text node which
333 * is at current editable character or next editable character if aPoint
334 * does not points an editable character.
336 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
338 static EditorDOMPointType
GetInclusiveNextEditableCharPoint(
339 Element
* aEditingHost
, const EditorDOMPointBase
<PT
, CT
>& aPoint
) {
340 if (aPoint
.IsInTextNode() && !aPoint
.IsEndOfContainer() &&
341 HTMLEditUtils::IsSimplyEditableNode(*aPoint
.ContainerAsText())) {
342 return EditorDOMPointType(aPoint
.ContainerAsText(), aPoint
.Offset());
344 return WSRunScanner(aEditingHost
, aPoint
)
345 .GetInclusiveNextEditableCharPoint
<EditorDOMPointType
>(aPoint
);
349 * GetPreviousEditableCharPoint() returns a point in a text node which
350 * is at previous editable character.
352 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
354 static EditorDOMPointType
GetPreviousEditableCharPoint(
355 Element
* aEditingHost
, const EditorDOMPointBase
<PT
, CT
>& aPoint
) {
356 if (aPoint
.IsInTextNode() && !aPoint
.IsStartOfContainer() &&
357 HTMLEditUtils::IsSimplyEditableNode(*aPoint
.ContainerAsText())) {
358 return EditorDOMPointType(aPoint
.ContainerAsText(), aPoint
.Offset() - 1);
360 return WSRunScanner(aEditingHost
, aPoint
)
361 .GetPreviousEditableCharPoint
<EditorDOMPointType
>(aPoint
);
365 * Scan aTextNode from end or start to find last or first visible things.
366 * I.e., this returns a point immediately before or after invisible
367 * white-spaces of aTextNode if aTextNode ends or begins with some invisible
369 * Note that the result may not be in different text node if aTextNode has
370 * only invisible white-spaces and there is previous or next text node.
372 template <typename EditorDOMPointType
>
373 static EditorDOMPointType
GetAfterLastVisiblePoint(
374 Text
& aTextNode
, const Element
* aAncestorLimiter
);
375 template <typename EditorDOMPointType
>
376 static EditorDOMPointType
GetFirstVisiblePoint(
377 Text
& aTextNode
, const Element
* aAncestorLimiter
);
380 * GetRangeInTextNodesToForwardDeleteFrom() returns the range to remove
381 * text when caret is at aPoint.
383 static Result
<EditorDOMRangeInTexts
, nsresult
>
384 GetRangeInTextNodesToForwardDeleteFrom(Element
* aEditingHost
,
385 const EditorDOMPoint
& aPoint
);
388 * GetRangeInTextNodesToBackspaceFrom() returns the range to remove text
389 * when caret is at aPoint.
391 static Result
<EditorDOMRangeInTexts
, nsresult
>
392 GetRangeInTextNodesToBackspaceFrom(Element
* aEditingHost
,
393 const EditorDOMPoint
& aPoint
);
396 * GetRangesForDeletingAtomicContent() returns the range to delete
397 * aAtomicContent. If it's followed by invisible white-spaces, they will
398 * be included into the range.
400 static EditorDOMRange
GetRangesForDeletingAtomicContent(
401 Element
* aEditingHost
, const nsIContent
& aAtomicContent
);
404 * GetRangeForDeleteBlockElementBoundaries() returns a range starting from end
405 * of aLeftBlockElement to start of aRightBlockElement and extend invisible
406 * white-spaces around them.
408 * @param aHTMLEditor The HTML editor.
409 * @param aLeftBlockElement The block element which will be joined with
410 * aRightBlockElement.
411 * @param aRightBlockElement The block element which will be joined with
412 * aLeftBlockElement. This must be an element
413 * after aLeftBlockElement.
414 * @param aPointContainingTheOtherBlock
415 * When aRightBlockElement is an ancestor of
416 * aLeftBlockElement, this must be set and the
417 * container must be aRightBlockElement.
418 * When aLeftBlockElement is an ancestor of
419 * aRightBlockElement, this must be set and the
420 * container must be aLeftBlockElement.
421 * Otherwise, must not be set.
423 static EditorDOMRange
GetRangeForDeletingBlockElementBoundaries(
424 const HTMLEditor
& aHTMLEditor
, const Element
& aLeftBlockElement
,
425 const Element
& aRightBlockElement
,
426 const EditorDOMPoint
& aPointContainingTheOtherBlock
);
429 * ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() may shrink aRange if it
430 * starts and/or ends with an atomic content, but the range boundary
431 * is in adjacent text nodes. Returns true if this modifies the range.
433 static Result
<bool, nsresult
> ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
434 const HTMLEditor
& aHTMLEditor
, nsRange
& aRange
,
435 const Element
* aEditingHost
);
438 * GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries() returns
439 * extended range if range boundaries of aRange are in invisible white-spaces.
441 static EditorDOMRange
GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
442 Element
* aEditingHost
, const EditorDOMRange
& aRange
);
445 * GetPrecedingBRElementUnlessVisibleContentFound() scans a `<br>` element
446 * backward, but stops scanning it if the scanner finds visible character
447 * or something. In other words, this method ignores only invisible
448 * white-spaces between `<br>` element and aPoint.
450 template <typename EditorDOMPointType
>
451 MOZ_NEVER_INLINE_DEBUG
static HTMLBRElement
*
452 GetPrecedingBRElementUnlessVisibleContentFound(
453 Element
* aEditingHost
, const EditorDOMPointType
& aPoint
) {
454 MOZ_ASSERT(aPoint
.IsSetAndValid());
455 // XXX This method behaves differently even in similar point.
456 // If aPoint is in a text node following `<br>` element, reaches the
457 // `<br>` element when all characters between the `<br>` and
458 // aPoint are ASCII whitespaces.
459 // But if aPoint is not in a text node, e.g., at start of an inline
460 // element which is immediately after a `<br>` element, returns the
461 // `<br>` element even if there is no invisible white-spaces.
462 if (aPoint
.IsStartOfContainer()) {
465 // TODO: Scan for end boundary is redundant in this case, we should optimize
467 TextFragmentData
textFragmentData(aPoint
, aEditingHost
);
468 return textFragmentData
.StartsFromBRElement()
469 ? textFragmentData
.StartReasonBRElementPtr()
473 const EditorDOMPoint
& ScanStartRef() const { return mScanStartPoint
; }
476 * GetStartReasonContent() and GetEndReasonContent() return a node which
477 * was found by scanning from mScanStartPoint backward or forward. If there
478 * was white-spaces or text from the point, returns the text node. Otherwise,
479 * returns an element which is explained by the following methods. Note that
480 * when the reason is WSType::CurrentBlockBoundary, In most cases, it's
481 * current block element which is editable, but also may be non-element and/or
482 * non-editable. See MOZ_ASSERT_IF()s in WSScanResult::AssertIfInvalidData()
485 nsIContent
* GetStartReasonContent() const {
486 return TextFragmentDataAtStartRef().GetStartReasonContent();
488 nsIContent
* GetEndReasonContent() const {
489 return TextFragmentDataAtStartRef().GetEndReasonContent();
492 bool StartsFromNonCollapsibleCharacters() const {
493 return TextFragmentDataAtStartRef().StartsFromNonCollapsibleCharacters();
495 bool StartsFromSpecialContent() const {
496 return TextFragmentDataAtStartRef().StartsFromSpecialContent();
498 bool StartsFromBRElement() const {
499 return TextFragmentDataAtStartRef().StartsFromBRElement();
501 bool StartsFromVisibleBRElement() const {
502 return TextFragmentDataAtStartRef().StartsFromVisibleBRElement();
504 bool StartsFromInvisibleBRElement() const {
505 return TextFragmentDataAtStartRef().StartsFromInvisibleBRElement();
507 bool StartsFromPreformattedLineBreak() const {
508 return TextFragmentDataAtStartRef().StartsFromPreformattedLineBreak();
510 bool StartsFromCurrentBlockBoundary() const {
511 return TextFragmentDataAtStartRef().StartsFromCurrentBlockBoundary();
513 bool StartsFromOtherBlockElement() const {
514 return TextFragmentDataAtStartRef().StartsFromOtherBlockElement();
516 bool StartsFromBlockBoundary() const {
517 return TextFragmentDataAtStartRef().StartsFromBlockBoundary();
519 bool StartsFromHardLineBreak() const {
520 return TextFragmentDataAtStartRef().StartsFromHardLineBreak();
522 bool EndsByNonCollapsibleCharacters() const {
523 return TextFragmentDataAtStartRef().EndsByNonCollapsibleCharacters();
525 bool EndsBySpecialContent() const {
526 return TextFragmentDataAtStartRef().EndsBySpecialContent();
528 bool EndsByBRElement() const {
529 return TextFragmentDataAtStartRef().EndsByBRElement();
531 bool EndsByVisibleBRElement() const {
532 return TextFragmentDataAtStartRef().EndsByVisibleBRElement();
534 bool EndsByInvisibleBRElement() const {
535 return TextFragmentDataAtStartRef().EndsByInvisibleBRElement();
537 bool EndsByPreformattedLineBreak() const {
538 return TextFragmentDataAtStartRef().EndsByPreformattedLineBreak();
540 bool EndsByCurrentBlockBoundary() const {
541 return TextFragmentDataAtStartRef().EndsByCurrentBlockBoundary();
543 bool EndsByOtherBlockElement() const {
544 return TextFragmentDataAtStartRef().EndsByOtherBlockElement();
546 bool EndsByBlockBoundary() const {
547 return TextFragmentDataAtStartRef().EndsByBlockBoundary();
550 MOZ_NEVER_INLINE_DEBUG Element
* StartReasonOtherBlockElementPtr() const {
551 return TextFragmentDataAtStartRef().StartReasonOtherBlockElementPtr();
553 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* StartReasonBRElementPtr() const {
554 return TextFragmentDataAtStartRef().StartReasonBRElementPtr();
556 MOZ_NEVER_INLINE_DEBUG Element
* EndReasonOtherBlockElementPtr() const {
557 return TextFragmentDataAtStartRef().EndReasonOtherBlockElementPtr();
559 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* EndReasonBRElementPtr() const {
560 return TextFragmentDataAtStartRef().EndReasonBRElementPtr();
564 * Active editing host when this instance is created.
566 Element
* GetEditingHost() const { return mEditingHost
; }
569 using EditorType
= EditorBase::EditorType
;
571 class TextFragmentData
;
573 // VisibleWhiteSpacesData represents 0 or more visible white-spaces.
574 class MOZ_STACK_CLASS VisibleWhiteSpacesData final
{
576 bool IsInitialized() const {
577 return mLeftWSType
!= WSType::NotInitialized
||
578 mRightWSType
!= WSType::NotInitialized
;
581 EditorDOMPoint
StartRef() const { return mStartPoint
; }
582 EditorDOMPoint
EndRef() const { return mEndPoint
; }
585 * Information why the white-spaces start from (i.e., this indicates the
586 * previous content type of the fragment).
588 bool StartsFromNonCollapsibleCharacters() const {
589 return mLeftWSType
== WSType::NonCollapsibleCharacters
;
591 bool StartsFromSpecialContent() const {
592 return mLeftWSType
== WSType::SpecialContent
;
594 bool StartsFromPreformattedLineBreak() const {
595 return mLeftWSType
== WSType::PreformattedLineBreak
;
599 * Information why the white-spaces end by (i.e., this indicates the
600 * next content type of the fragment).
602 bool EndsByNonCollapsibleCharacters() const {
603 return mRightWSType
== WSType::NonCollapsibleCharacters
;
605 bool EndsByTrailingWhiteSpaces() const {
606 return mRightWSType
== WSType::TrailingWhiteSpaces
;
608 bool EndsBySpecialContent() const {
609 return mRightWSType
== WSType::SpecialContent
;
611 bool EndsByBRElement() const { return mRightWSType
== WSType::BRElement
; }
612 bool EndsByPreformattedLineBreak() const {
613 return mRightWSType
== WSType::PreformattedLineBreak
;
615 bool EndsByBlockBoundary() const {
616 return mRightWSType
== WSType::CurrentBlockBoundary
||
617 mRightWSType
== WSType::OtherBlockBoundary
;
621 * ComparePoint() compares aPoint with the white-spaces.
623 enum class PointPosition
{
624 BeforeStartOfFragment
,
631 template <typename EditorDOMPointType
>
632 PointPosition
ComparePoint(const EditorDOMPointType
& aPoint
) const {
633 MOZ_ASSERT(aPoint
.IsSetAndValid());
634 if (StartRef() == aPoint
) {
635 return PointPosition::StartOfFragment
;
637 if (EndRef() == aPoint
) {
638 return PointPosition::EndOfFragment
;
640 const bool startIsBeforePoint
= StartRef().IsBefore(aPoint
);
641 const bool pointIsBeforeEnd
= aPoint
.IsBefore(EndRef());
642 if (startIsBeforePoint
&& pointIsBeforeEnd
) {
643 return PointPosition::MiddleOfFragment
;
645 if (startIsBeforePoint
) {
646 return PointPosition::AfterEndOfFragment
;
648 if (pointIsBeforeEnd
) {
649 return PointPosition::BeforeStartOfFragment
;
651 return PointPosition::NotInSameDOMTree
;
655 // Initializers should be accessible only from `TextFragmentData`.
656 friend class WSRunScanner::TextFragmentData
;
657 VisibleWhiteSpacesData()
658 : mLeftWSType(WSType::NotInitialized
),
659 mRightWSType(WSType::NotInitialized
) {}
661 template <typename EditorDOMPointType
>
662 void SetStartPoint(const EditorDOMPointType
& aStartPoint
) {
663 mStartPoint
= aStartPoint
;
665 template <typename EditorDOMPointType
>
666 void SetEndPoint(const EditorDOMPointType
& aEndPoint
) {
667 mEndPoint
= aEndPoint
;
669 void SetStartFrom(WSType aLeftWSType
) { mLeftWSType
= aLeftWSType
; }
670 void SetStartFromLeadingWhiteSpaces() {
671 mLeftWSType
= WSType::LeadingWhiteSpaces
;
673 void SetEndBy(WSType aRightWSType
) { mRightWSType
= aRightWSType
; }
674 void SetEndByTrailingWhiteSpaces() {
675 mRightWSType
= WSType::TrailingWhiteSpaces
;
678 EditorDOMPoint mStartPoint
;
679 EditorDOMPoint mEndPoint
;
680 WSType mLeftWSType
, mRightWSType
;
683 using PointPosition
= VisibleWhiteSpacesData::PointPosition
;
686 * GetInclusiveNextEditableCharPoint() returns aPoint if it points a character
687 * in an editable text node, or start of next editable text node otherwise.
688 * FYI: For the performance, this does not check whether given container
689 * is not after mStart.mReasonContent or not.
691 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
693 EditorDOMPointType
GetInclusiveNextEditableCharPoint(
694 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
695 return TextFragmentDataAtStartRef()
696 .GetInclusiveNextEditableCharPoint
<EditorDOMPointType
>(aPoint
);
700 * GetPreviousEditableCharPoint() returns previous editable point in a
701 * text node. Note that this returns last character point when it meets
702 * non-empty text node, otherwise, returns a point in an empty text node.
703 * FYI: For the performance, this does not check whether given container
704 * is not before mEnd.mReasonContent or not.
706 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
708 EditorDOMPointType
GetPreviousEditableCharPoint(
709 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
710 return TextFragmentDataAtStartRef()
711 .GetPreviousEditableCharPoint
<EditorDOMPointType
>(aPoint
);
715 * GetEndOfCollapsibleASCIIWhiteSpaces() returns the next visible char
716 * (meaning a character except ASCII white-spaces) point or end of last text
717 * node scanning from aPointAtASCIIWhiteSpace.
718 * Note that this may return different text node from the container of
719 * aPointAtASCIIWhiteSpace.
721 template <typename EditorDOMPointType
= EditorDOMPointInText
>
722 EditorDOMPointType
GetEndOfCollapsibleASCIIWhiteSpaces(
723 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
724 nsIEditor::EDirection aDirectionToDelete
) const {
725 MOZ_ASSERT(aDirectionToDelete
== nsIEditor::eNone
||
726 aDirectionToDelete
== nsIEditor::eNext
||
727 aDirectionToDelete
== nsIEditor::ePrevious
);
728 return TextFragmentDataAtStartRef()
729 .GetEndOfCollapsibleASCIIWhiteSpaces
<EditorDOMPointType
>(
730 aPointAtASCIIWhiteSpace
, aDirectionToDelete
);
734 * GetFirstASCIIWhiteSpacePointCollapsedTo() returns the first ASCII
735 * white-space which aPointAtASCIIWhiteSpace belongs to. In other words,
736 * the white-space at aPointAtASCIIWhiteSpace should be collapsed into
738 * Note that this may return different text node from the container of
739 * aPointAtASCIIWhiteSpace.
741 template <typename EditorDOMPointType
= EditorDOMPointInText
>
742 EditorDOMPointType
GetFirstASCIIWhiteSpacePointCollapsedTo(
743 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
744 nsIEditor::EDirection aDirectionToDelete
) const {
745 MOZ_ASSERT(aDirectionToDelete
== nsIEditor::eNone
||
746 aDirectionToDelete
== nsIEditor::eNext
||
747 aDirectionToDelete
== nsIEditor::ePrevious
);
748 return TextFragmentDataAtStartRef()
749 .GetFirstASCIIWhiteSpacePointCollapsedTo
<EditorDOMPointType
>(
750 aPointAtASCIIWhiteSpace
, aDirectionToDelete
);
753 EditorDOMPointInText
GetPreviousCharPointFromPointInText(
754 const EditorDOMPointInText
& aPoint
) const;
756 char16_t
GetCharAt(Text
* aTextNode
, uint32_t aOffset
) const;
759 * TextFragmentData stores the information of white-space sequence which
760 * contains `aPoint` of the constructor.
762 class MOZ_STACK_CLASS TextFragmentData final
{
764 class NoBreakingSpaceData
;
765 class MOZ_STACK_CLASS BoundaryData final
{
767 using NoBreakingSpaceData
=
768 WSRunScanner::TextFragmentData::NoBreakingSpaceData
;
771 * ScanCollapsibleWhiteSpaceStartFrom() returns start boundary data of
772 * white-spaces containing aPoint. When aPoint is in a text node and
773 * points a non-white-space character or the text node is preformatted,
774 * this returns the data at aPoint.
776 * @param aPoint Scan start point.
777 * @param aEditableBlockParentOrTopmostEditableInlineElement
778 * Nearest editable block parent element of
779 * aPoint if there is. Otherwise, inline editing
781 * @param aEditingHost Active editing host.
782 * @param aNBSPData Optional. If set, this recodes first and last
785 template <typename EditorDOMPointType
>
786 static BoundaryData
ScanCollapsibleWhiteSpaceStartFrom(
787 const EditorDOMPointType
& aPoint
,
788 const Element
& aEditableBlockParentOrTopmostEditableInlineElement
,
789 const Element
* aEditingHost
, NoBreakingSpaceData
* aNBSPData
);
792 * ScanCollapsibleWhiteSpaceEndFrom() returns end boundary data of
793 * white-spaces containing aPoint. When aPoint is in a text node and
794 * points a non-white-space character or the text node is preformatted,
795 * this returns the data at aPoint.
797 * @param aPoint Scan start point.
798 * @param aEditableBlockParentOrTopmostEditableInlineElement
799 * Nearest editable block parent element of
800 * aPoint if there is. Otherwise, inline editing
802 * @param aEditingHost Active editing host.
803 * @param aNBSPData Optional. If set, this recodes first and last
806 template <typename EditorDOMPointType
>
807 static BoundaryData
ScanCollapsibleWhiteSpaceEndFrom(
808 const EditorDOMPointType
& aPoint
,
809 const Element
& aEditableBlockParentOrTopmostEditableInlineElement
,
810 const Element
* aEditingHost
, NoBreakingSpaceData
* aNBSPData
);
812 BoundaryData() : mReason(WSType::NotInitialized
) {}
813 template <typename EditorDOMPointType
>
814 BoundaryData(const EditorDOMPointType
& aPoint
, nsIContent
& aReasonContent
,
816 : mReasonContent(&aReasonContent
),
817 mPoint(aPoint
.template To
<EditorDOMPoint
>()),
819 bool Initialized() const { return mReasonContent
&& mPoint
.IsSet(); }
821 nsIContent
* GetReasonContent() const { return mReasonContent
; }
822 const EditorDOMPoint
& PointRef() const { return mPoint
; }
823 WSType
RawReason() const { return mReason
; }
825 bool IsNonCollapsibleCharacters() const {
826 return mReason
== WSType::NonCollapsibleCharacters
;
828 bool IsSpecialContent() const {
829 return mReason
== WSType::SpecialContent
;
831 bool IsBRElement() const { return mReason
== WSType::BRElement
; }
832 bool IsPreformattedLineBreak() const {
833 return mReason
== WSType::PreformattedLineBreak
;
835 bool IsCurrentBlockBoundary() const {
836 return mReason
== WSType::CurrentBlockBoundary
;
838 bool IsOtherBlockBoundary() const {
839 return mReason
== WSType::OtherBlockBoundary
;
841 bool IsBlockBoundary() const {
842 return mReason
== WSType::CurrentBlockBoundary
||
843 mReason
== WSType::OtherBlockBoundary
;
845 bool IsHardLineBreak() const {
846 return mReason
== WSType::CurrentBlockBoundary
||
847 mReason
== WSType::OtherBlockBoundary
||
848 mReason
== WSType::BRElement
||
849 mReason
== WSType::PreformattedLineBreak
;
851 MOZ_NEVER_INLINE_DEBUG Element
* OtherBlockElementPtr() const {
852 MOZ_DIAGNOSTIC_ASSERT(mReasonContent
->IsElement());
853 return mReasonContent
->AsElement();
855 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* BRElementPtr() const {
856 MOZ_DIAGNOSTIC_ASSERT(mReasonContent
->IsHTMLElement(nsGkAtoms::br
));
857 return static_cast<HTMLBRElement
*>(mReasonContent
.get());
862 * Helper methods of ScanCollapsibleWhiteSpaceStartFrom() and
863 * ScanCollapsibleWhiteSpaceEndFrom() when they need to scan in a text
866 template <typename EditorDOMPointType
>
867 static Maybe
<BoundaryData
> ScanCollapsibleWhiteSpaceStartInTextNode(
868 const EditorDOMPointType
& aPoint
, NoBreakingSpaceData
* aNBSPData
);
869 template <typename EditorDOMPointType
>
870 static Maybe
<BoundaryData
> ScanCollapsibleWhiteSpaceEndInTextNode(
871 const EditorDOMPointType
& aPoint
, NoBreakingSpaceData
* aNBSPData
);
873 nsCOMPtr
<nsIContent
> mReasonContent
;
874 EditorDOMPoint mPoint
;
875 // Must be one of WSType::NotInitialized,
876 // WSType::NonCollapsibleCharacters, WSType::SpecialContent,
877 // WSType::BRElement, WSType::CurrentBlockBoundary or
878 // WSType::OtherBlockBoundary.
882 class MOZ_STACK_CLASS NoBreakingSpaceData final
{
884 enum class Scanning
{ Forward
, Backward
};
885 void NotifyNBSP(const EditorDOMPointInText
& aPoint
,
886 Scanning aScanningDirection
) {
887 MOZ_ASSERT(aPoint
.IsSetAndValid());
888 MOZ_ASSERT(aPoint
.IsCharNBSP());
889 if (!mFirst
.IsSet() || aScanningDirection
== Scanning::Backward
) {
892 if (!mLast
.IsSet() || aScanningDirection
== Scanning::Forward
) {
897 const EditorDOMPointInText
& FirstPointRef() const { return mFirst
; }
898 const EditorDOMPointInText
& LastPointRef() const { return mLast
; }
900 bool FoundNBSP() const {
901 MOZ_ASSERT(mFirst
.IsSet() == mLast
.IsSet());
902 return mFirst
.IsSet();
906 EditorDOMPointInText mFirst
;
907 EditorDOMPointInText mLast
;
911 TextFragmentData() = delete;
912 template <typename EditorDOMPointType
>
913 TextFragmentData(const EditorDOMPointType
& aPoint
,
914 const Element
* aEditingHost
);
916 bool IsInitialized() const {
917 return mStart
.Initialized() && mEnd
.Initialized();
920 nsIContent
* GetStartReasonContent() const {
921 return mStart
.GetReasonContent();
923 nsIContent
* GetEndReasonContent() const { return mEnd
.GetReasonContent(); }
925 bool StartsFromNonCollapsibleCharacters() const {
926 return mStart
.IsNonCollapsibleCharacters();
928 bool StartsFromSpecialContent() const { return mStart
.IsSpecialContent(); }
929 bool StartsFromBRElement() const { return mStart
.IsBRElement(); }
930 bool StartsFromVisibleBRElement() const {
931 return StartsFromBRElement() &&
932 HTMLEditUtils::IsVisibleBRElement(*GetStartReasonContent());
934 bool StartsFromInvisibleBRElement() const {
935 return StartsFromBRElement() &&
936 HTMLEditUtils::IsInvisibleBRElement(*GetStartReasonContent());
938 bool StartsFromPreformattedLineBreak() const {
939 return mStart
.IsPreformattedLineBreak();
941 bool StartsFromCurrentBlockBoundary() const {
942 return mStart
.IsCurrentBlockBoundary();
944 bool StartsFromOtherBlockElement() const {
945 return mStart
.IsOtherBlockBoundary();
947 bool StartsFromBlockBoundary() const { return mStart
.IsBlockBoundary(); }
948 bool StartsFromHardLineBreak() const { return mStart
.IsHardLineBreak(); }
949 bool EndsByNonCollapsibleCharacters() const {
950 return mEnd
.IsNonCollapsibleCharacters();
952 bool EndsBySpecialContent() const { return mEnd
.IsSpecialContent(); }
953 bool EndsByBRElement() const { return mEnd
.IsBRElement(); }
954 bool EndsByVisibleBRElement() const {
955 return EndsByBRElement() &&
956 HTMLEditUtils::IsVisibleBRElement(*GetEndReasonContent());
958 bool EndsByInvisibleBRElement() const {
959 return EndsByBRElement() &&
960 HTMLEditUtils::IsInvisibleBRElement(*GetEndReasonContent());
962 bool EndsByPreformattedLineBreak() const {
963 return mEnd
.IsPreformattedLineBreak();
965 bool EndsByInvisiblePreformattedLineBreak() const {
966 return mEnd
.IsPreformattedLineBreak() &&
967 HTMLEditUtils::IsInvisiblePreformattedNewLine(mEnd
.PointRef());
969 bool EndsByCurrentBlockBoundary() const {
970 return mEnd
.IsCurrentBlockBoundary();
972 bool EndsByOtherBlockElement() const { return mEnd
.IsOtherBlockBoundary(); }
973 bool EndsByBlockBoundary() const { return mEnd
.IsBlockBoundary(); }
975 WSType
StartRawReason() const { return mStart
.RawReason(); }
976 WSType
EndRawReason() const { return mEnd
.RawReason(); }
978 MOZ_NEVER_INLINE_DEBUG Element
* StartReasonOtherBlockElementPtr() const {
979 return mStart
.OtherBlockElementPtr();
981 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* StartReasonBRElementPtr() const {
982 return mStart
.BRElementPtr();
984 MOZ_NEVER_INLINE_DEBUG Element
* EndReasonOtherBlockElementPtr() const {
985 return mEnd
.OtherBlockElementPtr();
987 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* EndReasonBRElementPtr() const {
988 return mEnd
.BRElementPtr();
991 const EditorDOMPoint
& StartRef() const { return mStart
.PointRef(); }
992 const EditorDOMPoint
& EndRef() const { return mEnd
.PointRef(); }
994 const EditorDOMPoint
& ScanStartRef() const { return mScanStartPoint
; }
996 bool FoundNoBreakingWhiteSpaces() const { return mNBSPData
.FoundNBSP(); }
997 const EditorDOMPointInText
& FirstNBSPPointRef() const {
998 return mNBSPData
.FirstPointRef();
1000 const EditorDOMPointInText
& LastNBSPPointRef() const {
1001 return mNBSPData
.LastPointRef();
1004 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
1006 EditorDOMPointType
GetInclusiveNextEditableCharPoint(
1007 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const;
1008 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
1010 EditorDOMPointType
GetPreviousEditableCharPoint(
1011 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const;
1013 template <typename EditorDOMPointType
= EditorDOMPointInText
>
1014 EditorDOMPointType
GetEndOfCollapsibleASCIIWhiteSpaces(
1015 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
1016 nsIEditor::EDirection aDirectionToDelete
) const;
1017 template <typename EditorDOMPointType
= EditorDOMPointInText
>
1018 EditorDOMPointType
GetFirstASCIIWhiteSpacePointCollapsedTo(
1019 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
1020 nsIEditor::EDirection aDirectionToDelete
) const;
1023 * GetNonCollapsedRangeInTexts() returns non-empty range in texts which
1024 * is the largest range in aRange if there is some text nodes.
1026 EditorDOMRangeInTexts
GetNonCollapsedRangeInTexts(
1027 const EditorDOMRange
& aRange
) const;
1030 * InvisibleLeadingWhiteSpaceRangeRef() retruns reference to two DOM points,
1031 * start of the line and first visible point or end of the hard line. When
1032 * this returns non-positioned range or positioned but collapsed range,
1033 * there is no invisible leading white-spaces.
1034 * Note that if there are only invisible white-spaces in a hard line,
1035 * this returns all of the white-spaces.
1037 const EditorDOMRange
& InvisibleLeadingWhiteSpaceRangeRef() const;
1040 * InvisibleTrailingWhiteSpaceRangeRef() returns reference to two DOM
1041 * points, first invisible white-space and end of the hard line. When this
1042 * returns non-positioned range or positioned but collapsed range,
1043 * there is no invisible trailing white-spaces.
1044 * Note that if there are only invisible white-spaces in a hard line,
1045 * this returns all of the white-spaces.
1047 const EditorDOMRange
& InvisibleTrailingWhiteSpaceRangeRef() const;
1050 * GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt() returns new
1051 * invisible leading white-space range which should be removed if
1052 * splitting invisible white-space sequence at aPointToSplit creates
1053 * new invisible leading white-spaces in the new line.
1054 * Note that the result may be collapsed range if the point is around
1055 * invisible white-spaces.
1057 template <typename EditorDOMPointType
>
1058 EditorDOMRange
GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(
1059 const EditorDOMPointType
& aPointToSplit
) const {
1060 // If there are invisible trailing white-spaces and some or all of them
1061 // become invisible leading white-spaces in the new line, although we
1062 // don't need to delete them, but for aesthetically and backward
1063 // compatibility, we should remove them.
1064 const EditorDOMRange
& trailingWhiteSpaceRange
=
1065 InvisibleTrailingWhiteSpaceRangeRef();
1066 // XXX Why don't we check leading white-spaces too?
1067 if (!trailingWhiteSpaceRange
.IsPositioned()) {
1068 return trailingWhiteSpaceRange
;
1070 // If the point is before the trailing white-spaces, the new line won't
1071 // start with leading white-spaces.
1072 if (aPointToSplit
.IsBefore(trailingWhiteSpaceRange
.StartRef())) {
1073 return EditorDOMRange();
1075 // If the point is in the trailing white-spaces, the new line may
1076 // start with some leading white-spaces. Returning collapsed range
1077 // is intentional because the caller may want to know whether the
1078 // point is in trailing white-spaces or not.
1079 if (aPointToSplit
.EqualsOrIsBefore(trailingWhiteSpaceRange
.EndRef())) {
1080 return EditorDOMRange(trailingWhiteSpaceRange
.StartRef(),
1083 // Otherwise, if the point is after the trailing white-spaces, it may
1084 // be just outside of the text node. E.g., end of parent element.
1085 // This is possible case but the validation cost is not worthwhile
1086 // due to the runtime cost in the worst case. Therefore, we should just
1087 // return collapsed range at the end of trailing white-spaces. Then,
1088 // callers can know the point is immediately after the trailing
1090 return EditorDOMRange(trailingWhiteSpaceRange
.EndRef());
1094 * GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt() returns new
1095 * invisible trailing white-space range which should be removed if
1096 * splitting invisible white-space sequence at aPointToSplit creates
1097 * new invisible trailing white-spaces in the new line.
1098 * Note that the result may be collapsed range if the point is around
1099 * invisible white-spaces.
1101 template <typename EditorDOMPointType
>
1102 EditorDOMRange
GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(
1103 const EditorDOMPointType
& aPointToSplit
) const {
1104 // If there are invisible leading white-spaces and some or all of them
1105 // become end of current line, they will become visible. Therefore, we
1106 // need to delete the invisible leading white-spaces before insertion
1108 const EditorDOMRange
& leadingWhiteSpaceRange
=
1109 InvisibleLeadingWhiteSpaceRangeRef();
1110 if (!leadingWhiteSpaceRange
.IsPositioned()) {
1111 return leadingWhiteSpaceRange
;
1113 // If the point equals or is after the leading white-spaces, the line
1114 // will end without trailing white-spaces.
1115 if (leadingWhiteSpaceRange
.EndRef().IsBefore(aPointToSplit
)) {
1116 return EditorDOMRange();
1118 // If the point is in the leading white-spaces, the line may
1119 // end with some trailing white-spaces. Returning collapsed range
1120 // is intentional because the caller may want to know whether the
1121 // point is in leading white-spaces or not.
1122 if (leadingWhiteSpaceRange
.StartRef().EqualsOrIsBefore(aPointToSplit
)) {
1123 return EditorDOMRange(aPointToSplit
, leadingWhiteSpaceRange
.EndRef());
1125 // Otherwise, if the point is before the leading white-spaces, it may
1126 // be just outside of the text node. E.g., start of parent element.
1127 // This is possible case but the validation cost is not worthwhile
1128 // due to the runtime cost in the worst case. Therefore, we should
1129 // just return collapsed range at start of the leading white-spaces.
1130 // Then, callers can know the point is immediately before the leading
1132 return EditorDOMRange(leadingWhiteSpaceRange
.StartRef());
1136 * FollowingContentMayBecomeFirstVisibleContent() returns true if some
1137 * content may be first visible content after removing content after aPoint.
1138 * Note that it's completely broken what this does. Don't use this method
1141 template <typename EditorDOMPointType
>
1142 bool FollowingContentMayBecomeFirstVisibleContent(
1143 const EditorDOMPointType
& aPoint
) const {
1144 MOZ_ASSERT(aPoint
.IsSetAndValid());
1145 if (!mStart
.IsHardLineBreak()) {
1148 // If the point is before start of text fragment, that means that the
1149 // point may be at the block boundary or inline element boundary.
1150 if (aPoint
.EqualsOrIsBefore(mStart
.PointRef())) {
1153 // VisibleWhiteSpacesData is marked as start of line only when it
1154 // represents leading white-spaces.
1155 const EditorDOMRange
& leadingWhiteSpaceRange
=
1156 InvisibleLeadingWhiteSpaceRangeRef();
1157 if (!leadingWhiteSpaceRange
.StartRef().IsSet()) {
1160 if (aPoint
.EqualsOrIsBefore(leadingWhiteSpaceRange
.StartRef())) {
1163 if (!leadingWhiteSpaceRange
.EndRef().IsSet()) {
1166 return aPoint
.EqualsOrIsBefore(leadingWhiteSpaceRange
.EndRef());
1170 * PrecedingContentMayBecomeInvisible() returns true if end of preceding
1171 * content is collapsed (when ends with an ASCII white-space).
1172 * Note that it's completely broken what this does. Don't use this method
1175 template <typename EditorDOMPointType
>
1176 bool PrecedingContentMayBecomeInvisible(
1177 const EditorDOMPointType
& aPoint
) const {
1178 MOZ_ASSERT(aPoint
.IsSetAndValid());
1179 // If this fragment is ends by block boundary, always the caller needs
1180 // additional check.
1181 if (mEnd
.IsBlockBoundary()) {
1185 // If the point is in visible white-spaces and ends with an ASCII
1186 // white-space, it may be collapsed even if it won't be end of line.
1187 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
1188 VisibleWhiteSpacesDataRef();
1189 if (!visibleWhiteSpaces
.IsInitialized()) {
1192 // XXX Odd case, but keep traditional behavior of `FindNearestRun()`.
1193 if (!visibleWhiteSpaces
.StartRef().IsSet()) {
1196 if (!visibleWhiteSpaces
.StartRef().EqualsOrIsBefore(aPoint
)) {
1199 // XXX Odd case, but keep traditional behavior of `FindNearestRun()`.
1200 if (visibleWhiteSpaces
.EndsByTrailingWhiteSpaces()) {
1203 // XXX Must be a bug. This claims that the caller needs additional
1204 // check even when there is no white-spaces.
1205 if (visibleWhiteSpaces
.StartRef() == visibleWhiteSpaces
.EndRef()) {
1208 return aPoint
.IsBefore(visibleWhiteSpaces
.EndRef());
1212 * GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace() may return an
1213 * NBSP point which should be replaced with an ASCII white-space when we're
1214 * inserting text into aPointToInsert. Note that this is a helper method for
1215 * the traditional white-space normalizer. Don't use this with the new
1216 * white-space normalizer.
1217 * Must be called only when VisibleWhiteSpacesDataRef() returns initialized
1218 * instance and previous character of aPointToInsert is in the range.
1220 EditorDOMPointInText
GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
1221 const EditorDOMPoint
& aPointToInsert
) const;
1224 * GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace() may return
1225 * an NBSP point which should be replaced with an ASCII white-space when
1226 * the caller inserts text into aPointToInsert.
1227 * Note that this is a helper method for the traditional white-space
1228 * normalizer. Don't use this with the new white-space normalizer.
1229 * Must be called only when VisibleWhiteSpacesDataRef() returns initialized
1230 * instance, and inclusive next char of aPointToInsert is in the range.
1232 EditorDOMPointInText
1233 GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
1234 const EditorDOMPoint
& aPointToInsert
) const;
1237 * GetReplaceRangeDataAtEndOfDeletionRange() and
1238 * GetReplaceRangeDataAtStartOfDeletionRange() return delete range if
1239 * end or start of deleting range splits invisible trailing/leading
1240 * white-spaces and it may become visible, or return replace range if
1241 * end or start of deleting range splits visible white-spaces and it
1242 * causes some ASCII white-spaces become invisible unless replacing
1245 ReplaceRangeData
GetReplaceRangeDataAtEndOfDeletionRange(
1246 const TextFragmentData
& aTextFragmentDataAtStartToDelete
) const;
1247 ReplaceRangeData
GetReplaceRangeDataAtStartOfDeletionRange(
1248 const TextFragmentData
& aTextFragmentDataAtEndToDelete
) const;
1251 * VisibleWhiteSpacesDataRef() returns reference to visible white-spaces
1252 * data. That is zero or more white-spaces which are visible.
1253 * Note that when there is no visible content, it's not initialized.
1254 * Otherwise, even if there is no white-spaces, it's initialized and
1255 * the range is collapsed in such case.
1257 const VisibleWhiteSpacesData
& VisibleWhiteSpacesDataRef() const;
1260 EditorDOMPoint mScanStartPoint
;
1261 BoundaryData mStart
;
1263 NoBreakingSpaceData mNBSPData
;
1264 RefPtr
<const Element
> mEditingHost
;
1265 mutable Maybe
<EditorDOMRange
> mLeadingWhiteSpaceRange
;
1266 mutable Maybe
<EditorDOMRange
> mTrailingWhiteSpaceRange
;
1267 mutable Maybe
<VisibleWhiteSpacesData
> mVisibleWhiteSpacesData
;
1270 const TextFragmentData
& TextFragmentDataAtStartRef() const {
1271 return mTextFragmentDataAtStart
;
1274 // The node passed to our constructor.
1275 EditorDOMPoint mScanStartPoint
;
1276 // Together, the above represent the point at which we are building up ws
1279 // The editing host when the instance is created.
1280 RefPtr
<Element
> mEditingHost
;
1284 * ComputeRangeInTextNodesContainingInvisibleWhiteSpaces() returns range
1285 * containing invisible white-spaces if deleting between aStart and aEnd
1286 * causes them become visible.
1288 * @param aStart TextFragmentData at start of deleting range.
1289 * This must be initialized with DOM point in a text node.
1290 * @param aEnd TextFragmentData at end of deleting range.
1291 * This must be initialized with DOM point in a text node.
1293 static EditorDOMRangeInTexts
1294 ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
1295 const TextFragmentData
& aStart
, const TextFragmentData
& aEnd
);
1297 TextFragmentData mTextFragmentDataAtStart
;
1299 friend class WhiteSpaceVisibilityKeeper
;
1303 * WhiteSpaceVisibilityKeeper class helps `HTMLEditor` modifying the DOM tree
1304 * with keeps white-space sequence visibility automatically. E.g., invisible
1305 * leading/trailing white-spaces becomes visible, this class members delete
1306 * them. E.g., when splitting visible-white-space sequence, this class may
1307 * replace ASCII white-spaces at split edges with NBSPs.
1309 class WhiteSpaceVisibilityKeeper final
{
1311 using AutoTransactionsConserveSelection
=
1312 EditorBase::AutoTransactionsConserveSelection
;
1313 using EditorType
= EditorBase::EditorType
;
1314 using PointPosition
= WSRunScanner::PointPosition
;
1315 using TextFragmentData
= WSRunScanner::TextFragmentData
;
1316 using VisibleWhiteSpacesData
= WSRunScanner::VisibleWhiteSpacesData
;
1319 WhiteSpaceVisibilityKeeper() = delete;
1320 explicit WhiteSpaceVisibilityKeeper(
1321 const WhiteSpaceVisibilityKeeper
& aOther
) = delete;
1322 WhiteSpaceVisibilityKeeper(WhiteSpaceVisibilityKeeper
&& aOther
) = delete;
1325 * DeleteInvisibleASCIIWhiteSpaces() removes invisible leading white-spaces
1326 * and trailing white-spaces if there are around aPoint.
1328 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
1329 DeleteInvisibleASCIIWhiteSpaces(HTMLEditor
& aHTMLEditor
,
1330 const EditorDOMPoint
& aPoint
);
1332 // PrepareToDeleteRange fixes up ws before aStartPoint and after aEndPoint in
1333 // preperation for content in that range to be deleted. Note that the nodes
1334 // and offsets are adjusted in response to any dom changes we make while
1336 // example of fixup: trailingws before aStartPoint needs to be removed.
1337 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
1338 PrepareToDeleteRangeAndTrackPoints(HTMLEditor
& aHTMLEditor
,
1339 EditorDOMPoint
* aStartPoint
,
1340 EditorDOMPoint
* aEndPoint
) {
1341 MOZ_ASSERT(aStartPoint
->IsSetAndValid());
1342 MOZ_ASSERT(aEndPoint
->IsSetAndValid());
1343 AutoTrackDOMPoint
trackerStart(aHTMLEditor
.RangeUpdaterRef(), aStartPoint
);
1344 AutoTrackDOMPoint
trackerEnd(aHTMLEditor
.RangeUpdaterRef(), aEndPoint
);
1345 return WhiteSpaceVisibilityKeeper::PrepareToDeleteRange(
1346 aHTMLEditor
, EditorDOMRange(*aStartPoint
, *aEndPoint
));
1348 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
PrepareToDeleteRange(
1349 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aStartPoint
,
1350 const EditorDOMPoint
& aEndPoint
) {
1351 MOZ_ASSERT(aStartPoint
.IsSetAndValid());
1352 MOZ_ASSERT(aEndPoint
.IsSetAndValid());
1353 return WhiteSpaceVisibilityKeeper::PrepareToDeleteRange(
1354 aHTMLEditor
, EditorDOMRange(aStartPoint
, aEndPoint
));
1356 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
PrepareToDeleteRange(
1357 HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRange
) {
1358 MOZ_ASSERT(aRange
.IsPositionedAndValid());
1359 nsresult rv
= WhiteSpaceVisibilityKeeper::
1360 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(aHTMLEditor
,
1362 NS_WARNING_ASSERTION(
1364 "WhiteSpaceVisibilityKeeper::"
1365 "MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() failed");
1370 * PrepareToSplitBlockElement() makes sure that the invisible white-spaces
1371 * not to become visible and returns splittable point.
1373 * @param aHTMLEditor The HTML editor.
1374 * @param aPointToSplit The splitting point in aSplittingBlockElement.
1375 * @param aSplittingBlockElement A block element which will be split.
1377 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<EditorDOMPoint
, nsresult
>
1378 PrepareToSplitBlockElement(HTMLEditor
& aHTMLEditor
,
1379 const EditorDOMPoint
& aPointToSplit
,
1380 const Element
& aSplittingBlockElement
);
1383 * MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement() merges
1384 * first line in aRightBlockElement into end of aLeftBlockElement which
1385 * is a descendant of aRightBlockElement.
1387 * @param aHTMLEditor The HTML editor.
1388 * @param aLeftBlockElement The content will be merged into end of
1390 * @param aRightBlockElement The first line in this element will be
1391 * moved to aLeftBlockElement.
1392 * @param aAtRightBlockChild At a child of aRightBlockElement and inclusive
1393 * ancestor of aLeftBlockElement.
1394 * @param aListElementTagName Set some if aRightBlockElement is a list
1395 * element and it'll be merged with another
1398 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static EditActionResult
1399 MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
1400 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
1401 Element
& aRightBlockElement
, const EditorDOMPoint
& aAtRightBlockChild
,
1402 const Maybe
<nsAtom
*>& aListElementTagName
,
1403 const HTMLBRElement
* aPrecedingInvisibleBRElement
);
1406 * MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement() merges
1407 * first line in aRightBlockElement into end of aLeftBlockElement which
1408 * is an ancestor of aRightBlockElement, then, removes aRightBlockElement
1409 * if it becomes empty.
1411 * @param aHTMLEditor The HTML editor.
1412 * @param aLeftBlockElement The content will be merged into end of
1414 * @param aRightBlockElement The first line in this element will be
1415 * moved to aLeftBlockElement and maybe
1416 * removed when this becomes empty.
1417 * @param aAtLeftBlockChild At a child of aLeftBlockElement and inclusive
1418 * ancestor of aRightBlockElement.
1419 * @param aLeftContentInBlock The content whose inclusive ancestor is
1420 * aLeftBlockElement.
1421 * @param aListElementTagName Set some if aRightBlockElement is a list
1422 * element and it'll be merged with another
1425 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static EditActionResult
1426 MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
1427 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
1428 Element
& aRightBlockElement
, const EditorDOMPoint
& aAtLeftBlockChild
,
1429 nsIContent
& aLeftContentInBlock
,
1430 const Maybe
<nsAtom
*>& aListElementTagName
,
1431 const HTMLBRElement
* aPrecedingInvisibleBRElement
);
1434 * MergeFirstLineOfRightBlockElementIntoLeftBlockElement() merges first
1435 * line in aRightBlockElement into end of aLeftBlockElement and removes
1436 * aRightBlockElement when it has only one line.
1438 * @param aHTMLEditor The HTML editor.
1439 * @param aLeftBlockElement The content will be merged into end of
1441 * @param aRightBlockElement The first line in this element will be
1442 * moved to aLeftBlockElement and maybe
1443 * removed when this becomes empty.
1444 * @param aListElementTagName Set some if aRightBlockElement is a list
1445 * element and its type needs to be changed.
1447 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static EditActionResult
1448 MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
1449 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
1450 Element
& aRightBlockElement
, const Maybe
<nsAtom
*>& aListElementTagName
,
1451 const HTMLBRElement
* aPrecedingInvisibleBRElement
);
1454 * InsertBRElement() inserts a <br> node at (before) aPointToInsert and delete
1455 * unnecessary white-spaces around there and/or replaces white-spaces with
1456 * non-breaking spaces. Note that if the point is in a text node, the
1457 * text node will be split and insert new <br> node between the left node
1458 * and the right node.
1460 * @param aPointToInsert The point to insert new <br> element. Note that
1461 * it'll be inserted before this point. I.e., the
1462 * point will be the point of new <br>.
1463 * @return If succeeded, returns the new <br> element and
1464 * point to put caret.
1466 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static CreateElementResult
InsertBRElement(
1467 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToInsert
,
1468 Element
& aEditingHost
);
1471 * InsertText() inserts aStringToInsert to aPointToInsert and makes any needed
1472 * adjustments to white-spaces around the insertion point.
1474 * @param aStringToInsert The string to insert.
1475 * @param aRangeToBeReplaced The range to be deleted.
1476 * @return If succeeded, returns the point after inserted
1477 * aStringToInsert. So, when this method actually
1478 * inserts string, returns a point in the text
1479 * node. Otherwise, may return mScanStartPoint.
1481 template <typename EditorDOMPointType
>
1482 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<EditorDOMPoint
, nsresult
>
1483 InsertText(HTMLEditor
& aHTMLEditor
, const nsAString
& aStringToInsert
,
1484 const EditorDOMPointType
& aPointToInsert
) {
1485 return WhiteSpaceVisibilityKeeper::ReplaceText(
1486 aHTMLEditor
, aStringToInsert
, EditorDOMRange(aPointToInsert
));
1490 * ReplaceText() repaces aRangeToReplace with aStringToInsert and makes any
1491 * needed adjustments to white-spaces around both start of the range and
1494 * @param aStringToInsert The string to insert.
1495 * @param aRangeToBeReplaced The range to be deleted.
1496 * @return If succeeded, returns the point after inserted
1497 * aStringToInsert. So, when this method actually
1498 * inserts string, returns a point in the text
1499 * node. Otherwise, may return mScanStartPoint.
1501 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<EditorDOMPoint
, nsresult
>
1502 ReplaceText(HTMLEditor
& aHTMLEditor
, const nsAString
& aStringToInsert
,
1503 const EditorDOMRange
& aRangeToBeReplaced
);
1506 * DeletePreviousWhiteSpace() deletes previous white-space of aPoint.
1507 * This automatically keeps visibility of white-spaces around aPoint.
1508 * E.g., may remove invisible leading white-spaces.
1510 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
DeletePreviousWhiteSpace(
1511 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPoint
);
1514 * DeleteInclusiveNextWhiteSpace() delete inclusive next white-space of
1515 * aPoint. This automatically keeps visiblity of white-spaces around aPoint.
1516 * E.g., may remove invisible trailing white-spaces.
1518 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
1519 DeleteInclusiveNextWhiteSpace(HTMLEditor
& aHTMLEditor
,
1520 const EditorDOMPoint
& aPoint
);
1523 * DeleteContentNodeAndJoinTextNodesAroundIt() deletes aContentToDelete and
1524 * may remove/replace white-spaces around it. Then, if deleting content makes
1525 * 2 text nodes around it are adjacent siblings, this joins them and put
1526 * selection at the joined point.
1528 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
1529 DeleteContentNodeAndJoinTextNodesAroundIt(HTMLEditor
& aHTMLEditor
,
1530 nsIContent
& aContentToDelete
,
1531 const EditorDOMPoint
& aCaretPoint
);
1534 * NormalizeVisibleWhiteSpacesAt() tries to normalize visible white-space
1535 * sequence around aPoint.
1537 template <typename EditorDOMPointType
>
1538 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
1539 NormalizeVisibleWhiteSpacesAt(HTMLEditor
& aHTMLEditor
,
1540 const EditorDOMPointType
& aPoint
);
1544 * MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() may delete
1545 * invisible white-spaces for keeping make them invisible and/or may replace
1546 * ASCII white-spaces with NBSPs for making visible white-spaces to keep
1549 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
1550 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
1551 HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRangeToDelete
);
1554 * MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit() replaces ASCII white-
1555 * spaces which becomes invisible after split with NBSPs.
1557 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
1558 MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
1559 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToSplit
);
1562 * ReplaceTextAndRemoveEmptyTextNodes() replaces the range between
1563 * aRangeToReplace with aReplaceString simply. Additionally, removes
1564 * empty text nodes in the range.
1566 * @param aRangeToReplace Range to replace text.
1567 * @param aReplaceString The new string. Empty string is allowed.
1569 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
1570 ReplaceTextAndRemoveEmptyTextNodes(
1571 HTMLEditor
& aHTMLEditor
, const EditorDOMRangeInTexts
& aRangeToReplace
,
1572 const nsAString
& aReplaceString
);
1575 } // namespace mozilla
1577 #endif // #ifndef WSRunObject_h