1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 #include "EditAction.h"
10 #include "EditorBase.h"
11 #include "EditorForwards.h"
12 #include "EditorDOMPoint.h" // for EditorDOMPoint
13 #include "EditorUtils.h" // for CaretPoint
14 #include "HTMLEditHelpers.h"
15 #include "HTMLEditor.h"
16 #include "HTMLEditUtils.h"
18 #include "mozilla/Assertions.h"
19 #include "mozilla/Maybe.h"
20 #include "mozilla/Result.h"
21 #include "mozilla/dom/Element.h"
22 #include "mozilla/dom/HTMLBRElement.h"
23 #include "mozilla/dom/Text.h"
25 #include "nsIContent.h"
32 * WSScanResult is result of ScanNextVisibleNodeOrBlockBoundaryFrom(),
33 * ScanPreviousVisibleNodeOrBlockBoundaryFrom(), and their static wrapper
34 * methods. This will have information of found visible content (and its
35 * position) or reached block element or topmost editable content at the
38 class MOZ_STACK_CLASS WSScanResult final
{
40 enum class WSType
: uint8_t {
42 // Could be the DOM tree is broken as like crash tests.
44 // The scanner cannot work in uncomposed tree, but tried to scan in it.
46 // The run is maybe collapsible white-spaces at start of a hard line.
48 // The run is maybe collapsible white-spaces at end of a hard line.
50 // Collapsible, but visible white-spaces.
51 CollapsibleWhiteSpaces
,
52 // Visible characters except collapsible white-spaces.
53 NonCollapsibleCharacters
,
54 // Special content such as `<img>`, etc.
58 // A linefeed which is preformatted.
59 PreformattedLineBreak
,
60 // Other block's boundary (child block of current block, maybe).
62 // Current block's boundary.
64 // Inline editing host boundary.
65 InlineEditingHostBoundary
,
68 friend std::ostream
& operator<<(std::ostream
& aStream
, const WSType
& aType
) {
70 case WSType::NotInitialized
:
71 return aStream
<< "WSType::NotInitialized";
72 case WSType::UnexpectedError
:
73 return aStream
<< "WSType::UnexpectedError";
74 case WSType::InUncomposedDoc
:
75 return aStream
<< "WSType::InUncomposedDoc";
76 case WSType::LeadingWhiteSpaces
:
77 return aStream
<< "WSType::LeadingWhiteSpaces";
78 case WSType::TrailingWhiteSpaces
:
79 return aStream
<< "WSType::TrailingWhiteSpaces";
80 case WSType::CollapsibleWhiteSpaces
:
81 return aStream
<< "WSType::CollapsibleWhiteSpaces";
82 case WSType::NonCollapsibleCharacters
:
83 return aStream
<< "WSType::NonCollapsibleCharacters";
84 case WSType::SpecialContent
:
85 return aStream
<< "WSType::SpecialContent";
86 case WSType::BRElement
:
87 return aStream
<< "WSType::BRElement";
88 case WSType::PreformattedLineBreak
:
89 return aStream
<< "WSType::PreformattedLineBreak";
90 case WSType::OtherBlockBoundary
:
91 return aStream
<< "WSType::OtherBlockBoundary";
92 case WSType::CurrentBlockBoundary
:
93 return aStream
<< "WSType::CurrentBlockBoundary";
94 case WSType::InlineEditingHostBoundary
:
95 return aStream
<< "WSType::InlineEditingHostBoundary";
97 return aStream
<< "<Illegal value>";
100 friend class WSRunScanner
; // Because of WSType.
102 explicit WSScanResult(WSType aReason
) : mReason(aReason
) {
103 MOZ_ASSERT(mReason
== WSType::UnexpectedError
||
104 mReason
== WSType::NotInitialized
);
108 WSScanResult() = delete;
109 enum class ScanDirection
: bool { Backward
, Forward
};
110 MOZ_NEVER_INLINE_DEBUG
WSScanResult(ScanDirection aScanDirection
,
111 nsIContent
& aContent
, WSType aReason
,
112 BlockInlineCheck aBlockInlineCheck
)
113 : mContent(&aContent
), mReason(aReason
), mDirection(aScanDirection
) {
114 MOZ_ASSERT(aReason
!= WSType::CollapsibleWhiteSpaces
&&
115 aReason
!= WSType::NonCollapsibleCharacters
&&
116 aReason
!= WSType::PreformattedLineBreak
);
117 AssertIfInvalidData(aBlockInlineCheck
);
119 MOZ_NEVER_INLINE_DEBUG
WSScanResult(ScanDirection aScanDirection
,
120 const EditorDOMPoint
& aPoint
,
122 BlockInlineCheck aBlockInlineCheck
)
123 : mContent(aPoint
.GetContainerAs
<nsIContent
>()),
124 mOffset(Some(aPoint
.Offset())),
126 mDirection(aScanDirection
) {
127 AssertIfInvalidData(aBlockInlineCheck
);
130 static WSScanResult
Error() { return WSScanResult(WSType::UnexpectedError
); }
132 MOZ_NEVER_INLINE_DEBUG
void AssertIfInvalidData(
133 BlockInlineCheck aBlockInlineCheck
) const {
135 MOZ_ASSERT(mReason
== WSType::UnexpectedError
||
136 mReason
== WSType::InUncomposedDoc
||
137 mReason
== WSType::NonCollapsibleCharacters
||
138 mReason
== WSType::CollapsibleWhiteSpaces
||
139 mReason
== WSType::BRElement
||
140 mReason
== WSType::PreformattedLineBreak
||
141 mReason
== WSType::SpecialContent
||
142 mReason
== WSType::CurrentBlockBoundary
||
143 mReason
== WSType::OtherBlockBoundary
||
144 mReason
== WSType::InlineEditingHostBoundary
);
145 MOZ_ASSERT_IF(mReason
== WSType::UnexpectedError
, !mContent
);
146 MOZ_ASSERT_IF(mReason
!= WSType::UnexpectedError
, mContent
);
147 MOZ_ASSERT_IF(mReason
== WSType::InUncomposedDoc
,
148 !mContent
->IsInComposedDoc());
149 MOZ_ASSERT_IF(mContent
&& !mContent
->IsInComposedDoc(),
150 mReason
== WSType::InUncomposedDoc
);
151 MOZ_ASSERT_IF(mReason
== WSType::NonCollapsibleCharacters
||
152 mReason
== WSType::CollapsibleWhiteSpaces
||
153 mReason
== WSType::PreformattedLineBreak
,
155 MOZ_ASSERT_IF(mReason
== WSType::NonCollapsibleCharacters
||
156 mReason
== WSType::CollapsibleWhiteSpaces
||
157 mReason
== WSType::PreformattedLineBreak
,
159 MOZ_ASSERT_IF(mReason
== WSType::NonCollapsibleCharacters
||
160 mReason
== WSType::CollapsibleWhiteSpaces
||
161 mReason
== WSType::PreformattedLineBreak
,
162 mContent
->AsText()->TextDataLength() > 0);
163 MOZ_ASSERT_IF(mDirection
== ScanDirection::Backward
&&
164 (mReason
== WSType::NonCollapsibleCharacters
||
165 mReason
== WSType::CollapsibleWhiteSpaces
||
166 mReason
== WSType::PreformattedLineBreak
),
168 MOZ_ASSERT_IF(mDirection
== ScanDirection::Forward
&&
169 (mReason
== WSType::NonCollapsibleCharacters
||
170 mReason
== WSType::CollapsibleWhiteSpaces
||
171 mReason
== WSType::PreformattedLineBreak
),
172 *mOffset
< mContent
->AsText()->TextDataLength());
173 MOZ_ASSERT_IF(mReason
== WSType::BRElement
,
174 mContent
->IsHTMLElement(nsGkAtoms::br
));
175 MOZ_ASSERT_IF(mReason
== WSType::PreformattedLineBreak
,
176 EditorUtils::IsNewLinePreformatted(*mContent
));
178 mReason
== WSType::SpecialContent
,
179 (mContent
->IsText() && !mContent
->IsEditable()) ||
180 (!mContent
->IsHTMLElement(nsGkAtoms::br
) &&
181 !HTMLEditUtils::IsBlockElement(*mContent
, aBlockInlineCheck
)));
182 MOZ_ASSERT_IF(mReason
== WSType::OtherBlockBoundary
,
183 HTMLEditUtils::IsBlockElement(*mContent
, aBlockInlineCheck
));
184 MOZ_ASSERT_IF(mReason
== WSType::CurrentBlockBoundary
,
185 mContent
->IsElement());
186 MOZ_ASSERT_IF(mReason
== WSType::CurrentBlockBoundary
,
187 mContent
->IsEditable());
188 MOZ_ASSERT_IF(mReason
== WSType::CurrentBlockBoundary
,
189 HTMLEditUtils::IsBlockElement(*mContent
, aBlockInlineCheck
));
190 MOZ_ASSERT_IF(mReason
== WSType::InlineEditingHostBoundary
,
191 mContent
->IsElement());
192 MOZ_ASSERT_IF(mReason
== WSType::InlineEditingHostBoundary
,
193 mContent
->IsEditable());
194 MOZ_ASSERT_IF(mReason
== WSType::InlineEditingHostBoundary
,
195 !HTMLEditUtils::IsBlockElement(*mContent
, aBlockInlineCheck
));
196 MOZ_ASSERT_IF(mReason
== WSType::InlineEditingHostBoundary
,
197 !mContent
->GetParentElement() ||
198 !mContent
->GetParentElement()->IsEditable());
199 #endif // #ifdef DEBUG
202 bool Failed() const {
203 return mReason
== WSType::NotInitialized
||
204 mReason
== WSType::UnexpectedError
;
208 * GetContent() returns found visible and editable content/element.
209 * See MOZ_ASSERT_IF()s in AssertIfInvalidData() for the detail.
211 nsIContent
* GetContent() const { return mContent
; }
213 [[nodiscard
]] bool ContentIsElement() const {
214 return mContent
&& mContent
->IsElement();
217 [[nodiscard
]] bool ContentIsText() const {
218 return mContent
&& mContent
->IsText();
222 * The following accessors makes it easier to understand each callers.
224 MOZ_NEVER_INLINE_DEBUG Element
* ElementPtr() const {
225 MOZ_DIAGNOSTIC_ASSERT(mContent
->IsElement());
226 return mContent
->AsElement();
228 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* BRElementPtr() const {
229 MOZ_DIAGNOSTIC_ASSERT(mContent
->IsHTMLElement(nsGkAtoms::br
));
230 return static_cast<HTMLBRElement
*>(mContent
.get());
232 MOZ_NEVER_INLINE_DEBUG Text
* TextPtr() const {
233 MOZ_DIAGNOSTIC_ASSERT(mContent
->IsText());
234 return mContent
->AsText();
238 * Returns true if found or reached content is editable.
240 bool IsContentEditable() const { return mContent
&& mContent
->IsEditable(); }
243 * Offset_Deprecated() returns meaningful value only when
244 * InVisibleOrCollapsibleCharacters() returns true or the scanner reached to
245 * start or end of its scanning range and that is same as start or end
246 * container which are specified when the scanner is initialized. If it's
247 * result of scanning backward, this offset means the point of the found
248 * point. Otherwise, i.e., scanning forward, this offset means next point
249 * of the found point. E.g., if it reaches a collapsible white-space, this
250 * offset is at the first non-collapsible character after it.
252 MOZ_NEVER_INLINE_DEBUG
uint32_t Offset_Deprecated() const {
253 NS_ASSERTION(mOffset
.isSome(), "Retrieved non-meaningful offset");
254 return mOffset
.valueOr(0);
258 * Point_Deprecated() returns the position in found visible node or reached
259 * block boundary. So, this returns meaningful point only when
260 * Offset_Deprecated() returns meaningful value.
262 template <typename EditorDOMPointType
>
263 EditorDOMPointType
Point_Deprecated() const {
264 NS_ASSERTION(mOffset
.isSome(), "Retrieved non-meaningful point");
265 return EditorDOMPointType(mContent
, mOffset
.valueOr(0));
269 * PointAtReachedContent() returns the position of found visible content or
270 * reached block element.
272 template <typename EditorDOMPointType
>
273 EditorDOMPointType
PointAtReachedContent() const {
274 MOZ_ASSERT(mContent
);
276 case WSType::CollapsibleWhiteSpaces
:
277 case WSType::NonCollapsibleCharacters
:
278 case WSType::PreformattedLineBreak
:
279 MOZ_DIAGNOSTIC_ASSERT(mOffset
.isSome());
280 return mDirection
== ScanDirection::Forward
281 ? EditorDOMPointType(mContent
, mOffset
.valueOr(0))
282 : EditorDOMPointType(mContent
,
283 std::max(mOffset
.valueOr(1), 1u) - 1);
285 return EditorDOMPointType(mContent
);
290 * PointAfterReachedContent() returns the position after found visible content
291 * or reached block element.
293 template <typename EditorDOMPointType
>
294 EditorDOMPointType
PointAfterReachedContent() const {
295 MOZ_ASSERT(mContent
);
296 return PointAtReachedContent
<EditorDOMPointType
>().template NextPoint
<>();
300 * The scanner reached <img> or something which is inline and is not a
303 bool ReachedSpecialContent() const {
304 return mReason
== WSType::SpecialContent
;
308 * The point is in visible characters or collapsible white-spaces.
310 bool InVisibleOrCollapsibleCharacters() const {
311 return mReason
== WSType::CollapsibleWhiteSpaces
||
312 mReason
== WSType::NonCollapsibleCharacters
;
316 * The point is in collapsible white-spaces.
318 bool InCollapsibleWhiteSpaces() const {
319 return mReason
== WSType::CollapsibleWhiteSpaces
;
323 * The point is in visible non-collapsible characters.
325 bool InNonCollapsibleCharacters() const {
326 return mReason
== WSType::NonCollapsibleCharacters
;
330 * The scanner reached a <br> element.
332 bool ReachedBRElement() const { return mReason
== WSType::BRElement
; }
333 bool ReachedVisibleBRElement() const {
334 return ReachedBRElement() &&
335 HTMLEditUtils::IsVisibleBRElement(*BRElementPtr());
337 bool ReachedInvisibleBRElement() const {
338 return ReachedBRElement() &&
339 HTMLEditUtils::IsInvisibleBRElement(*BRElementPtr());
342 bool ReachedPreformattedLineBreak() const {
343 return mReason
== WSType::PreformattedLineBreak
;
347 * The scanner reached a <hr> element.
349 bool ReachedHRElement() const {
350 return mContent
&& mContent
->IsHTMLElement(nsGkAtoms::hr
);
354 * The scanner reached current block boundary or other block element.
356 bool ReachedBlockBoundary() const {
357 return mReason
== WSType::CurrentBlockBoundary
||
358 mReason
== WSType::OtherBlockBoundary
;
362 * The scanner reached current block element boundary.
364 bool ReachedCurrentBlockBoundary() const {
365 return mReason
== WSType::CurrentBlockBoundary
;
369 * The scanner reached other block element.
371 bool ReachedOtherBlockElement() const {
372 return mReason
== WSType::OtherBlockBoundary
;
376 * The scanner reached other block element that isn't editable
378 bool ReachedNonEditableOtherBlockElement() const {
379 return ReachedOtherBlockElement() && !GetContent()->IsEditable();
383 * The scanner reached inline editing host boundary.
385 [[nodiscard
]] bool ReachedInlineEditingHostBoundary() const {
386 return mReason
== WSType::InlineEditingHostBoundary
;
390 * The scanner reached something non-text node.
392 bool ReachedSomethingNonTextContent() const {
393 return !InVisibleOrCollapsibleCharacters();
397 nsCOMPtr
<nsIContent
> mContent
;
398 Maybe
<uint32_t> mOffset
;
400 ScanDirection mDirection
= ScanDirection::Backward
;
403 class MOZ_STACK_CLASS WSRunScanner final
{
405 using WSType
= WSScanResult::WSType
;
407 template <typename EditorDOMPointType
>
408 WSRunScanner(const Element
* aEditingHost
,
409 const EditorDOMPointType
& aScanStartPoint
,
410 BlockInlineCheck aBlockInlineCheck
)
411 : mScanStartPoint(aScanStartPoint
.template To
<EditorDOMPoint
>()),
412 mEditingHost(const_cast<Element
*>(aEditingHost
)),
413 mTextFragmentDataAtStart(mScanStartPoint
, mEditingHost
,
415 mBlockInlineCheck(aBlockInlineCheck
) {}
417 // ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom() returns the first visible
418 // node at or after aPoint. If there is no visible nodes after aPoint,
419 // returns topmost editable inline ancestor at end of current block. See
420 // comments around WSScanResult for the detail. When you reach a character,
421 // this returns WSScanResult both whose Point_Deprecated() and
422 // PointAtReachedContent() return the found character position.
423 template <typename PT
, typename CT
>
424 WSScanResult
ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
425 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const;
426 template <typename PT
, typename CT
>
427 static WSScanResult
ScanInclusiveNextVisibleNodeOrBlockBoundary(
428 const Element
* aEditingHost
, const EditorDOMPointBase
<PT
, CT
>& aPoint
,
429 BlockInlineCheck aBlockInlineCheck
) {
430 return WSRunScanner(aEditingHost
, aPoint
, aBlockInlineCheck
)
431 .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aPoint
);
434 // ScanPreviousVisibleNodeOrBlockBoundaryFrom() returns the first visible node
435 // before aPoint. If there is no visible nodes before aPoint, returns topmost
436 // editable inline ancestor at start of current block. See comments around
437 // WSScanResult for the detail. When you reach a character, this returns
438 // WSScanResult whose Point_Deprecated() returns next point of the found
439 // character and PointAtReachedContent() returns the point at found character.
440 template <typename PT
, typename CT
>
441 WSScanResult
ScanPreviousVisibleNodeOrBlockBoundaryFrom(
442 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const;
443 template <typename PT
, typename CT
>
444 static WSScanResult
ScanPreviousVisibleNodeOrBlockBoundary(
445 const Element
* aEditingHost
, const EditorDOMPointBase
<PT
, CT
>& aPoint
,
446 BlockInlineCheck aBlockInlineCheck
) {
447 return WSRunScanner(aEditingHost
, aPoint
, aBlockInlineCheck
)
448 .ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPoint
);
452 * GetInclusiveNextEditableCharPoint() returns a point in a text node which
453 * is at current editable character or next editable character if aPoint
454 * does not points an editable character.
456 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
458 static EditorDOMPointType
GetInclusiveNextEditableCharPoint(
459 Element
* aEditingHost
, const EditorDOMPointBase
<PT
, CT
>& aPoint
,
460 BlockInlineCheck aBlockInlineCheck
) {
461 if (aPoint
.IsInTextNode() && !aPoint
.IsEndOfContainer() &&
462 HTMLEditUtils::IsSimplyEditableNode(
463 *aPoint
.template ContainerAs
<Text
>())) {
464 return EditorDOMPointType(aPoint
.template ContainerAs
<Text
>(),
467 return WSRunScanner(aEditingHost
, aPoint
, aBlockInlineCheck
)
468 .GetInclusiveNextEditableCharPoint
<EditorDOMPointType
>(aPoint
);
472 * GetPreviousEditableCharPoint() returns a point in a text node which
473 * is at previous editable character.
475 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
477 static EditorDOMPointType
GetPreviousEditableCharPoint(
478 Element
* aEditingHost
, const EditorDOMPointBase
<PT
, CT
>& aPoint
,
479 BlockInlineCheck aBlockInlineCheck
) {
480 if (aPoint
.IsInTextNode() && !aPoint
.IsStartOfContainer() &&
481 HTMLEditUtils::IsSimplyEditableNode(
482 *aPoint
.template ContainerAs
<Text
>())) {
483 return EditorDOMPointType(aPoint
.template ContainerAs
<Text
>(),
484 aPoint
.Offset() - 1);
486 return WSRunScanner(aEditingHost
, aPoint
, aBlockInlineCheck
)
487 .GetPreviousEditableCharPoint
<EditorDOMPointType
>(aPoint
);
491 * Scan aTextNode from end or start to find last or first visible things.
492 * I.e., this returns a point immediately before or after invisible
493 * white-spaces of aTextNode if aTextNode ends or begins with some invisible
495 * Note that the result may not be in different text node if aTextNode has
496 * only invisible white-spaces and there is previous or next text node.
498 template <typename EditorDOMPointType
>
499 static EditorDOMPointType
GetAfterLastVisiblePoint(
500 Text
& aTextNode
, const Element
* aAncestorLimiter
);
501 template <typename EditorDOMPointType
>
502 static EditorDOMPointType
GetFirstVisiblePoint(
503 Text
& aTextNode
, const Element
* aAncestorLimiter
);
506 * GetRangeInTextNodesToForwardDeleteFrom() returns the range to remove
507 * text when caret is at aPoint.
509 static Result
<EditorDOMRangeInTexts
, nsresult
>
510 GetRangeInTextNodesToForwardDeleteFrom(const EditorDOMPoint
& aPoint
,
511 const Element
& aEditingHost
);
514 * GetRangeInTextNodesToBackspaceFrom() returns the range to remove text
515 * when caret is at aPoint.
517 static Result
<EditorDOMRangeInTexts
, nsresult
>
518 GetRangeInTextNodesToBackspaceFrom(const EditorDOMPoint
& aPoint
,
519 const Element
& aEditingHost
);
522 * GetRangesForDeletingAtomicContent() returns the range to delete
523 * aAtomicContent. If it's followed by invisible white-spaces, they will
524 * be included into the range.
526 static EditorDOMRange
GetRangesForDeletingAtomicContent(
527 Element
* aEditingHost
, const nsIContent
& aAtomicContent
);
530 * GetRangeForDeleteBlockElementBoundaries() returns a range starting from end
531 * of aLeftBlockElement to start of aRightBlockElement and extend invisible
532 * white-spaces around them.
534 * @param aHTMLEditor The HTML editor.
535 * @param aLeftBlockElement The block element which will be joined with
536 * aRightBlockElement.
537 * @param aRightBlockElement The block element which will be joined with
538 * aLeftBlockElement. This must be an element
539 * after aLeftBlockElement.
540 * @param aPointContainingTheOtherBlock
541 * When aRightBlockElement is an ancestor of
542 * aLeftBlockElement, this must be set and the
543 * container must be aRightBlockElement.
544 * When aLeftBlockElement is an ancestor of
545 * aRightBlockElement, this must be set and the
546 * container must be aLeftBlockElement.
547 * Otherwise, must not be set.
549 static EditorDOMRange
GetRangeForDeletingBlockElementBoundaries(
550 const HTMLEditor
& aHTMLEditor
, const Element
& aLeftBlockElement
,
551 const Element
& aRightBlockElement
,
552 const EditorDOMPoint
& aPointContainingTheOtherBlock
);
555 * ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() may shrink aRange if it
556 * starts and/or ends with an atomic content, but the range boundary
557 * is in adjacent text nodes. Returns true if this modifies the range.
559 static Result
<bool, nsresult
> ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
560 const HTMLEditor
& aHTMLEditor
, nsRange
& aRange
,
561 const Element
* aEditingHost
);
564 * GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries() returns
565 * extended range if range boundaries of aRange are in invisible white-spaces.
567 static EditorDOMRange
GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
568 Element
* aEditingHost
, const EditorDOMRange
& aRange
);
571 * GetPrecedingBRElementUnlessVisibleContentFound() scans a `<br>` element
572 * backward, but stops scanning it if the scanner finds visible character
573 * or something. In other words, this method ignores only invisible
574 * white-spaces between `<br>` element and aPoint.
576 template <typename EditorDOMPointType
>
577 MOZ_NEVER_INLINE_DEBUG
static HTMLBRElement
*
578 GetPrecedingBRElementUnlessVisibleContentFound(
579 Element
* aEditingHost
, const EditorDOMPointType
& aPoint
,
580 BlockInlineCheck aBlockInlineCheck
) {
581 MOZ_ASSERT(aPoint
.IsSetAndValid());
582 // XXX This method behaves differently even in similar point.
583 // If aPoint is in a text node following `<br>` element, reaches the
584 // `<br>` element when all characters between the `<br>` and
585 // aPoint are ASCII whitespaces.
586 // But if aPoint is not in a text node, e.g., at start of an inline
587 // element which is immediately after a `<br>` element, returns the
588 // `<br>` element even if there is no invisible white-spaces.
589 if (aPoint
.IsStartOfContainer()) {
592 // TODO: Scan for end boundary is redundant in this case, we should optimize
594 TextFragmentData
textFragmentData(aPoint
, aEditingHost
, aBlockInlineCheck
);
595 return textFragmentData
.StartsFromBRElement()
596 ? textFragmentData
.StartReasonBRElementPtr()
600 const EditorDOMPoint
& ScanStartRef() const { return mScanStartPoint
; }
603 * GetStartReasonContent() and GetEndReasonContent() return a node which
604 * was found by scanning from mScanStartPoint backward or forward. If there
605 * was white-spaces or text from the point, returns the text node. Otherwise,
606 * returns an element which is explained by the following methods. Note that
607 * when the reason is WSType::CurrentBlockBoundary, In most cases, it's
608 * current block element which is editable, but also may be non-element and/or
609 * non-editable. See MOZ_ASSERT_IF()s in WSScanResult::AssertIfInvalidData()
612 nsIContent
* GetStartReasonContent() const {
613 return TextFragmentDataAtStartRef().GetStartReasonContent();
615 nsIContent
* GetEndReasonContent() const {
616 return TextFragmentDataAtStartRef().GetEndReasonContent();
619 bool StartsFromNonCollapsibleCharacters() const {
620 return TextFragmentDataAtStartRef().StartsFromNonCollapsibleCharacters();
622 bool StartsFromSpecialContent() const {
623 return TextFragmentDataAtStartRef().StartsFromSpecialContent();
625 bool StartsFromBRElement() const {
626 return TextFragmentDataAtStartRef().StartsFromBRElement();
628 bool StartsFromVisibleBRElement() const {
629 return TextFragmentDataAtStartRef().StartsFromVisibleBRElement();
631 bool StartsFromInvisibleBRElement() const {
632 return TextFragmentDataAtStartRef().StartsFromInvisibleBRElement();
634 bool StartsFromPreformattedLineBreak() const {
635 return TextFragmentDataAtStartRef().StartsFromPreformattedLineBreak();
637 bool StartsFromCurrentBlockBoundary() const {
638 return TextFragmentDataAtStartRef().StartsFromCurrentBlockBoundary();
640 bool StartsFromOtherBlockElement() const {
641 return TextFragmentDataAtStartRef().StartsFromOtherBlockElement();
643 bool StartsFromBlockBoundary() const {
644 return TextFragmentDataAtStartRef().StartsFromBlockBoundary();
646 bool StartsFromInlineEditingHostBoundary() const {
647 return TextFragmentDataAtStartRef().StartsFromInlineEditingHostBoundary();
649 bool StartsFromHardLineBreak() const {
650 return TextFragmentDataAtStartRef().StartsFromHardLineBreak();
652 bool EndsByNonCollapsibleCharacters() const {
653 return TextFragmentDataAtStartRef().EndsByNonCollapsibleCharacters();
655 bool EndsBySpecialContent() const {
656 return TextFragmentDataAtStartRef().EndsBySpecialContent();
658 bool EndsByBRElement() const {
659 return TextFragmentDataAtStartRef().EndsByBRElement();
661 bool EndsByVisibleBRElement() const {
662 return TextFragmentDataAtStartRef().EndsByVisibleBRElement();
664 bool EndsByInvisibleBRElement() const {
665 return TextFragmentDataAtStartRef().EndsByInvisibleBRElement();
667 bool EndsByPreformattedLineBreak() const {
668 return TextFragmentDataAtStartRef().EndsByPreformattedLineBreak();
670 bool EndsByCurrentBlockBoundary() const {
671 return TextFragmentDataAtStartRef().EndsByCurrentBlockBoundary();
673 bool EndsByOtherBlockElement() const {
674 return TextFragmentDataAtStartRef().EndsByOtherBlockElement();
676 bool EndsByBlockBoundary() const {
677 return TextFragmentDataAtStartRef().EndsByBlockBoundary();
679 bool EndsByInlineEditingHostBoundary() const {
680 return TextFragmentDataAtStartRef().EndsByInlineEditingHostBoundary();
683 MOZ_NEVER_INLINE_DEBUG Element
* StartReasonOtherBlockElementPtr() const {
684 return TextFragmentDataAtStartRef().StartReasonOtherBlockElementPtr();
686 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* StartReasonBRElementPtr() const {
687 return TextFragmentDataAtStartRef().StartReasonBRElementPtr();
689 MOZ_NEVER_INLINE_DEBUG Element
* EndReasonOtherBlockElementPtr() const {
690 return TextFragmentDataAtStartRef().EndReasonOtherBlockElementPtr();
692 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* EndReasonBRElementPtr() const {
693 return TextFragmentDataAtStartRef().EndReasonBRElementPtr();
697 * Active editing host when this instance is created.
699 Element
* GetEditingHost() const { return mEditingHost
; }
702 using EditorType
= EditorBase::EditorType
;
704 class TextFragmentData
;
706 // VisibleWhiteSpacesData represents 0 or more visible white-spaces.
707 class MOZ_STACK_CLASS VisibleWhiteSpacesData final
{
709 bool IsInitialized() const {
710 return mLeftWSType
!= WSType::NotInitialized
||
711 mRightWSType
!= WSType::NotInitialized
;
714 EditorDOMPoint
StartRef() const { return mStartPoint
; }
715 EditorDOMPoint
EndRef() const { return mEndPoint
; }
718 * Information why the white-spaces start from (i.e., this indicates the
719 * previous content type of the fragment).
721 bool StartsFromNonCollapsibleCharacters() const {
722 return mLeftWSType
== WSType::NonCollapsibleCharacters
;
724 bool StartsFromSpecialContent() const {
725 return mLeftWSType
== WSType::SpecialContent
;
727 bool StartsFromPreformattedLineBreak() const {
728 return mLeftWSType
== WSType::PreformattedLineBreak
;
732 * Information why the white-spaces end by (i.e., this indicates the
733 * next content type of the fragment).
735 bool EndsByNonCollapsibleCharacters() const {
736 return mRightWSType
== WSType::NonCollapsibleCharacters
;
738 bool EndsByTrailingWhiteSpaces() const {
739 return mRightWSType
== WSType::TrailingWhiteSpaces
;
741 bool EndsBySpecialContent() const {
742 return mRightWSType
== WSType::SpecialContent
;
744 bool EndsByBRElement() const { return mRightWSType
== WSType::BRElement
; }
745 bool EndsByPreformattedLineBreak() const {
746 return mRightWSType
== WSType::PreformattedLineBreak
;
748 bool EndsByBlockBoundary() const {
749 return mRightWSType
== WSType::CurrentBlockBoundary
||
750 mRightWSType
== WSType::OtherBlockBoundary
;
752 bool EndsByInlineEditingHostBoundary() const {
753 return mRightWSType
== WSType::InlineEditingHostBoundary
;
757 * ComparePoint() compares aPoint with the white-spaces.
759 enum class PointPosition
{
760 BeforeStartOfFragment
,
767 template <typename EditorDOMPointType
>
768 PointPosition
ComparePoint(const EditorDOMPointType
& aPoint
) const {
769 MOZ_ASSERT(aPoint
.IsSetAndValid());
770 if (StartRef() == aPoint
) {
771 return PointPosition::StartOfFragment
;
773 if (EndRef() == aPoint
) {
774 return PointPosition::EndOfFragment
;
776 const bool startIsBeforePoint
= StartRef().IsBefore(aPoint
);
777 const bool pointIsBeforeEnd
= aPoint
.IsBefore(EndRef());
778 if (startIsBeforePoint
&& pointIsBeforeEnd
) {
779 return PointPosition::MiddleOfFragment
;
781 if (startIsBeforePoint
) {
782 return PointPosition::AfterEndOfFragment
;
784 if (pointIsBeforeEnd
) {
785 return PointPosition::BeforeStartOfFragment
;
787 return PointPosition::NotInSameDOMTree
;
791 // Initializers should be accessible only from `TextFragmentData`.
792 friend class WSRunScanner::TextFragmentData
;
793 VisibleWhiteSpacesData()
794 : mLeftWSType(WSType::NotInitialized
),
795 mRightWSType(WSType::NotInitialized
) {}
797 template <typename EditorDOMPointType
>
798 void SetStartPoint(const EditorDOMPointType
& aStartPoint
) {
799 mStartPoint
= aStartPoint
;
801 template <typename EditorDOMPointType
>
802 void SetEndPoint(const EditorDOMPointType
& aEndPoint
) {
803 mEndPoint
= aEndPoint
;
805 void SetStartFrom(WSType aLeftWSType
) { mLeftWSType
= aLeftWSType
; }
806 void SetStartFromLeadingWhiteSpaces() {
807 mLeftWSType
= WSType::LeadingWhiteSpaces
;
809 void SetEndBy(WSType aRightWSType
) { mRightWSType
= aRightWSType
; }
810 void SetEndByTrailingWhiteSpaces() {
811 mRightWSType
= WSType::TrailingWhiteSpaces
;
814 EditorDOMPoint mStartPoint
;
815 EditorDOMPoint mEndPoint
;
816 WSType mLeftWSType
, mRightWSType
;
819 using PointPosition
= VisibleWhiteSpacesData::PointPosition
;
822 * GetInclusiveNextEditableCharPoint() returns aPoint if it points a character
823 * in an editable text node, or start of next editable text node otherwise.
824 * FYI: For the performance, this does not check whether given container
825 * is not after mStart.mReasonContent or not.
827 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
829 EditorDOMPointType
GetInclusiveNextEditableCharPoint(
830 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
831 return TextFragmentDataAtStartRef()
832 .GetInclusiveNextEditableCharPoint
<EditorDOMPointType
>(aPoint
);
836 * GetPreviousEditableCharPoint() returns previous editable point in a
837 * text node. Note that this returns last character point when it meets
838 * non-empty text node, otherwise, returns a point in an empty text node.
839 * FYI: For the performance, this does not check whether given container
840 * is not before mEnd.mReasonContent or not.
842 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
844 EditorDOMPointType
GetPreviousEditableCharPoint(
845 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
846 return TextFragmentDataAtStartRef()
847 .GetPreviousEditableCharPoint
<EditorDOMPointType
>(aPoint
);
851 * GetEndOfCollapsibleASCIIWhiteSpaces() returns the next visible char
852 * (meaning a character except ASCII white-spaces) point or end of last text
853 * node scanning from aPointAtASCIIWhiteSpace.
854 * Note that this may return different text node from the container of
855 * aPointAtASCIIWhiteSpace.
857 template <typename EditorDOMPointType
= EditorDOMPointInText
>
858 EditorDOMPointType
GetEndOfCollapsibleASCIIWhiteSpaces(
859 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
860 nsIEditor::EDirection aDirectionToDelete
) const {
861 MOZ_ASSERT(aDirectionToDelete
== nsIEditor::eNone
||
862 aDirectionToDelete
== nsIEditor::eNext
||
863 aDirectionToDelete
== nsIEditor::ePrevious
);
864 return TextFragmentDataAtStartRef()
865 .GetEndOfCollapsibleASCIIWhiteSpaces
<EditorDOMPointType
>(
866 aPointAtASCIIWhiteSpace
, aDirectionToDelete
);
870 * GetFirstASCIIWhiteSpacePointCollapsedTo() returns the first ASCII
871 * white-space which aPointAtASCIIWhiteSpace belongs to. In other words,
872 * the white-space at aPointAtASCIIWhiteSpace should be collapsed into
874 * Note that this may return different text node from the container of
875 * aPointAtASCIIWhiteSpace.
877 template <typename EditorDOMPointType
= EditorDOMPointInText
>
878 EditorDOMPointType
GetFirstASCIIWhiteSpacePointCollapsedTo(
879 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
880 nsIEditor::EDirection aDirectionToDelete
) const {
881 MOZ_ASSERT(aDirectionToDelete
== nsIEditor::eNone
||
882 aDirectionToDelete
== nsIEditor::eNext
||
883 aDirectionToDelete
== nsIEditor::ePrevious
);
884 return TextFragmentDataAtStartRef()
885 .GetFirstASCIIWhiteSpacePointCollapsedTo
<EditorDOMPointType
>(
886 aPointAtASCIIWhiteSpace
, aDirectionToDelete
);
889 EditorDOMPointInText
GetPreviousCharPointFromPointInText(
890 const EditorDOMPointInText
& aPoint
) const;
892 char16_t
GetCharAt(Text
* aTextNode
, uint32_t aOffset
) const;
895 * TextFragmentData stores the information of white-space sequence which
896 * contains `aPoint` of the constructor.
898 class MOZ_STACK_CLASS TextFragmentData final
{
900 class NoBreakingSpaceData
;
901 class MOZ_STACK_CLASS BoundaryData final
{
903 using NoBreakingSpaceData
=
904 WSRunScanner::TextFragmentData::NoBreakingSpaceData
;
907 * ScanCollapsibleWhiteSpaceStartFrom() returns start boundary data of
908 * white-spaces containing aPoint. When aPoint is in a text node and
909 * points a non-white-space character or the text node is preformatted,
910 * this returns the data at aPoint.
912 * @param aPoint Scan start point.
913 * @param aEditableBlockParentOrTopmostEditableInlineElement
914 * Nearest editable block parent element of
915 * aPoint if there is. Otherwise, inline editing
917 * @param aEditingHost Active editing host.
918 * @param aNBSPData Optional. If set, this recodes first and last
921 template <typename EditorDOMPointType
>
922 static BoundaryData
ScanCollapsibleWhiteSpaceStartFrom(
923 const EditorDOMPointType
& aPoint
,
924 const Element
& aEditableBlockParentOrTopmostEditableInlineElement
,
925 const Element
* aEditingHost
, NoBreakingSpaceData
* aNBSPData
,
926 BlockInlineCheck aBlockInlineCheck
);
929 * ScanCollapsibleWhiteSpaceEndFrom() returns end boundary data of
930 * white-spaces containing aPoint. When aPoint is in a text node and
931 * points a non-white-space character or the text node is preformatted,
932 * this returns the data at aPoint.
934 * @param aPoint Scan start point.
935 * @param aEditableBlockParentOrTopmostEditableInlineElement
936 * Nearest editable block parent element of
937 * aPoint if there is. Otherwise, inline editing
939 * @param aEditingHost Active editing host.
940 * @param aNBSPData Optional. If set, this recodes first and last
943 template <typename EditorDOMPointType
>
944 static BoundaryData
ScanCollapsibleWhiteSpaceEndFrom(
945 const EditorDOMPointType
& aPoint
,
946 const Element
& aEditableBlockParentOrTopmostEditableInlineElement
,
947 const Element
* aEditingHost
, NoBreakingSpaceData
* aNBSPData
,
948 BlockInlineCheck aBlockInlineCheck
);
950 BoundaryData() = default;
951 template <typename EditorDOMPointType
>
952 BoundaryData(const EditorDOMPointType
& aPoint
, nsIContent
& aReasonContent
,
954 : mReasonContent(&aReasonContent
),
955 mPoint(aPoint
.template To
<EditorDOMPoint
>()),
957 bool Initialized() const { return mReasonContent
&& mPoint
.IsSet(); }
959 nsIContent
* GetReasonContent() const { return mReasonContent
; }
960 const EditorDOMPoint
& PointRef() const { return mPoint
; }
961 WSType
RawReason() const { return mReason
; }
963 bool IsNonCollapsibleCharacters() const {
964 return mReason
== WSType::NonCollapsibleCharacters
;
966 bool IsSpecialContent() const {
967 return mReason
== WSType::SpecialContent
;
969 bool IsBRElement() const { return mReason
== WSType::BRElement
; }
970 bool IsPreformattedLineBreak() const {
971 return mReason
== WSType::PreformattedLineBreak
;
973 bool IsCurrentBlockBoundary() const {
974 return mReason
== WSType::CurrentBlockBoundary
;
976 bool IsOtherBlockBoundary() const {
977 return mReason
== WSType::OtherBlockBoundary
;
979 bool IsBlockBoundary() const {
980 return mReason
== WSType::CurrentBlockBoundary
||
981 mReason
== WSType::OtherBlockBoundary
;
983 bool IsInlineEditingHostBoundary() const {
984 return mReason
== WSType::InlineEditingHostBoundary
;
986 bool IsHardLineBreak() const {
987 return mReason
== WSType::CurrentBlockBoundary
||
988 mReason
== WSType::OtherBlockBoundary
||
989 mReason
== WSType::BRElement
||
990 mReason
== WSType::PreformattedLineBreak
;
992 MOZ_NEVER_INLINE_DEBUG Element
* OtherBlockElementPtr() const {
993 MOZ_DIAGNOSTIC_ASSERT(mReasonContent
->IsElement());
994 return mReasonContent
->AsElement();
996 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* BRElementPtr() const {
997 MOZ_DIAGNOSTIC_ASSERT(mReasonContent
->IsHTMLElement(nsGkAtoms::br
));
998 return static_cast<HTMLBRElement
*>(mReasonContent
.get());
1003 * Helper methods of ScanCollapsibleWhiteSpaceStartFrom() and
1004 * ScanCollapsibleWhiteSpaceEndFrom() when they need to scan in a text
1007 template <typename EditorDOMPointType
>
1008 static Maybe
<BoundaryData
> ScanCollapsibleWhiteSpaceStartInTextNode(
1009 const EditorDOMPointType
& aPoint
, NoBreakingSpaceData
* aNBSPData
,
1010 BlockInlineCheck aBlockInlineCheck
);
1011 template <typename EditorDOMPointType
>
1012 static Maybe
<BoundaryData
> ScanCollapsibleWhiteSpaceEndInTextNode(
1013 const EditorDOMPointType
& aPoint
, NoBreakingSpaceData
* aNBSPData
,
1014 BlockInlineCheck aBlockInlineCheck
);
1016 nsCOMPtr
<nsIContent
> mReasonContent
;
1017 EditorDOMPoint mPoint
;
1018 // Must be one of WSType::NotInitialized,
1019 // WSType::NonCollapsibleCharacters, WSType::SpecialContent,
1020 // WSType::BRElement, WSType::CurrentBlockBoundary,
1021 // WSType::OtherBlockBoundary or WSType::InlineEditingHostBoundary.
1022 WSType mReason
= WSType::NotInitialized
;
1025 class MOZ_STACK_CLASS NoBreakingSpaceData final
{
1027 enum class Scanning
{ Forward
, Backward
};
1028 void NotifyNBSP(const EditorDOMPointInText
& aPoint
,
1029 Scanning aScanningDirection
) {
1030 MOZ_ASSERT(aPoint
.IsSetAndValid());
1031 MOZ_ASSERT(aPoint
.IsCharNBSP());
1032 if (!mFirst
.IsSet() || aScanningDirection
== Scanning::Backward
) {
1035 if (!mLast
.IsSet() || aScanningDirection
== Scanning::Forward
) {
1040 const EditorDOMPointInText
& FirstPointRef() const { return mFirst
; }
1041 const EditorDOMPointInText
& LastPointRef() const { return mLast
; }
1043 bool FoundNBSP() const {
1044 MOZ_ASSERT(mFirst
.IsSet() == mLast
.IsSet());
1045 return mFirst
.IsSet();
1049 EditorDOMPointInText mFirst
;
1050 EditorDOMPointInText mLast
;
1054 TextFragmentData() = delete;
1055 template <typename EditorDOMPointType
>
1056 TextFragmentData(const WSRunScanner
& aWSRunScanner
,
1057 const EditorDOMPointType
& aPoint
)
1058 : TextFragmentData(aPoint
, aWSRunScanner
.mEditingHost
,
1059 aWSRunScanner
.mBlockInlineCheck
) {}
1060 template <typename EditorDOMPointType
>
1061 TextFragmentData(const EditorDOMPointType
& aPoint
,
1062 const Element
* aEditingHost
,
1063 BlockInlineCheck aBlockInlineCheck
);
1065 bool IsInitialized() const {
1066 return mStart
.Initialized() && mEnd
.Initialized();
1069 nsIContent
* GetStartReasonContent() const {
1070 return mStart
.GetReasonContent();
1072 nsIContent
* GetEndReasonContent() const { return mEnd
.GetReasonContent(); }
1074 bool StartsFromNonCollapsibleCharacters() const {
1075 return mStart
.IsNonCollapsibleCharacters();
1077 bool StartsFromSpecialContent() const { return mStart
.IsSpecialContent(); }
1078 bool StartsFromBRElement() const { return mStart
.IsBRElement(); }
1079 bool StartsFromVisibleBRElement() const {
1080 return StartsFromBRElement() &&
1081 HTMLEditUtils::IsVisibleBRElement(*GetStartReasonContent());
1083 bool StartsFromInvisibleBRElement() const {
1084 return StartsFromBRElement() &&
1085 HTMLEditUtils::IsInvisibleBRElement(*GetStartReasonContent());
1087 bool StartsFromPreformattedLineBreak() const {
1088 return mStart
.IsPreformattedLineBreak();
1090 bool StartsFromCurrentBlockBoundary() const {
1091 return mStart
.IsCurrentBlockBoundary();
1093 bool StartsFromOtherBlockElement() const {
1094 return mStart
.IsOtherBlockBoundary();
1096 bool StartsFromBlockBoundary() const { return mStart
.IsBlockBoundary(); }
1097 bool StartsFromInlineEditingHostBoundary() const {
1098 return mStart
.IsInlineEditingHostBoundary();
1100 bool StartsFromHardLineBreak() const { return mStart
.IsHardLineBreak(); }
1101 bool EndsByNonCollapsibleCharacters() const {
1102 return mEnd
.IsNonCollapsibleCharacters();
1104 bool EndsBySpecialContent() const { return mEnd
.IsSpecialContent(); }
1105 bool EndsByBRElement() const { return mEnd
.IsBRElement(); }
1106 bool EndsByVisibleBRElement() const {
1107 return EndsByBRElement() &&
1108 HTMLEditUtils::IsVisibleBRElement(*GetEndReasonContent());
1110 bool EndsByInvisibleBRElement() const {
1111 return EndsByBRElement() &&
1112 HTMLEditUtils::IsInvisibleBRElement(*GetEndReasonContent());
1114 bool EndsByPreformattedLineBreak() const {
1115 return mEnd
.IsPreformattedLineBreak();
1117 bool EndsByInvisiblePreformattedLineBreak() const {
1118 return mEnd
.IsPreformattedLineBreak() &&
1119 HTMLEditUtils::IsInvisiblePreformattedNewLine(mEnd
.PointRef());
1121 bool EndsByCurrentBlockBoundary() const {
1122 return mEnd
.IsCurrentBlockBoundary();
1124 bool EndsByOtherBlockElement() const { return mEnd
.IsOtherBlockBoundary(); }
1125 bool EndsByBlockBoundary() const { return mEnd
.IsBlockBoundary(); }
1126 bool EndsByInlineEditingHostBoundary() const {
1127 return mEnd
.IsInlineEditingHostBoundary();
1130 WSType
StartRawReason() const { return mStart
.RawReason(); }
1131 WSType
EndRawReason() const { return mEnd
.RawReason(); }
1133 MOZ_NEVER_INLINE_DEBUG Element
* StartReasonOtherBlockElementPtr() const {
1134 return mStart
.OtherBlockElementPtr();
1136 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* StartReasonBRElementPtr() const {
1137 return mStart
.BRElementPtr();
1139 MOZ_NEVER_INLINE_DEBUG Element
* EndReasonOtherBlockElementPtr() const {
1140 return mEnd
.OtherBlockElementPtr();
1142 MOZ_NEVER_INLINE_DEBUG HTMLBRElement
* EndReasonBRElementPtr() const {
1143 return mEnd
.BRElementPtr();
1146 const EditorDOMPoint
& StartRef() const { return mStart
.PointRef(); }
1147 const EditorDOMPoint
& EndRef() const { return mEnd
.PointRef(); }
1149 const EditorDOMPoint
& ScanStartRef() const { return mScanStartPoint
; }
1151 bool FoundNoBreakingWhiteSpaces() const { return mNBSPData
.FoundNBSP(); }
1152 const EditorDOMPointInText
& FirstNBSPPointRef() const {
1153 return mNBSPData
.FirstPointRef();
1155 const EditorDOMPointInText
& LastNBSPPointRef() const {
1156 return mNBSPData
.LastPointRef();
1159 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
1161 EditorDOMPointType
GetInclusiveNextEditableCharPoint(
1162 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const;
1163 template <typename EditorDOMPointType
= EditorDOMPointInText
, typename PT
,
1165 EditorDOMPointType
GetPreviousEditableCharPoint(
1166 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const;
1168 template <typename EditorDOMPointType
= EditorDOMPointInText
>
1169 EditorDOMPointType
GetEndOfCollapsibleASCIIWhiteSpaces(
1170 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
1171 nsIEditor::EDirection aDirectionToDelete
) const;
1172 template <typename EditorDOMPointType
= EditorDOMPointInText
>
1173 EditorDOMPointType
GetFirstASCIIWhiteSpacePointCollapsedTo(
1174 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
1175 nsIEditor::EDirection aDirectionToDelete
) const;
1178 * GetNonCollapsedRangeInTexts() returns non-empty range in texts which
1179 * is the largest range in aRange if there is some text nodes.
1181 EditorDOMRangeInTexts
GetNonCollapsedRangeInTexts(
1182 const EditorDOMRange
& aRange
) const;
1185 * InvisibleLeadingWhiteSpaceRangeRef() retruns reference to two DOM points,
1186 * start of the line and first visible point or end of the hard line. When
1187 * this returns non-positioned range or positioned but collapsed range,
1188 * there is no invisible leading white-spaces.
1189 * Note that if there are only invisible white-spaces in a hard line,
1190 * this returns all of the white-spaces.
1192 const EditorDOMRange
& InvisibleLeadingWhiteSpaceRangeRef() const;
1195 * InvisibleTrailingWhiteSpaceRangeRef() returns reference to two DOM
1196 * points, first invisible white-space and end of the hard line. When this
1197 * returns non-positioned range or positioned but collapsed range,
1198 * there is no invisible trailing white-spaces.
1199 * Note that if there are only invisible white-spaces in a hard line,
1200 * this returns all of the white-spaces.
1202 const EditorDOMRange
& InvisibleTrailingWhiteSpaceRangeRef() const;
1205 * GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt() returns new
1206 * invisible leading white-space range which should be removed if
1207 * splitting invisible white-space sequence at aPointToSplit creates
1208 * new invisible leading white-spaces in the new line.
1209 * Note that the result may be collapsed range if the point is around
1210 * invisible white-spaces.
1212 template <typename EditorDOMPointType
>
1213 EditorDOMRange
GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(
1214 const EditorDOMPointType
& aPointToSplit
) const {
1215 // If there are invisible trailing white-spaces and some or all of them
1216 // become invisible leading white-spaces in the new line, although we
1217 // don't need to delete them, but for aesthetically and backward
1218 // compatibility, we should remove them.
1219 const EditorDOMRange
& trailingWhiteSpaceRange
=
1220 InvisibleTrailingWhiteSpaceRangeRef();
1221 // XXX Why don't we check leading white-spaces too?
1222 if (!trailingWhiteSpaceRange
.IsPositioned()) {
1223 return trailingWhiteSpaceRange
;
1225 // If the point is before the trailing white-spaces, the new line won't
1226 // start with leading white-spaces.
1227 if (aPointToSplit
.IsBefore(trailingWhiteSpaceRange
.StartRef())) {
1228 return EditorDOMRange();
1230 // If the point is in the trailing white-spaces, the new line may
1231 // start with some leading white-spaces. Returning collapsed range
1232 // is intentional because the caller may want to know whether the
1233 // point is in trailing white-spaces or not.
1234 if (aPointToSplit
.EqualsOrIsBefore(trailingWhiteSpaceRange
.EndRef())) {
1235 return EditorDOMRange(trailingWhiteSpaceRange
.StartRef(),
1238 // Otherwise, if the point is after the trailing white-spaces, it may
1239 // be just outside of the text node. E.g., end of parent element.
1240 // This is possible case but the validation cost is not worthwhile
1241 // due to the runtime cost in the worst case. Therefore, we should just
1242 // return collapsed range at the end of trailing white-spaces. Then,
1243 // callers can know the point is immediately after the trailing
1245 return EditorDOMRange(trailingWhiteSpaceRange
.EndRef());
1249 * GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt() returns new
1250 * invisible trailing white-space range which should be removed if
1251 * splitting invisible white-space sequence at aPointToSplit creates
1252 * new invisible trailing white-spaces in the new line.
1253 * Note that the result may be collapsed range if the point is around
1254 * invisible white-spaces.
1256 template <typename EditorDOMPointType
>
1257 EditorDOMRange
GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(
1258 const EditorDOMPointType
& aPointToSplit
) const {
1259 // If there are invisible leading white-spaces and some or all of them
1260 // become end of current line, they will become visible. Therefore, we
1261 // need to delete the invisible leading white-spaces before insertion
1263 const EditorDOMRange
& leadingWhiteSpaceRange
=
1264 InvisibleLeadingWhiteSpaceRangeRef();
1265 if (!leadingWhiteSpaceRange
.IsPositioned()) {
1266 return leadingWhiteSpaceRange
;
1268 // If the point equals or is after the leading white-spaces, the line
1269 // will end without trailing white-spaces.
1270 if (leadingWhiteSpaceRange
.EndRef().IsBefore(aPointToSplit
)) {
1271 return EditorDOMRange();
1273 // If the point is in the leading white-spaces, the line may
1274 // end with some trailing white-spaces. Returning collapsed range
1275 // is intentional because the caller may want to know whether the
1276 // point is in leading white-spaces or not.
1277 if (leadingWhiteSpaceRange
.StartRef().EqualsOrIsBefore(aPointToSplit
)) {
1278 return EditorDOMRange(aPointToSplit
, leadingWhiteSpaceRange
.EndRef());
1280 // Otherwise, if the point is before the leading white-spaces, it may
1281 // be just outside of the text node. E.g., start of parent element.
1282 // This is possible case but the validation cost is not worthwhile
1283 // due to the runtime cost in the worst case. Therefore, we should
1284 // just return collapsed range at start of the leading white-spaces.
1285 // Then, callers can know the point is immediately before the leading
1287 return EditorDOMRange(leadingWhiteSpaceRange
.StartRef());
1291 * FollowingContentMayBecomeFirstVisibleContent() returns true if some
1292 * content may be first visible content after removing content after aPoint.
1293 * Note that it's completely broken what this does. Don't use this method
1296 template <typename EditorDOMPointType
>
1297 bool FollowingContentMayBecomeFirstVisibleContent(
1298 const EditorDOMPointType
& aPoint
) const {
1299 MOZ_ASSERT(aPoint
.IsSetAndValid());
1300 if (!mStart
.IsHardLineBreak() && !mStart
.IsInlineEditingHostBoundary()) {
1303 // If the point is before start of text fragment, that means that the
1304 // point may be at the block boundary or inline element boundary.
1305 if (aPoint
.EqualsOrIsBefore(mStart
.PointRef())) {
1308 // VisibleWhiteSpacesData is marked as start of line only when it
1309 // represents leading white-spaces.
1310 const EditorDOMRange
& leadingWhiteSpaceRange
=
1311 InvisibleLeadingWhiteSpaceRangeRef();
1312 if (!leadingWhiteSpaceRange
.StartRef().IsSet()) {
1315 if (aPoint
.EqualsOrIsBefore(leadingWhiteSpaceRange
.StartRef())) {
1318 if (!leadingWhiteSpaceRange
.EndRef().IsSet()) {
1321 return aPoint
.EqualsOrIsBefore(leadingWhiteSpaceRange
.EndRef());
1325 * PrecedingContentMayBecomeInvisible() returns true if end of preceding
1326 * content is collapsed (when ends with an ASCII white-space).
1327 * Note that it's completely broken what this does. Don't use this method
1330 template <typename EditorDOMPointType
>
1331 bool PrecedingContentMayBecomeInvisible(
1332 const EditorDOMPointType
& aPoint
) const {
1333 MOZ_ASSERT(aPoint
.IsSetAndValid());
1334 // If this fragment is ends by block boundary, always the caller needs
1335 // additional check.
1336 if (mEnd
.IsBlockBoundary() || mEnd
.IsInlineEditingHostBoundary()) {
1340 // If the point is in visible white-spaces and ends with an ASCII
1341 // white-space, it may be collapsed even if it won't be end of line.
1342 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
1343 VisibleWhiteSpacesDataRef();
1344 if (!visibleWhiteSpaces
.IsInitialized()) {
1347 // XXX Odd case, but keep traditional behavior of `FindNearestRun()`.
1348 if (!visibleWhiteSpaces
.StartRef().IsSet()) {
1351 if (!visibleWhiteSpaces
.StartRef().EqualsOrIsBefore(aPoint
)) {
1354 // XXX Odd case, but keep traditional behavior of `FindNearestRun()`.
1355 if (visibleWhiteSpaces
.EndsByTrailingWhiteSpaces()) {
1358 // XXX Must be a bug. This claims that the caller needs additional
1359 // check even when there is no white-spaces.
1360 if (visibleWhiteSpaces
.StartRef() == visibleWhiteSpaces
.EndRef()) {
1363 return aPoint
.IsBefore(visibleWhiteSpaces
.EndRef());
1367 * GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace() may return an
1368 * NBSP point which should be replaced with an ASCII white-space when we're
1369 * inserting text into aPointToInsert. Note that this is a helper method for
1370 * the traditional white-space normalizer. Don't use this with the new
1371 * white-space normalizer.
1372 * Must be called only when VisibleWhiteSpacesDataRef() returns initialized
1373 * instance and previous character of aPointToInsert is in the range.
1375 EditorDOMPointInText
GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
1376 const EditorDOMPoint
& aPointToInsert
) const;
1379 * GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace() may return
1380 * an NBSP point which should be replaced with an ASCII white-space when
1381 * the caller inserts text into aPointToInsert.
1382 * Note that this is a helper method for the traditional white-space
1383 * normalizer. Don't use this with the new white-space normalizer.
1384 * Must be called only when VisibleWhiteSpacesDataRef() returns initialized
1385 * instance, and inclusive next char of aPointToInsert is in the range.
1387 EditorDOMPointInText
1388 GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
1389 const EditorDOMPoint
& aPointToInsert
) const;
1392 * GetReplaceRangeDataAtEndOfDeletionRange() and
1393 * GetReplaceRangeDataAtStartOfDeletionRange() return delete range if
1394 * end or start of deleting range splits invisible trailing/leading
1395 * white-spaces and it may become visible, or return replace range if
1396 * end or start of deleting range splits visible white-spaces and it
1397 * causes some ASCII white-spaces become invisible unless replacing
1400 ReplaceRangeData
GetReplaceRangeDataAtEndOfDeletionRange(
1401 const TextFragmentData
& aTextFragmentDataAtStartToDelete
) const;
1402 ReplaceRangeData
GetReplaceRangeDataAtStartOfDeletionRange(
1403 const TextFragmentData
& aTextFragmentDataAtEndToDelete
) const;
1406 * VisibleWhiteSpacesDataRef() returns reference to visible white-spaces
1407 * data. That is zero or more white-spaces which are visible.
1408 * Note that when there is no visible content, it's not initialized.
1409 * Otherwise, even if there is no white-spaces, it's initialized and
1410 * the range is collapsed in such case.
1412 const VisibleWhiteSpacesData
& VisibleWhiteSpacesDataRef() const;
1415 EditorDOMPoint mScanStartPoint
;
1416 BoundaryData mStart
;
1418 NoBreakingSpaceData mNBSPData
;
1419 RefPtr
<const Element
> mEditingHost
;
1420 mutable Maybe
<EditorDOMRange
> mLeadingWhiteSpaceRange
;
1421 mutable Maybe
<EditorDOMRange
> mTrailingWhiteSpaceRange
;
1422 mutable Maybe
<VisibleWhiteSpacesData
> mVisibleWhiteSpacesData
;
1423 BlockInlineCheck mBlockInlineCheck
;
1426 const TextFragmentData
& TextFragmentDataAtStartRef() const {
1427 return mTextFragmentDataAtStart
;
1430 // The node passed to our constructor.
1431 EditorDOMPoint mScanStartPoint
;
1432 // Together, the above represent the point at which we are building up ws
1435 // The editing host when the instance is created.
1436 RefPtr
<Element
> mEditingHost
;
1440 * ComputeRangeInTextNodesContainingInvisibleWhiteSpaces() returns range
1441 * containing invisible white-spaces if deleting between aStart and aEnd
1442 * causes them become visible.
1444 * @param aStart TextFragmentData at start of deleting range.
1445 * This must be initialized with DOM point in a text node.
1446 * @param aEnd TextFragmentData at end of deleting range.
1447 * This must be initialized with DOM point in a text node.
1449 static EditorDOMRangeInTexts
1450 ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
1451 const TextFragmentData
& aStart
, const TextFragmentData
& aEnd
);
1453 TextFragmentData mTextFragmentDataAtStart
;
1455 const BlockInlineCheck mBlockInlineCheck
;
1457 friend class WhiteSpaceVisibilityKeeper
;
1461 * WhiteSpaceVisibilityKeeper class helps `HTMLEditor` modifying the DOM tree
1462 * with keeps white-space sequence visibility automatically. E.g., invisible
1463 * leading/trailing white-spaces becomes visible, this class members delete
1464 * them. E.g., when splitting visible-white-space sequence, this class may
1465 * replace ASCII white-spaces at split edges with NBSPs.
1467 class WhiteSpaceVisibilityKeeper final
{
1469 using AutoTransactionsConserveSelection
=
1470 EditorBase::AutoTransactionsConserveSelection
;
1471 using EditorType
= EditorBase::EditorType
;
1472 using PointPosition
= WSRunScanner::PointPosition
;
1473 using TextFragmentData
= WSRunScanner::TextFragmentData
;
1474 using VisibleWhiteSpacesData
= WSRunScanner::VisibleWhiteSpacesData
;
1477 using InsertTextTo
= EditorBase::InsertTextTo
;
1479 WhiteSpaceVisibilityKeeper() = delete;
1480 explicit WhiteSpaceVisibilityKeeper(
1481 const WhiteSpaceVisibilityKeeper
& aOther
) = delete;
1482 WhiteSpaceVisibilityKeeper(WhiteSpaceVisibilityKeeper
&& aOther
) = delete;
1485 * Remove invisible leading white-spaces and trailing white-spaces if there
1486 * are around aPoint.
1488 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
1489 DeleteInvisibleASCIIWhiteSpaces(HTMLEditor
& aHTMLEditor
,
1490 const EditorDOMPoint
& aPoint
);
1493 * Fix up white-spaces before aStartPoint and after aEndPoint in preparation
1494 * for content to keep the white-spaces visibility after the range is deleted.
1495 * Note that the nodes and offsets are adjusted in response to any dom changes
1496 * we make while adjusting white-spaces.
1498 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
1499 PrepareToDeleteRangeAndTrackPoints(HTMLEditor
& aHTMLEditor
,
1500 EditorDOMPoint
* aStartPoint
,
1501 EditorDOMPoint
* aEndPoint
,
1502 const Element
& aEditingHost
) {
1503 MOZ_ASSERT(aStartPoint
->IsSetAndValid());
1504 MOZ_ASSERT(aEndPoint
->IsSetAndValid());
1505 AutoTrackDOMPoint
trackerStart(aHTMLEditor
.RangeUpdaterRef(), aStartPoint
);
1506 AutoTrackDOMPoint
trackerEnd(aHTMLEditor
.RangeUpdaterRef(), aEndPoint
);
1507 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1508 WhiteSpaceVisibilityKeeper::PrepareToDeleteRange(
1509 aHTMLEditor
, EditorDOMRange(*aStartPoint
, *aEndPoint
),
1511 NS_WARNING_ASSERTION(
1512 caretPointOrError
.isOk(),
1513 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() failed");
1514 return caretPointOrError
;
1516 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
1517 PrepareToDeleteRange(HTMLEditor
& aHTMLEditor
,
1518 const EditorDOMPoint
& aStartPoint
,
1519 const EditorDOMPoint
& aEndPoint
,
1520 const Element
& aEditingHost
) {
1521 MOZ_ASSERT(aStartPoint
.IsSetAndValid());
1522 MOZ_ASSERT(aEndPoint
.IsSetAndValid());
1523 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1524 WhiteSpaceVisibilityKeeper::PrepareToDeleteRange(
1525 aHTMLEditor
, EditorDOMRange(aStartPoint
, aEndPoint
), aEditingHost
);
1526 NS_WARNING_ASSERTION(
1527 caretPointOrError
.isOk(),
1528 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() failed");
1529 return caretPointOrError
;
1531 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
1532 PrepareToDeleteRange(HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRange
,
1533 const Element
& aEditingHost
) {
1534 MOZ_ASSERT(aRange
.IsPositionedAndValid());
1535 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1536 WhiteSpaceVisibilityKeeper::
1537 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
1538 aHTMLEditor
, aRange
, aEditingHost
);
1539 NS_WARNING_ASSERTION(
1540 caretPointOrError
.isOk(),
1541 "WhiteSpaceVisibilityKeeper::"
1542 "MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() failed");
1543 return caretPointOrError
;
1547 * PrepareToSplitBlockElement() makes sure that the invisible white-spaces
1548 * not to become visible and returns splittable point.
1550 * @param aHTMLEditor The HTML editor.
1551 * @param aPointToSplit The splitting point in aSplittingBlockElement.
1552 * @param aSplittingBlockElement A block element which will be split.
1554 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<EditorDOMPoint
, nsresult
>
1555 PrepareToSplitBlockElement(HTMLEditor
& aHTMLEditor
,
1556 const EditorDOMPoint
& aPointToSplit
,
1557 const Element
& aSplittingBlockElement
);
1560 * MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement() merges
1561 * first line in aRightBlockElement into end of aLeftBlockElement which
1562 * is a descendant of aRightBlockElement.
1564 * @param aHTMLEditor The HTML editor.
1565 * @param aLeftBlockElement The content will be merged into end of
1567 * @param aRightBlockElement The first line in this element will be
1568 * moved to aLeftBlockElement.
1569 * @param aAtRightBlockChild At a child of aRightBlockElement and inclusive
1570 * ancestor of aLeftBlockElement.
1571 * @param aListElementTagName Set some if aRightBlockElement is a list
1572 * element and it'll be merged with another
1574 * @param aEditingHost The editing host.
1576 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<EditActionResult
, nsresult
>
1577 MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
1578 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
1579 Element
& aRightBlockElement
, const EditorDOMPoint
& aAtRightBlockChild
,
1580 const Maybe
<nsAtom
*>& aListElementTagName
,
1581 const HTMLBRElement
* aPrecedingInvisibleBRElement
,
1582 const Element
& aEditingHost
);
1585 * MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement() merges
1586 * first line in aRightBlockElement into end of aLeftBlockElement which
1587 * is an ancestor of aRightBlockElement, then, removes aRightBlockElement
1588 * if it becomes empty.
1590 * @param aHTMLEditor The HTML editor.
1591 * @param aLeftBlockElement The content will be merged into end of
1593 * @param aRightBlockElement The first line in this element will be
1594 * moved to aLeftBlockElement and maybe
1595 * removed when this becomes empty.
1596 * @param aAtLeftBlockChild At a child of aLeftBlockElement and inclusive
1597 * ancestor of aRightBlockElement.
1598 * @param aLeftContentInBlock The content whose inclusive ancestor is
1599 * aLeftBlockElement.
1600 * @param aListElementTagName Set some if aRightBlockElement is a list
1601 * element and it'll be merged with another
1603 * @param aEditingHost The editing host.
1605 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<EditActionResult
, nsresult
>
1606 MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
1607 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
1608 Element
& aRightBlockElement
, const EditorDOMPoint
& aAtLeftBlockChild
,
1609 nsIContent
& aLeftContentInBlock
,
1610 const Maybe
<nsAtom
*>& aListElementTagName
,
1611 const HTMLBRElement
* aPrecedingInvisibleBRElement
,
1612 const Element
& aEditingHost
);
1615 * MergeFirstLineOfRightBlockElementIntoLeftBlockElement() merges first
1616 * line in aRightBlockElement into end of aLeftBlockElement and removes
1617 * aRightBlockElement when it has only one line.
1619 * @param aHTMLEditor The HTML editor.
1620 * @param aLeftBlockElement The content will be merged into end of
1622 * @param aRightBlockElement The first line in this element will be
1623 * moved to aLeftBlockElement and maybe
1624 * removed when this becomes empty.
1625 * @param aListElementTagName Set some if aRightBlockElement is a list
1626 * element and its type needs to be changed.
1627 * @param aEditingHost The editing host.
1629 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<EditActionResult
, nsresult
>
1630 MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
1631 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
1632 Element
& aRightBlockElement
, const Maybe
<nsAtom
*>& aListElementTagName
,
1633 const HTMLBRElement
* aPrecedingInvisibleBRElement
,
1634 const Element
& aEditingHost
);
1637 * InsertBRElement() inserts a <br> node at (before) aPointToInsert and delete
1638 * unnecessary white-spaces around there and/or replaces white-spaces with
1639 * non-breaking spaces. Note that if the point is in a text node, the
1640 * text node will be split and insert new <br> node between the left node
1641 * and the right node.
1643 * @param aPointToInsert The point to insert new <br> element. Note that
1644 * it'll be inserted before this point. I.e., the
1645 * point will be the point of new <br>.
1646 * @return If succeeded, returns the new <br> element and
1647 * point to put caret.
1649 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CreateElementResult
, nsresult
>
1650 InsertBRElement(HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToInsert
,
1651 const Element
& aEditingHost
);
1654 * Insert aStringToInsert to aPointToInsert and makes any needed adjustments
1655 * to white-spaces around the insertion point.
1657 * @param aStringToInsert The string to insert.
1658 * @param aRangeToBeReplaced The range to be replaced.
1659 * @param aInsertTextTo Whether forcibly creates a new `Text` node in
1660 * specific condition or use existing `Text` if
1663 template <typename EditorDOMPointType
>
1664 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<InsertTextResult
, nsresult
>
1665 InsertText(HTMLEditor
& aHTMLEditor
, const nsAString
& aStringToInsert
,
1666 const EditorDOMPointType
& aPointToInsert
,
1667 InsertTextTo aInsertTextTo
, const Element
& aEditingHost
) {
1668 return WhiteSpaceVisibilityKeeper::ReplaceText(
1669 aHTMLEditor
, aStringToInsert
, EditorDOMRange(aPointToInsert
),
1670 aInsertTextTo
, aEditingHost
);
1674 * Replace aRangeToReplace with aStringToInsert and makes any needed
1675 * adjustments to white-spaces around both start of the range and end of the
1678 * @param aStringToInsert The string to insert.
1679 * @param aRangeToBeReplaced The range to be replaced.
1680 * @param aInsertTextTo Whether forcibly creates a new `Text` node in
1681 * specific condition or use existing `Text` if
1684 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<InsertTextResult
, nsresult
>
1685 ReplaceText(HTMLEditor
& aHTMLEditor
, const nsAString
& aStringToInsert
,
1686 const EditorDOMRange
& aRangeToBeReplaced
,
1687 InsertTextTo aInsertTextTo
, const Element
& aEditingHost
);
1690 * Delete previous white-space of aPoint. This automatically keeps visibility
1691 * of white-spaces around aPoint. E.g., may remove invisible leading
1694 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
1695 DeletePreviousWhiteSpace(HTMLEditor
& aHTMLEditor
,
1696 const EditorDOMPoint
& aPoint
,
1697 const Element
& aEditingHost
);
1700 * Delete inclusive next white-space of aPoint. This automatically keeps
1701 * visiblity of white-spaces around aPoint. E.g., may remove invisible
1702 * trailing white-spaces.
1704 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
1705 DeleteInclusiveNextWhiteSpace(HTMLEditor
& aHTMLEditor
,
1706 const EditorDOMPoint
& aPoint
,
1707 const Element
& aEditingHost
);
1710 * Delete aContentToDelete and may remove/replace white-spaces around it.
1711 * Then, if deleting content makes 2 text nodes around it are adjacent
1712 * siblings, this joins them and put selection at the joined point.
1714 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
1715 DeleteContentNodeAndJoinTextNodesAroundIt(HTMLEditor
& aHTMLEditor
,
1716 nsIContent
& aContentToDelete
,
1717 const EditorDOMPoint
& aCaretPoint
,
1718 const Element
& aEditingHost
);
1721 * Try to normalize visible white-space sequence around aPoint.
1722 * This may collapse `Selection` after replaced text. Therefore, the callers
1723 * of this need to restore `Selection` by themselves (this does not do it for
1724 * performance reason of multiple calls).
1726 template <typename EditorDOMPointType
>
1727 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
1728 NormalizeVisibleWhiteSpacesAt(HTMLEditor
& aHTMLEditor
,
1729 const EditorDOMPointType
& aPoint
);
1733 * Maybe delete invisible white-spaces for keeping make them invisible and/or
1734 * may replace ASCII white-spaces with NBSPs for making visible white-spaces
1737 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static Result
<CaretPoint
, nsresult
>
1738 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
1739 HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRangeToDelete
,
1740 const Element
& aEditingHost
);
1743 * MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit() replaces ASCII white-
1744 * spaces which becomes invisible after split with NBSPs.
1746 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
1747 MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
1748 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToSplit
);
1751 * ReplaceTextAndRemoveEmptyTextNodes() replaces the range between
1752 * aRangeToReplace with aReplaceString simply. Additionally, removes
1753 * empty text nodes in the range.
1755 * @param aRangeToReplace Range to replace text.
1756 * @param aReplaceString The new string. Empty string is allowed.
1758 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT
static nsresult
1759 ReplaceTextAndRemoveEmptyTextNodes(
1760 HTMLEditor
& aHTMLEditor
, const EditorDOMRangeInTexts
& aRangeToReplace
,
1761 const nsAString
& aReplaceString
);
1764 } // namespace mozilla
1766 #endif // #ifndef WSRunObject_h