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