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/. */ 5 6 #ifndef WSRunObject_h 7 #define WSRunObject_h 8 9 #include "HTMLEditUtils.h" 10 #include "mozilla/Assertions.h" 11 #include "mozilla/EditAction.h" 12 #include "mozilla/EditorBase.h" 13 #include "mozilla/EditorDOMPoint.h" // for EditorDOMPoint 14 #include "mozilla/HTMLEditor.h" 15 #include "mozilla/Maybe.h" 16 #include "mozilla/Result.h" 17 #include "mozilla/dom/Element.h" 18 #include "mozilla/dom/HTMLBRElement.h" 19 #include "mozilla/dom/Text.h" 20 #include "nsCOMPtr.h" 21 #include "nsIContent.h" 22 23 namespace mozilla { 24 25 namespace dom { 26 class Element; 27 } 28 29 class WSRunScanner; 30 31 /** 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 36 * start of scanner. 37 */ 38 class MOZ_STACK_CLASS WSScanResult final { 39 private: 40 enum class WSType : uint8_t { 41 NotInitialized, 42 // Could be the DOM tree is broken as like crash tests. 43 UnexpectedError, 44 // The run is maybe collapsible white-spaces at start of a hard line. 45 LeadingWhiteSpaces, 46 // The run is maybe collapsible white-spaces at end of a hard line. 47 TrailingWhiteSpaces, 48 // Normal (perhaps, meaning visible) white-spaces. 49 NormalWhiteSpaces, 50 // Normal text, not white-spaces. 51 NormalText, 52 // Special content such as `<img>`, etc. 53 SpecialContent, 54 // <br> element. 55 BRElement, 56 // Other block's boundary (child block of current block, maybe). 57 OtherBlockBoundary, 58 // Current block's boundary. 59 CurrentBlockBoundary, 60 }; 61 62 friend class WSRunScanner; // Because of WSType. 63 64 public: 65 WSScanResult() = delete; WSScanResult(nsIContent * aContent,WSType aReason)66 MOZ_NEVER_INLINE_DEBUG WSScanResult(nsIContent* aContent, WSType aReason) 67 : mContent(aContent), mReason(aReason) { 68 AssertIfInvalidData(); 69 } WSScanResult(const EditorDOMPoint & aPoint,WSType aReason)70 MOZ_NEVER_INLINE_DEBUG WSScanResult(const EditorDOMPoint& aPoint, 71 WSType aReason) 72 : mContent(aPoint.GetContainerAsContent()), 73 mOffset(Some(aPoint.Offset())), 74 mReason(aReason) { 75 AssertIfInvalidData(); 76 } 77 AssertIfInvalidData()78 MOZ_NEVER_INLINE_DEBUG void AssertIfInvalidData() const { 79 #ifdef DEBUG 80 MOZ_ASSERT( 81 mReason == WSType::UnexpectedError || mReason == WSType::NormalText || 82 mReason == WSType::NormalWhiteSpaces || mReason == WSType::BRElement || 83 mReason == WSType::SpecialContent || 84 mReason == WSType::CurrentBlockBoundary || 85 mReason == WSType::OtherBlockBoundary); 86 MOZ_ASSERT_IF(mReason == WSType::UnexpectedError, !mContent); 87 MOZ_ASSERT_IF( 88 mReason == WSType::NormalText || mReason == WSType::NormalWhiteSpaces, 89 mContent && mContent->IsText()); 90 MOZ_ASSERT_IF(mReason == WSType::BRElement, 91 mContent && mContent->IsHTMLElement(nsGkAtoms::br)); 92 MOZ_ASSERT_IF( 93 mReason == WSType::SpecialContent, 94 mContent && ((mContent->IsText() && !mContent->IsEditable()) || 95 (!mContent->IsHTMLElement(nsGkAtoms::br) && 96 !HTMLEditUtils::IsBlockElement(*mContent)))); 97 MOZ_ASSERT_IF(mReason == WSType::OtherBlockBoundary, 98 mContent && HTMLEditUtils::IsBlockElement(*mContent)); 99 // If mReason is WSType::CurrentBlockBoundary, mContent can be any content. 100 // In most cases, it's current block element which is editable. However, if 101 // there is no editable block parent, this is topmost editable inline 102 // content. Additionally, if there is no editable content, this is the 103 // container start of scanner and is not editable. 104 MOZ_ASSERT_IF( 105 mReason == WSType::CurrentBlockBoundary, 106 !mContent || !mContent->GetParentElement() || 107 HTMLEditUtils::IsBlockElement(*mContent) || 108 HTMLEditUtils::IsBlockElement(*mContent->GetParentElement()) || 109 !mContent->GetParentElement()->IsEditable()); 110 #endif // #ifdef DEBUG 111 } 112 Failed()113 bool Failed() const { 114 return mReason == WSType::NotInitialized || 115 mReason == WSType::UnexpectedError; 116 } 117 118 /** 119 * GetContent() returns found visible and editable content/element. 120 * See MOZ_ASSERT_IF()s in AssertIfInvalidData() for the detail. 121 */ GetContent()122 nsIContent* GetContent() const { return mContent; } 123 124 /** 125 * The following accessors makes it easier to understand each callers. 126 */ ElementPtr()127 MOZ_NEVER_INLINE_DEBUG dom::Element* ElementPtr() const { 128 MOZ_DIAGNOSTIC_ASSERT(mContent->IsElement()); 129 return mContent->AsElement(); 130 } BRElementPtr()131 MOZ_NEVER_INLINE_DEBUG dom::HTMLBRElement* BRElementPtr() const { 132 MOZ_DIAGNOSTIC_ASSERT(mContent->IsHTMLElement(nsGkAtoms::br)); 133 return static_cast<dom::HTMLBRElement*>(mContent.get()); 134 } TextPtr()135 MOZ_NEVER_INLINE_DEBUG dom::Text* TextPtr() const { 136 MOZ_DIAGNOSTIC_ASSERT(mContent->IsText()); 137 return mContent->AsText(); 138 } 139 140 /** 141 * Returns true if found or reached content is ediable. 142 */ IsContentEditable()143 bool IsContentEditable() const { return mContent && mContent->IsEditable(); } 144 145 /** 146 * Offset() returns meaningful value only when InNormalWhiteSpacesOrText() 147 * returns true or the scanner reached to start or end of its scanning 148 * range and that is same as start or end container which are specified 149 * when the scanner is initialized. If it's result of scanning backward, 150 * this offset means before the found point. Otherwise, i.e., scanning 151 * forward, this offset means after the found point. 152 */ Offset()153 MOZ_NEVER_INLINE_DEBUG uint32_t Offset() const { 154 NS_ASSERTION(mOffset.isSome(), "Retrieved non-meaningful offset"); 155 return mOffset.valueOr(0); 156 } 157 158 /** 159 * Point() and RawPoint() return the position in found visible node or 160 * reached block boundary. So, they return meaningful point only when 161 * Offset() returns meaningful value. 162 */ Point()163 MOZ_NEVER_INLINE_DEBUG EditorDOMPoint Point() const { 164 NS_ASSERTION(mOffset.isSome(), "Retrieved non-meaningful point"); 165 return EditorDOMPoint(mContent, mOffset.valueOr(0)); 166 } RawPoint()167 MOZ_NEVER_INLINE_DEBUG EditorRawDOMPoint RawPoint() const { 168 NS_ASSERTION(mOffset.isSome(), "Retrieved non-meaningful raw point"); 169 return EditorRawDOMPoint(mContent, mOffset.valueOr(0)); 170 } 171 172 /** 173 * PointAtContent() and RawPointAtContent() return the position of found 174 * visible content or reached block element. 175 */ PointAtContent()176 MOZ_NEVER_INLINE_DEBUG EditorDOMPoint PointAtContent() const { 177 MOZ_ASSERT(mContent); 178 return EditorDOMPoint(mContent); 179 } RawPointAtContent()180 MOZ_NEVER_INLINE_DEBUG EditorRawDOMPoint RawPointAtContent() const { 181 MOZ_ASSERT(mContent); 182 return EditorRawDOMPoint(mContent); 183 } 184 185 /** 186 * PointAfterContent() and RawPointAfterContent() retrun the position after 187 * found visible content or reached block element. 188 */ PointAfterContent()189 MOZ_NEVER_INLINE_DEBUG EditorDOMPoint PointAfterContent() const { 190 MOZ_ASSERT(mContent); 191 return mContent ? EditorDOMPoint::After(mContent) : EditorDOMPoint(); 192 } RawPointAfterContent()193 MOZ_NEVER_INLINE_DEBUG EditorRawDOMPoint RawPointAfterContent() const { 194 MOZ_ASSERT(mContent); 195 return mContent ? EditorRawDOMPoint::After(mContent) : EditorRawDOMPoint(); 196 } 197 198 /** 199 * The scanner reached <img> or something which is inline and is not a 200 * container. 201 */ ReachedSpecialContent()202 bool ReachedSpecialContent() const { 203 return mReason == WSType::SpecialContent; 204 } 205 206 /** 207 * The point is in normal white-spaces or text. 208 */ InNormalWhiteSpacesOrText()209 bool InNormalWhiteSpacesOrText() const { 210 return mReason == WSType::NormalWhiteSpaces || 211 mReason == WSType::NormalText; 212 } 213 214 /** 215 * The point is in normal white-spaces. 216 */ InNormalWhiteSpaces()217 bool InNormalWhiteSpaces() const { 218 return mReason == WSType::NormalWhiteSpaces; 219 } 220 221 /** 222 * The point is in normal text. 223 */ InNormalText()224 bool InNormalText() const { return mReason == WSType::NormalText; } 225 226 /** 227 * The scanner reached a <br> element. 228 */ ReachedBRElement()229 bool ReachedBRElement() const { return mReason == WSType::BRElement; } ReachedVisibleBRElement()230 bool ReachedVisibleBRElement() const { 231 return ReachedBRElement() && 232 HTMLEditUtils::IsVisibleBRElement(*BRElementPtr()); 233 } ReachedInvisibleBRElement()234 bool ReachedInvisibleBRElement() const { 235 return ReachedBRElement() && 236 HTMLEditUtils::IsInvisibleBRElement(*BRElementPtr()); 237 } 238 239 /** 240 * The scanner reached a <hr> element. 241 */ ReachedHRElement()242 bool ReachedHRElement() const { 243 return mContent && mContent->IsHTMLElement(nsGkAtoms::hr); 244 } 245 246 /** 247 * The scanner reached current block boundary or other block element. 248 */ ReachedBlockBoundary()249 bool ReachedBlockBoundary() const { 250 return mReason == WSType::CurrentBlockBoundary || 251 mReason == WSType::OtherBlockBoundary; 252 } 253 254 /** 255 * The scanner reached current block element boundary. 256 */ ReachedCurrentBlockBoundary()257 bool ReachedCurrentBlockBoundary() const { 258 return mReason == WSType::CurrentBlockBoundary; 259 } 260 261 /** 262 * The scanner reached other block element. 263 */ ReachedOtherBlockElement()264 bool ReachedOtherBlockElement() const { 265 return mReason == WSType::OtherBlockBoundary; 266 } 267 268 /** 269 * The scanner reached other block element that isn't editable 270 */ ReachedNonEditableOtherBlockElement()271 bool ReachedNonEditableOtherBlockElement() const { 272 return ReachedOtherBlockElement() && !GetContent()->IsEditable(); 273 } 274 275 /** 276 * The scanner reached something non-text node. 277 */ ReachedSomething()278 bool ReachedSomething() const { return !InNormalWhiteSpacesOrText(); } 279 280 private: 281 nsCOMPtr<nsIContent> mContent; 282 Maybe<uint32_t> mOffset; 283 WSType mReason; 284 }; 285 286 class WhiteSpaceVisibilityKeeper; 287 288 class MOZ_STACK_CLASS WSRunScanner final { 289 public: 290 using WSType = WSScanResult::WSType; 291 292 template <typename EditorDOMPointType> WSRunScanner(const dom::Element * aEditingHost,const EditorDOMPointType & aScanStartPoint)293 WSRunScanner(const dom::Element* aEditingHost, 294 const EditorDOMPointType& aScanStartPoint) 295 : mScanStartPoint(aScanStartPoint), 296 mEditingHost(const_cast<dom::Element*>(aEditingHost)), 297 mTextFragmentDataAtStart(mScanStartPoint, mEditingHost) {} 298 299 // ScanNextVisibleNodeOrBlockBoundaryForwardFrom() returns the first visible 300 // node after aPoint. If there is no visible nodes after aPoint, returns 301 // topmost editable inline ancestor at end of current block. See comments 302 // around WSScanResult for the detail. 303 template <typename PT, typename CT> 304 WSScanResult ScanNextVisibleNodeOrBlockBoundaryFrom( 305 const EditorDOMPointBase<PT, CT>& aPoint) const; 306 template <typename PT, typename CT> ScanNextVisibleNodeOrBlockBoundary(const dom::Element * aEditingHost,const EditorDOMPointBase<PT,CT> & aPoint)307 static WSScanResult ScanNextVisibleNodeOrBlockBoundary( 308 const dom::Element* aEditingHost, 309 const EditorDOMPointBase<PT, CT>& aPoint) { 310 return WSRunScanner(aEditingHost, aPoint) 311 .ScanNextVisibleNodeOrBlockBoundaryFrom(aPoint); 312 } 313 314 // ScanPreviousVisibleNodeOrBlockBoundaryFrom() returns the first visible node 315 // before aPoint. If there is no visible nodes before aPoint, returns topmost 316 // editable inline ancestor at start of current block. See comments around 317 // WSScanResult for the detail. 318 template <typename PT, typename CT> 319 WSScanResult ScanPreviousVisibleNodeOrBlockBoundaryFrom( 320 const EditorDOMPointBase<PT, CT>& aPoint) const; 321 template <typename PT, typename CT> ScanPreviousVisibleNodeOrBlockBoundary(const dom::Element * aEditingHost,const EditorDOMPointBase<PT,CT> & aPoint)322 static WSScanResult ScanPreviousVisibleNodeOrBlockBoundary( 323 const dom::Element* aEditingHost, 324 const EditorDOMPointBase<PT, CT>& aPoint) { 325 return WSRunScanner(aEditingHost, aPoint) 326 .ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPoint); 327 } 328 329 /** 330 * GetInclusiveNextEditableCharPoint() returns a point in a text node which 331 * is at current editable character or next editable character if aPoint 332 * does not points an editable character. 333 */ 334 template <typename PT, typename CT> GetInclusiveNextEditableCharPoint(dom::Element * aEditingHost,const EditorDOMPointBase<PT,CT> & aPoint)335 static EditorDOMPointInText GetInclusiveNextEditableCharPoint( 336 dom::Element* aEditingHost, const EditorDOMPointBase<PT, CT>& aPoint) { 337 if (aPoint.IsInTextNode() && !aPoint.IsEndOfContainer() && 338 HTMLEditUtils::IsSimplyEditableNode(*aPoint.ContainerAsText())) { 339 return EditorDOMPointInText(aPoint.ContainerAsText(), aPoint.Offset()); 340 } 341 return WSRunScanner(aEditingHost, aPoint) 342 .GetInclusiveNextEditableCharPoint(aPoint); 343 } 344 345 /** 346 * GetPreviousEditableCharPoint() returns a point in a text node which 347 * is at previous editable character. 348 */ 349 template <typename PT, typename CT> GetPreviousEditableCharPoint(dom::Element * aEditingHost,const EditorDOMPointBase<PT,CT> & aPoint)350 static EditorDOMPointInText GetPreviousEditableCharPoint( 351 dom::Element* aEditingHost, const EditorDOMPointBase<PT, CT>& aPoint) { 352 if (aPoint.IsInTextNode() && !aPoint.IsStartOfContainer() && 353 HTMLEditUtils::IsSimplyEditableNode(*aPoint.ContainerAsText())) { 354 return EditorDOMPointInText(aPoint.ContainerAsText(), 355 aPoint.Offset() - 1); 356 } 357 return WSRunScanner(aEditingHost, aPoint) 358 .GetPreviousEditableCharPoint(aPoint); 359 } 360 361 /** 362 * Scan aTextNode from end or start to find last or first visible things. 363 * I.e., this returns a point immediately before or after invisible 364 * white-spaces of aTextNode if aTextNode ends or begins with some invisible 365 * white-spaces. 366 * Note that the result may not be in different text node if aTextNode has 367 * only invisible white-spaces and there is previous or next text node. 368 */ 369 template <typename EditorDOMPointType> 370 static EditorDOMPointType GetAfterLastVisiblePoint( 371 dom::Text& aTextNode, const dom::Element* aAncestorLimiter); 372 template <typename EditorDOMPointType> 373 static EditorDOMPointType GetFirstVisiblePoint( 374 dom::Text& aTextNode, const dom::Element* aAncestorLimiter); 375 376 /** 377 * GetRangeInTextNodesToForwardDeleteFrom() returns the range to remove 378 * text when caret is at aPoint. 379 */ 380 static Result<EditorDOMRangeInTexts, nsresult> 381 GetRangeInTextNodesToForwardDeleteFrom(dom::Element* aEditingHost, 382 const EditorDOMPoint& aPoint); 383 384 /** 385 * GetRangeInTextNodesToBackspaceFrom() returns the range to remove text 386 * when caret is at aPoint. 387 */ 388 static Result<EditorDOMRangeInTexts, nsresult> 389 GetRangeInTextNodesToBackspaceFrom(dom::Element* aEditingHost, 390 const EditorDOMPoint& aPoint); 391 392 /** 393 * GetRangesForDeletingAtomicContent() returns the range to delete 394 * aAtomicContent. If it's followed by invisible white-spaces, they will 395 * be included into the range. 396 */ 397 static EditorDOMRange GetRangesForDeletingAtomicContent( 398 dom::Element* aEditingHost, const nsIContent& aAtomicContent); 399 400 /** 401 * GetRangeForDeleteBlockElementBoundaries() returns a range starting from end 402 * of aLeftBlockElement to start of aRightBlockElement and extend invisible 403 * white-spaces around them. 404 * 405 * @param aHTMLEditor The HTML editor. 406 * @param aLeftBlockElement The block element which will be joined with 407 * aRightBlockElement. 408 * @param aRightBlockElement The block element which will be joined with 409 * aLeftBlockElement. This must be an element 410 * after aLeftBlockElement. 411 * @param aPointContainingTheOtherBlock 412 * When aRightBlockElement is an ancestor of 413 * aLeftBlockElement, this must be set and the 414 * container must be aRightBlockElement. 415 * When aLeftBlockElement is an ancestor of 416 * aRightBlockElement, this must be set and the 417 * container must be aLeftBlockElement. 418 * Otherwise, must not be set. 419 */ 420 static EditorDOMRange GetRangeForDeletingBlockElementBoundaries( 421 const HTMLEditor& aHTMLEditor, const dom::Element& aLeftBlockElement, 422 const dom::Element& aRightBlockElement, 423 const EditorDOMPoint& aPointContainingTheOtherBlock); 424 425 /** 426 * ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() may shrink aRange if it 427 * starts and/or ends with an atomic content, but the range boundary 428 * is in adjacent text nodes. Returns true if this modifies the range. 429 */ 430 static Result<bool, nsresult> ShrinkRangeIfStartsFromOrEndsAfterAtomicContent( 431 const HTMLEditor& aHTMLEditor, nsRange& aRange, 432 const dom::Element* aEditingHost); 433 434 /** 435 * GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries() returns 436 * extended range if range boundaries of aRange are in invisible white-spaces. 437 */ 438 static EditorDOMRange GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries( 439 dom::Element* aEditingHost, const EditorDOMRange& aRange); 440 441 /** 442 * GetPrecedingBRElementUnlessVisibleContentFound() scans a `<br>` element 443 * backward, but stops scanning it if the scanner finds visible character 444 * or something. In other words, this method ignores only invisible 445 * white-spaces between `<br>` element and aPoint. 446 */ 447 template <typename EditorDOMPointType> 448 MOZ_NEVER_INLINE_DEBUG static dom::HTMLBRElement* GetPrecedingBRElementUnlessVisibleContentFound(dom::Element * aEditingHost,const EditorDOMPointType & aPoint)449 GetPrecedingBRElementUnlessVisibleContentFound( 450 dom::Element* aEditingHost, const EditorDOMPointType& aPoint) { 451 MOZ_ASSERT(aPoint.IsSetAndValid()); 452 // XXX This method behaves differently even in similar point. 453 // If aPoint is in a text node following `<br>` element, reaches the 454 // `<br>` element when all characters between the `<br>` and 455 // aPoint are ASCII whitespaces. 456 // But if aPoint is not in a text node, e.g., at start of an inline 457 // element which is immediately after a `<br>` element, returns the 458 // `<br>` element even if there is no invisible white-spaces. 459 if (aPoint.IsStartOfContainer()) { 460 return nullptr; 461 } 462 // TODO: Scan for end boundary is redundant in this case, we should optimize 463 // it. 464 TextFragmentData textFragmentData(aPoint, aEditingHost); 465 return textFragmentData.StartsFromBRElement() 466 ? textFragmentData.StartReasonBRElementPtr() 467 : nullptr; 468 } 469 ScanStartRef()470 const EditorDOMPoint& ScanStartRef() const { return mScanStartPoint; } 471 472 /** 473 * GetStartReasonContent() and GetEndReasonContent() return a node which 474 * was found by scanning from mScanStartPoint backward or forward. If there 475 * was white-spaces or text from the point, returns the text node. Otherwise, 476 * returns an element which is explained by the following methods. Note that 477 * when the reason is WSType::CurrentBlockBoundary, In most cases, it's 478 * current block element which is editable, but also may be non-element and/or 479 * non-editable. See MOZ_ASSERT_IF()s in WSScanResult::AssertIfInvalidData() 480 * for the detail. 481 */ GetStartReasonContent()482 nsIContent* GetStartReasonContent() const { 483 return TextFragmentDataAtStartRef().GetStartReasonContent(); 484 } GetEndReasonContent()485 nsIContent* GetEndReasonContent() const { 486 return TextFragmentDataAtStartRef().GetEndReasonContent(); 487 } 488 StartsFromNormalText()489 bool StartsFromNormalText() const { 490 return TextFragmentDataAtStartRef().StartsFromNormalText(); 491 } StartsFromSpecialContent()492 bool StartsFromSpecialContent() const { 493 return TextFragmentDataAtStartRef().StartsFromSpecialContent(); 494 } StartsFromBRElement()495 bool StartsFromBRElement() const { 496 return TextFragmentDataAtStartRef().StartsFromBRElement(); 497 } StartsFromVisibleBRElement()498 bool StartsFromVisibleBRElement() const { 499 return TextFragmentDataAtStartRef().StartsFromVisibleBRElement(); 500 } StartsFromInvisibleBRElement()501 bool StartsFromInvisibleBRElement() const { 502 return TextFragmentDataAtStartRef().StartsFromInvisibleBRElement(); 503 } StartsFromCurrentBlockBoundary()504 bool StartsFromCurrentBlockBoundary() const { 505 return TextFragmentDataAtStartRef().StartsFromCurrentBlockBoundary(); 506 } StartsFromOtherBlockElement()507 bool StartsFromOtherBlockElement() const { 508 return TextFragmentDataAtStartRef().StartsFromOtherBlockElement(); 509 } StartsFromBlockBoundary()510 bool StartsFromBlockBoundary() const { 511 return TextFragmentDataAtStartRef().StartsFromBlockBoundary(); 512 } StartsFromHardLineBreak()513 bool StartsFromHardLineBreak() const { 514 return TextFragmentDataAtStartRef().StartsFromHardLineBreak(); 515 } EndsByNormalText()516 bool EndsByNormalText() const { 517 return TextFragmentDataAtStartRef().EndsByNormalText(); 518 } EndsBySpecialContent()519 bool EndsBySpecialContent() const { 520 return TextFragmentDataAtStartRef().EndsBySpecialContent(); 521 } EndsByBRElement()522 bool EndsByBRElement() const { 523 return TextFragmentDataAtStartRef().EndsByBRElement(); 524 } EndsByVisibleBRElement()525 bool EndsByVisibleBRElement() const { 526 return TextFragmentDataAtStartRef().EndsByVisibleBRElement(); 527 } EndsByInvisibleBRElement()528 bool EndsByInvisibleBRElement() const { 529 return TextFragmentDataAtStartRef().EndsByInvisibleBRElement(); 530 } EndsByCurrentBlockBoundary()531 bool EndsByCurrentBlockBoundary() const { 532 return TextFragmentDataAtStartRef().EndsByCurrentBlockBoundary(); 533 } EndsByOtherBlockElement()534 bool EndsByOtherBlockElement() const { 535 return TextFragmentDataAtStartRef().EndsByOtherBlockElement(); 536 } EndsByBlockBoundary()537 bool EndsByBlockBoundary() const { 538 return TextFragmentDataAtStartRef().EndsByBlockBoundary(); 539 } 540 StartReasonOtherBlockElementPtr()541 MOZ_NEVER_INLINE_DEBUG dom::Element* StartReasonOtherBlockElementPtr() const { 542 return TextFragmentDataAtStartRef().StartReasonOtherBlockElementPtr(); 543 } StartReasonBRElementPtr()544 MOZ_NEVER_INLINE_DEBUG dom::HTMLBRElement* StartReasonBRElementPtr() const { 545 return TextFragmentDataAtStartRef().StartReasonBRElementPtr(); 546 } EndReasonOtherBlockElementPtr()547 MOZ_NEVER_INLINE_DEBUG dom::Element* EndReasonOtherBlockElementPtr() const { 548 return TextFragmentDataAtStartRef().EndReasonOtherBlockElementPtr(); 549 } EndReasonBRElementPtr()550 MOZ_NEVER_INLINE_DEBUG dom::HTMLBRElement* EndReasonBRElementPtr() const { 551 return TextFragmentDataAtStartRef().EndReasonBRElementPtr(); 552 } 553 554 /** 555 * Active editing host when this instance is created. 556 */ GetEditingHost()557 dom::Element* GetEditingHost() const { return mEditingHost; } 558 559 protected: 560 using EditorType = EditorBase::EditorType; 561 562 class TextFragmentData; 563 564 // VisibleWhiteSpacesData represents 0 or more visible white-spaces. 565 class MOZ_STACK_CLASS VisibleWhiteSpacesData final { 566 public: IsInitialized()567 bool IsInitialized() const { 568 return mLeftWSType != WSType::NotInitialized || 569 mRightWSType != WSType::NotInitialized; 570 } 571 StartRef()572 EditorDOMPoint StartRef() const { return mStartPoint; } EndRef()573 EditorDOMPoint EndRef() const { return mEndPoint; } 574 575 /** 576 * Information why the white-spaces start from (i.e., this indicates the 577 * previous content type of the fragment). 578 */ StartsFromNormalText()579 bool StartsFromNormalText() const { 580 return mLeftWSType == WSType::NormalText; 581 } StartsFromSpecialContent()582 bool StartsFromSpecialContent() const { 583 return mLeftWSType == WSType::SpecialContent; 584 } 585 586 /** 587 * Information why the white-spaces end by (i.e., this indicates the 588 * next content type of the fragment). 589 */ EndsByNormalText()590 bool EndsByNormalText() const { return mRightWSType == WSType::NormalText; } EndsByTrailingWhiteSpaces()591 bool EndsByTrailingWhiteSpaces() const { 592 return mRightWSType == WSType::TrailingWhiteSpaces; 593 } EndsBySpecialContent()594 bool EndsBySpecialContent() const { 595 return mRightWSType == WSType::SpecialContent; 596 } EndsByBRElement()597 bool EndsByBRElement() const { return mRightWSType == WSType::BRElement; } EndsByBlockBoundary()598 bool EndsByBlockBoundary() const { 599 return mRightWSType == WSType::CurrentBlockBoundary || 600 mRightWSType == WSType::OtherBlockBoundary; 601 } 602 603 /** 604 * ComparePoint() compares aPoint with the white-spaces. 605 */ 606 enum class PointPosition { 607 BeforeStartOfFragment, 608 StartOfFragment, 609 MiddleOfFragment, 610 EndOfFragment, 611 AfterEndOfFragment, 612 NotInSameDOMTree, 613 }; 614 template <typename EditorDOMPointType> ComparePoint(const EditorDOMPointType & aPoint)615 PointPosition ComparePoint(const EditorDOMPointType& aPoint) const { 616 MOZ_ASSERT(aPoint.IsSetAndValid()); 617 if (StartRef() == aPoint) { 618 return PointPosition::StartOfFragment; 619 } 620 if (EndRef() == aPoint) { 621 return PointPosition::EndOfFragment; 622 } 623 const bool startIsBeforePoint = StartRef().IsBefore(aPoint); 624 const bool pointIsBeforeEnd = aPoint.IsBefore(EndRef()); 625 if (startIsBeforePoint && pointIsBeforeEnd) { 626 return PointPosition::MiddleOfFragment; 627 } 628 if (startIsBeforePoint) { 629 return PointPosition::AfterEndOfFragment; 630 } 631 if (pointIsBeforeEnd) { 632 return PointPosition::BeforeStartOfFragment; 633 } 634 return PointPosition::NotInSameDOMTree; 635 } 636 637 private: 638 // Initializers should be accessible only from `TextFragmentData`. 639 friend class WSRunScanner::TextFragmentData; VisibleWhiteSpacesData()640 VisibleWhiteSpacesData() 641 : mLeftWSType(WSType::NotInitialized), 642 mRightWSType(WSType::NotInitialized) {} 643 644 template <typename EditorDOMPointType> SetStartPoint(const EditorDOMPointType & aStartPoint)645 void SetStartPoint(const EditorDOMPointType& aStartPoint) { 646 mStartPoint = aStartPoint; 647 } 648 template <typename EditorDOMPointType> SetEndPoint(const EditorDOMPointType & aEndPoint)649 void SetEndPoint(const EditorDOMPointType& aEndPoint) { 650 mEndPoint = aEndPoint; 651 } SetStartFrom(WSType aLeftWSType)652 void SetStartFrom(WSType aLeftWSType) { mLeftWSType = aLeftWSType; } SetStartFromLeadingWhiteSpaces()653 void SetStartFromLeadingWhiteSpaces() { 654 mLeftWSType = WSType::LeadingWhiteSpaces; 655 } SetStartFromNormalWhiteSpaces()656 void SetStartFromNormalWhiteSpaces() { 657 mLeftWSType = WSType::NormalWhiteSpaces; 658 } SetEndBy(WSType aRightWSType)659 void SetEndBy(WSType aRightWSType) { mRightWSType = aRightWSType; } SetEndByNormalWiteSpaces()660 void SetEndByNormalWiteSpaces() { 661 mRightWSType = WSType::NormalWhiteSpaces; 662 } SetEndByTrailingWhiteSpaces()663 void SetEndByTrailingWhiteSpaces() { 664 mRightWSType = WSType::TrailingWhiteSpaces; 665 } 666 667 EditorDOMPoint mStartPoint; 668 EditorDOMPoint mEndPoint; 669 WSType mLeftWSType, mRightWSType; 670 }; 671 672 using PointPosition = VisibleWhiteSpacesData::PointPosition; 673 674 /** 675 * GetInclusiveNextEditableCharPoint() returns aPoint if it points a character 676 * in an editable text node, or start of next editable text node otherwise. 677 * FYI: For the performance, this does not check whether given container 678 * is not after mStart.mReasonContent or not. 679 */ 680 template <typename PT, typename CT> GetInclusiveNextEditableCharPoint(const EditorDOMPointBase<PT,CT> & aPoint)681 EditorDOMPointInText GetInclusiveNextEditableCharPoint( 682 const EditorDOMPointBase<PT, CT>& aPoint) const { 683 return TextFragmentDataAtStartRef().GetInclusiveNextEditableCharPoint( 684 aPoint); 685 } 686 687 /** 688 * GetPreviousEditableCharPoint() returns previous editable point in a 689 * text node. Note that this returns last character point when it meets 690 * non-empty text node, otherwise, returns a point in an empty text node. 691 * FYI: For the performance, this does not check whether given container 692 * is not before mEnd.mReasonContent or not. 693 */ 694 template <typename PT, typename CT> GetPreviousEditableCharPoint(const EditorDOMPointBase<PT,CT> & aPoint)695 EditorDOMPointInText GetPreviousEditableCharPoint( 696 const EditorDOMPointBase<PT, CT>& aPoint) const { 697 return TextFragmentDataAtStartRef().GetPreviousEditableCharPoint(aPoint); 698 } 699 700 /** 701 * GetEndOfCollapsibleASCIIWhiteSpaces() returns the next visible char 702 * (meaning a character except ASCII white-spaces) point or end of last text 703 * node scanning from aPointAtASCIIWhiteSpace. 704 * Note that this may return different text node from the container of 705 * aPointAtASCIIWhiteSpace. 706 */ GetEndOfCollapsibleASCIIWhiteSpaces(const EditorDOMPointInText & aPointAtASCIIWhiteSpace)707 EditorDOMPointInText GetEndOfCollapsibleASCIIWhiteSpaces( 708 const EditorDOMPointInText& aPointAtASCIIWhiteSpace) const { 709 return TextFragmentDataAtStartRef().GetEndOfCollapsibleASCIIWhiteSpaces( 710 aPointAtASCIIWhiteSpace); 711 } 712 713 /** 714 * GetFirstASCIIWhiteSpacePointCollapsedTo() returns the first ASCII 715 * white-space which aPointAtASCIIWhiteSpace belongs to. In other words, 716 * the white-space at aPointAtASCIIWhiteSpace should be collapsed into 717 * the result. 718 * Note that this may return different text node from the container of 719 * aPointAtASCIIWhiteSpace. 720 */ GetFirstASCIIWhiteSpacePointCollapsedTo(const EditorDOMPointInText & aPointAtASCIIWhiteSpace)721 EditorDOMPointInText GetFirstASCIIWhiteSpacePointCollapsedTo( 722 const EditorDOMPointInText& aPointAtASCIIWhiteSpace) const { 723 return TextFragmentDataAtStartRef().GetFirstASCIIWhiteSpacePointCollapsedTo( 724 aPointAtASCIIWhiteSpace); 725 } 726 727 EditorDOMPointInText GetPreviousCharPointFromPointInText( 728 const EditorDOMPointInText& aPoint) const; 729 730 char16_t GetCharAt(dom::Text* aTextNode, uint32_t aOffset) const; 731 732 /** 733 * TextFragmentData stores the information of white-space sequence which 734 * contains `aPoint` of the constructor. 735 */ 736 class MOZ_STACK_CLASS TextFragmentData final { 737 private: 738 class NoBreakingSpaceData; 739 class MOZ_STACK_CLASS BoundaryData final { 740 public: 741 using NoBreakingSpaceData = 742 WSRunScanner::TextFragmentData::NoBreakingSpaceData; 743 744 /** 745 * ScanCollapsibleWhiteSpaceStartFrom() returns start boundary data of 746 * white-spaces containing aPoint. When aPoint is in a text node and 747 * points a non-white-space character or the text node is preformatted, 748 * this returns the data at aPoint. 749 * 750 * @param aPoint Scan start point. 751 * @param aEditableBlockParentOrTopmostEditableInlineElement 752 * Nearest editable block parent element of 753 * aPoint if there is. Otherwise, inline editing 754 * host. 755 * @param aEditingHost Active editing host. 756 * @param aNBSPData Optional. If set, this recodes first and last 757 * NBSP positions. 758 */ 759 template <typename EditorDOMPointType> 760 static BoundaryData ScanCollapsibleWhiteSpaceStartFrom( 761 const EditorDOMPointType& aPoint, 762 const dom::Element& 763 aEditableBlockParentOrTopmostEditableInlineElement, 764 const dom::Element* aEditingHost, NoBreakingSpaceData* aNBSPData); 765 766 /** 767 * ScanCollapsibleWhiteSpaceEndFrom() returns end boundary data of 768 * white-spaces containing aPoint. When aPoint is in a text node and 769 * points a non-white-space character or the text node is preformatted, 770 * this returns the data at aPoint. 771 * 772 * @param aPoint Scan start point. 773 * @param aEditableBlockParentOrTopmostEditableInlineElement 774 * Nearest editable block parent element of 775 * aPoint if there is. Otherwise, inline editing 776 * host. 777 * @param aEditingHost Active editing host. 778 * @param aNBSPData Optional. If set, this recodes first and last 779 * NBSP positions. 780 */ 781 template <typename EditorDOMPointType> 782 static BoundaryData ScanCollapsibleWhiteSpaceEndFrom( 783 const EditorDOMPointType& aPoint, 784 const dom::Element& 785 aEditableBlockParentOrTopmostEditableInlineElement, 786 const dom::Element* aEditingHost, NoBreakingSpaceData* aNBSPData); 787 788 enum class Preformatted : bool { Yes, No }; BoundaryData()789 BoundaryData() 790 : mReason(WSType::NotInitialized), 791 mAcrossPreformattedCharacter(Preformatted::No) {} 792 template <typename EditorDOMPointType> BoundaryData(const EditorDOMPointType & aPoint,nsIContent & aReasonContent,WSType aReason,Preformatted aDidCrossPreformattedCharacter)793 BoundaryData(const EditorDOMPointType& aPoint, nsIContent& aReasonContent, 794 WSType aReason, Preformatted aDidCrossPreformattedCharacter) 795 : mReasonContent(&aReasonContent), 796 mPoint(aPoint), 797 mReason(aReason), 798 mAcrossPreformattedCharacter(aDidCrossPreformattedCharacter) {} Initialized()799 bool Initialized() const { return mReasonContent && mPoint.IsSet(); } 800 GetReasonContent()801 nsIContent* GetReasonContent() const { return mReasonContent; } PointRef()802 const EditorDOMPoint& PointRef() const { return mPoint; } RawReason()803 WSType RawReason() const { return mReason; } AcrossPreformattedCharacter()804 bool AcrossPreformattedCharacter() const { 805 return mAcrossPreformattedCharacter == Preformatted::Yes; 806 } 807 IsNormalText()808 bool IsNormalText() const { return mReason == WSType::NormalText; } IsSpecialContent()809 bool IsSpecialContent() const { 810 return mReason == WSType::SpecialContent; 811 } IsBRElement()812 bool IsBRElement() const { return mReason == WSType::BRElement; } IsCurrentBlockBoundary()813 bool IsCurrentBlockBoundary() const { 814 return mReason == WSType::CurrentBlockBoundary; 815 } IsOtherBlockBoundary()816 bool IsOtherBlockBoundary() const { 817 return mReason == WSType::OtherBlockBoundary; 818 } IsBlockBoundary()819 bool IsBlockBoundary() const { 820 return mReason == WSType::CurrentBlockBoundary || 821 mReason == WSType::OtherBlockBoundary; 822 } IsHardLineBreak()823 bool IsHardLineBreak() const { 824 return mReason == WSType::CurrentBlockBoundary || 825 mReason == WSType::OtherBlockBoundary || 826 mReason == WSType::BRElement; 827 } OtherBlockElementPtr()828 MOZ_NEVER_INLINE_DEBUG dom::Element* OtherBlockElementPtr() const { 829 MOZ_DIAGNOSTIC_ASSERT(mReasonContent->IsElement()); 830 return mReasonContent->AsElement(); 831 } BRElementPtr()832 MOZ_NEVER_INLINE_DEBUG dom::HTMLBRElement* BRElementPtr() const { 833 MOZ_DIAGNOSTIC_ASSERT(mReasonContent->IsHTMLElement(nsGkAtoms::br)); 834 return static_cast<dom::HTMLBRElement*>(mReasonContent.get()); 835 } 836 837 private: 838 /** 839 * Helper methods of ScanCollapsibleWhiteSpaceStartFrom() and 840 * ScanCollapsibleWhiteSpaceEndFrom() when they need to scan in a text 841 * node. 842 */ 843 template <typename EditorDOMPointType> 844 static Maybe<BoundaryData> ScanCollapsibleWhiteSpaceStartInTextNode( 845 const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData); 846 template <typename EditorDOMPointType> 847 static Maybe<BoundaryData> ScanCollapsibleWhiteSpaceEndInTextNode( 848 const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData); 849 850 nsCOMPtr<nsIContent> mReasonContent; 851 EditorDOMPoint mPoint; 852 // Must be one of WSType::NotInitialized, WSType::NormalText, 853 // WSType::SpecialContent, WSType::BRElement, WSType::CurrentBlockBoundary 854 // or WSType::OtherBlockBoundary. 855 WSType mReason; 856 // If the point crosses a preformatted character from scanning start 857 // point, set to "Yes". So, this may NOT equal to the style at mPoint 858 // nor mReasonContent. 859 Preformatted mAcrossPreformattedCharacter; 860 }; 861 862 class MOZ_STACK_CLASS NoBreakingSpaceData final { 863 public: 864 enum class Scanning { Forward, Backward }; NotifyNBSP(const EditorDOMPointInText & aPoint,Scanning aScanningDirection)865 void NotifyNBSP(const EditorDOMPointInText& aPoint, 866 Scanning aScanningDirection) { 867 MOZ_ASSERT(aPoint.IsSetAndValid()); 868 MOZ_ASSERT(aPoint.IsCharNBSP()); 869 if (!mFirst.IsSet() || aScanningDirection == Scanning::Backward) { 870 mFirst = aPoint; 871 } 872 if (!mLast.IsSet() || aScanningDirection == Scanning::Forward) { 873 mLast = aPoint; 874 } 875 } 876 FirstPointRef()877 const EditorDOMPointInText& FirstPointRef() const { return mFirst; } LastPointRef()878 const EditorDOMPointInText& LastPointRef() const { return mLast; } 879 FoundNBSP()880 bool FoundNBSP() const { 881 MOZ_ASSERT(mFirst.IsSet() == mLast.IsSet()); 882 return mFirst.IsSet(); 883 } 884 885 private: 886 EditorDOMPointInText mFirst; 887 EditorDOMPointInText mLast; 888 }; 889 890 public: 891 TextFragmentData() = delete; 892 template <typename EditorDOMPointType> 893 TextFragmentData(const EditorDOMPointType& aPoint, 894 const dom::Element* aEditingHost); 895 IsInitialized()896 bool IsInitialized() const { 897 return mStart.Initialized() && mEnd.Initialized(); 898 } 899 GetStartReasonContent()900 nsIContent* GetStartReasonContent() const { 901 return mStart.GetReasonContent(); 902 } GetEndReasonContent()903 nsIContent* GetEndReasonContent() const { return mEnd.GetReasonContent(); } 904 StartsFromNormalText()905 bool StartsFromNormalText() const { return mStart.IsNormalText(); } StartsFromSpecialContent()906 bool StartsFromSpecialContent() const { return mStart.IsSpecialContent(); } StartsFromBRElement()907 bool StartsFromBRElement() const { return mStart.IsBRElement(); } StartsFromVisibleBRElement()908 bool StartsFromVisibleBRElement() const { 909 return StartsFromBRElement() && 910 HTMLEditUtils::IsVisibleBRElement(*GetStartReasonContent(), 911 mEditingHost); 912 } StartsFromInvisibleBRElement()913 bool StartsFromInvisibleBRElement() const { 914 return StartsFromBRElement() && 915 HTMLEditUtils::IsInvisibleBRElement(*GetStartReasonContent(), 916 mEditingHost); 917 } StartsFromCurrentBlockBoundary()918 bool StartsFromCurrentBlockBoundary() const { 919 return mStart.IsCurrentBlockBoundary(); 920 } StartsFromOtherBlockElement()921 bool StartsFromOtherBlockElement() const { 922 return mStart.IsOtherBlockBoundary(); 923 } StartsFromBlockBoundary()924 bool StartsFromBlockBoundary() const { return mStart.IsBlockBoundary(); } StartsFromHardLineBreak()925 bool StartsFromHardLineBreak() const { return mStart.IsHardLineBreak(); } EndsByNormalText()926 bool EndsByNormalText() const { return mEnd.IsNormalText(); } EndsBySpecialContent()927 bool EndsBySpecialContent() const { return mEnd.IsSpecialContent(); } EndsByBRElement()928 bool EndsByBRElement() const { return mEnd.IsBRElement(); } EndsByVisibleBRElement()929 bool EndsByVisibleBRElement() const { 930 return EndsByBRElement() && HTMLEditUtils::IsVisibleBRElement( 931 *GetEndReasonContent(), mEditingHost); 932 } EndsByInvisibleBRElement()933 bool EndsByInvisibleBRElement() const { 934 return EndsByBRElement() && HTMLEditUtils::IsInvisibleBRElement( 935 *GetEndReasonContent(), mEditingHost); 936 } EndsByCurrentBlockBoundary()937 bool EndsByCurrentBlockBoundary() const { 938 return mEnd.IsCurrentBlockBoundary(); 939 } EndsByOtherBlockElement()940 bool EndsByOtherBlockElement() const { return mEnd.IsOtherBlockBoundary(); } EndsByBlockBoundary()941 bool EndsByBlockBoundary() const { return mEnd.IsBlockBoundary(); } 942 StartRawReason()943 WSType StartRawReason() const { return mStart.RawReason(); } EndRawReason()944 WSType EndRawReason() const { return mEnd.RawReason(); } 945 StartReasonOtherBlockElementPtr()946 MOZ_NEVER_INLINE_DEBUG dom::Element* StartReasonOtherBlockElementPtr() 947 const { 948 return mStart.OtherBlockElementPtr(); 949 } StartReasonBRElementPtr()950 MOZ_NEVER_INLINE_DEBUG dom::HTMLBRElement* StartReasonBRElementPtr() const { 951 return mStart.BRElementPtr(); 952 } EndReasonOtherBlockElementPtr()953 MOZ_NEVER_INLINE_DEBUG dom::Element* EndReasonOtherBlockElementPtr() const { 954 return mEnd.OtherBlockElementPtr(); 955 } EndReasonBRElementPtr()956 MOZ_NEVER_INLINE_DEBUG dom::HTMLBRElement* EndReasonBRElementPtr() const { 957 return mEnd.BRElementPtr(); 958 } 959 StartRef()960 const EditorDOMPoint& StartRef() const { return mStart.PointRef(); } EndRef()961 const EditorDOMPoint& EndRef() const { return mEnd.PointRef(); } 962 ScanStartRef()963 const EditorDOMPoint& ScanStartRef() const { return mScanStartPoint; } 964 FoundNoBreakingWhiteSpaces()965 bool FoundNoBreakingWhiteSpaces() const { return mNBSPData.FoundNBSP(); } FirstNBSPPointRef()966 const EditorDOMPointInText& FirstNBSPPointRef() const { 967 return mNBSPData.FirstPointRef(); 968 } LastNBSPPointRef()969 const EditorDOMPointInText& LastNBSPPointRef() const { 970 return mNBSPData.LastPointRef(); 971 } 972 IsPreformatted()973 bool IsPreformatted() const { return mIsPreformatted; } 974 975 template <typename PT, typename CT> 976 EditorDOMPointInText GetInclusiveNextEditableCharPoint( 977 const EditorDOMPointBase<PT, CT>& aPoint) const; 978 template <typename PT, typename CT> 979 EditorDOMPointInText GetPreviousEditableCharPoint( 980 const EditorDOMPointBase<PT, CT>& aPoint) const; 981 982 EditorDOMPointInText GetEndOfCollapsibleASCIIWhiteSpaces( 983 const EditorDOMPointInText& aPointAtASCIIWhiteSpace) const; 984 EditorDOMPointInText GetFirstASCIIWhiteSpacePointCollapsedTo( 985 const EditorDOMPointInText& aPointAtASCIIWhiteSpace) const; 986 987 /** 988 * GetNonCollapsedRangeInTexts() returns non-empty range in texts which 989 * is the largest range in aRange if there is some text nodes. 990 */ 991 EditorDOMRangeInTexts GetNonCollapsedRangeInTexts( 992 const EditorDOMRange& aRange) const; 993 994 /** 995 * InvisibleLeadingWhiteSpaceRangeRef() retruns reference to two DOM points, 996 * start of the line and first visible point or end of the hard line. When 997 * this returns non-positioned range or positioned but collapsed range, 998 * there is no invisible leading white-spaces. 999 * Note that if there are only invisible white-spaces in a hard line, 1000 * this returns all of the white-spaces. 1001 */ 1002 const EditorDOMRange& InvisibleLeadingWhiteSpaceRangeRef() const; 1003 1004 /** 1005 * InvisibleTrailingWhiteSpaceRangeRef() returns reference to two DOM 1006 * points, first invisible white-space and end of the hard line. When this 1007 * returns non-positioned range or positioned but collapsed range, 1008 * there is no invisible trailing white-spaces. 1009 * Note that if there are only invisible white-spaces in a hard line, 1010 * this returns all of the white-spaces. 1011 */ 1012 const EditorDOMRange& InvisibleTrailingWhiteSpaceRangeRef() const; 1013 1014 /** 1015 * GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt() returns new 1016 * invisible leading white-space range which should be removed if 1017 * splitting invisible white-space sequence at aPointToSplit creates 1018 * new invisible leading white-spaces in the new line. 1019 * Note that the result may be collapsed range if the point is around 1020 * invisible white-spaces. 1021 */ 1022 template <typename EditorDOMPointType> GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(const EditorDOMPointType & aPointToSplit)1023 EditorDOMRange GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt( 1024 const EditorDOMPointType& aPointToSplit) const { 1025 // If there are invisible trailing white-spaces and some or all of them 1026 // become invisible leading white-spaces in the new line, although we 1027 // don't need to delete them, but for aesthetically and backward 1028 // compatibility, we should remove them. 1029 const EditorDOMRange& trailingWhiteSpaceRange = 1030 InvisibleTrailingWhiteSpaceRangeRef(); 1031 // XXX Why don't we check leading white-spaces too? 1032 if (!trailingWhiteSpaceRange.IsPositioned()) { 1033 return trailingWhiteSpaceRange; 1034 } 1035 // If the point is before the trailing white-spaces, the new line won't 1036 // start with leading white-spaces. 1037 if (aPointToSplit.IsBefore(trailingWhiteSpaceRange.StartRef())) { 1038 return EditorDOMRange(); 1039 } 1040 // If the point is in the trailing white-spaces, the new line may 1041 // start with some leading white-spaces. Returning collapsed range 1042 // is intentional because the caller may want to know whether the 1043 // point is in trailing white-spaces or not. 1044 if (aPointToSplit.EqualsOrIsBefore(trailingWhiteSpaceRange.EndRef())) { 1045 return EditorDOMRange(trailingWhiteSpaceRange.StartRef(), 1046 aPointToSplit); 1047 } 1048 // Otherwise, if the point is after the trailing white-spaces, it may 1049 // be just outside of the text node. E.g., end of parent element. 1050 // This is possible case but the validation cost is not worthwhile 1051 // due to the runtime cost in the worst case. Therefore, we should just 1052 // return collapsed range at the end of trailing white-spaces. Then, 1053 // callers can know the point is immediately after the trailing 1054 // white-spaces. 1055 return EditorDOMRange(trailingWhiteSpaceRange.EndRef()); 1056 } 1057 1058 /** 1059 * GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt() returns new 1060 * invisible trailing white-space range which should be removed if 1061 * splitting invisible white-space sequence at aPointToSplit creates 1062 * new invisible trailing white-spaces in the new line. 1063 * Note that the result may be collapsed range if the point is around 1064 * invisible white-spaces. 1065 */ 1066 template <typename EditorDOMPointType> GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(const EditorDOMPointType & aPointToSplit)1067 EditorDOMRange GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt( 1068 const EditorDOMPointType& aPointToSplit) const { 1069 // If there are invisible leading white-spaces and some or all of them 1070 // become end of current line, they will become visible. Therefore, we 1071 // need to delete the invisible leading white-spaces before insertion 1072 // point. 1073 const EditorDOMRange& leadingWhiteSpaceRange = 1074 InvisibleLeadingWhiteSpaceRangeRef(); 1075 if (!leadingWhiteSpaceRange.IsPositioned()) { 1076 return leadingWhiteSpaceRange; 1077 } 1078 // If the point equals or is after the leading white-spaces, the line 1079 // will end without trailing white-spaces. 1080 if (leadingWhiteSpaceRange.EndRef().IsBefore(aPointToSplit)) { 1081 return EditorDOMRange(); 1082 } 1083 // If the point is in the leading white-spaces, the line may 1084 // end with some trailing white-spaces. Returning collapsed range 1085 // is intentional because the caller may want to know whether the 1086 // point is in leading white-spaces or not. 1087 if (leadingWhiteSpaceRange.StartRef().EqualsOrIsBefore(aPointToSplit)) { 1088 return EditorDOMRange(aPointToSplit, leadingWhiteSpaceRange.EndRef()); 1089 } 1090 // Otherwise, if the point is before the leading white-spaces, it may 1091 // be just outside of the text node. E.g., start of parent element. 1092 // This is possible case but the validation cost is not worthwhile 1093 // due to the runtime cost in the worst case. Therefore, we should 1094 // just return collapsed range at start of the leading white-spaces. 1095 // Then, callers can know the point is immediately before the leading 1096 // white-spaces. 1097 return EditorDOMRange(leadingWhiteSpaceRange.StartRef()); 1098 } 1099 1100 /** 1101 * FollowingContentMayBecomeFirstVisibleContent() returns true if some 1102 * content may be first visible content after removing content after aPoint. 1103 * Note that it's completely broken what this does. Don't use this method 1104 * with new code. 1105 */ 1106 template <typename EditorDOMPointType> FollowingContentMayBecomeFirstVisibleContent(const EditorDOMPointType & aPoint)1107 bool FollowingContentMayBecomeFirstVisibleContent( 1108 const EditorDOMPointType& aPoint) const { 1109 MOZ_ASSERT(aPoint.IsSetAndValid()); 1110 if (!mStart.IsHardLineBreak()) { 1111 return false; 1112 } 1113 // If the point is before start of text fragment, that means that the 1114 // point may be at the block boundary or inline element boundary. 1115 if (aPoint.EqualsOrIsBefore(mStart.PointRef())) { 1116 return true; 1117 } 1118 // VisibleWhiteSpacesData is marked as start of line only when it 1119 // represents leading white-spaces. 1120 const EditorDOMRange& leadingWhiteSpaceRange = 1121 InvisibleLeadingWhiteSpaceRangeRef(); 1122 if (!leadingWhiteSpaceRange.StartRef().IsSet()) { 1123 return false; 1124 } 1125 if (aPoint.EqualsOrIsBefore(leadingWhiteSpaceRange.StartRef())) { 1126 return true; 1127 } 1128 if (!leadingWhiteSpaceRange.EndRef().IsSet()) { 1129 return false; 1130 } 1131 return aPoint.EqualsOrIsBefore(leadingWhiteSpaceRange.EndRef()); 1132 } 1133 1134 /** 1135 * PrecedingContentMayBecomeInvisible() returns true if end of preceding 1136 * content is collapsed (when ends with an ASCII white-space). 1137 * Note that it's completely broken what this does. Don't use this method 1138 * with new code. 1139 */ 1140 template <typename EditorDOMPointType> PrecedingContentMayBecomeInvisible(const EditorDOMPointType & aPoint)1141 bool PrecedingContentMayBecomeInvisible( 1142 const EditorDOMPointType& aPoint) const { 1143 MOZ_ASSERT(aPoint.IsSetAndValid()); 1144 // If this fragment is ends by block boundary, always the caller needs 1145 // additional check. 1146 if (mEnd.IsBlockBoundary()) { 1147 return true; 1148 } 1149 1150 // If the point is in visible white-spaces and ends with an ASCII 1151 // white-space, it may be collapsed even if it won't be end of line. 1152 const VisibleWhiteSpacesData& visibleWhiteSpaces = 1153 VisibleWhiteSpacesDataRef(); 1154 if (!visibleWhiteSpaces.IsInitialized()) { 1155 return false; 1156 } 1157 // XXX Odd case, but keep traditional behavior of `FindNearestRun()`. 1158 if (!visibleWhiteSpaces.StartRef().IsSet()) { 1159 return true; 1160 } 1161 if (!visibleWhiteSpaces.StartRef().EqualsOrIsBefore(aPoint)) { 1162 return false; 1163 } 1164 // XXX Odd case, but keep traditional behavior of `FindNearestRun()`. 1165 if (visibleWhiteSpaces.EndsByTrailingWhiteSpaces()) { 1166 return true; 1167 } 1168 // XXX Must be a bug. This claims that the caller needs additional 1169 // check even when there is no white-spaces. 1170 if (visibleWhiteSpaces.StartRef() == visibleWhiteSpaces.EndRef()) { 1171 return true; 1172 } 1173 return aPoint.IsBefore(visibleWhiteSpaces.EndRef()); 1174 } 1175 1176 /** 1177 * GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace() may return an 1178 * NBSP point which should be replaced with an ASCII white-space when we're 1179 * inserting text into aPointToInsert. Note that this is a helper method for 1180 * the traditional white-space normalizer. Don't use this with the new 1181 * white-space normalizer. 1182 * Must be called only when VisibleWhiteSpacesDataRef() returns initialized 1183 * instance and previous character of aPointToInsert is in the range. 1184 */ 1185 EditorDOMPointInText GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace( 1186 const EditorDOMPoint& aPointToInsert) const; 1187 1188 /** 1189 * GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace() may return 1190 * an NBSP point which should be replaced with an ASCII white-space when 1191 * the caller inserts text into aPointToInsert. 1192 * Note that this is a helper method for the traditional white-space 1193 * normalizer. Don't use this with the new white-space normalizer. 1194 * Must be called only when VisibleWhiteSpacesDataRef() returns initialized 1195 * instance, and inclusive next char of aPointToInsert is in the range. 1196 */ 1197 EditorDOMPointInText 1198 GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace( 1199 const EditorDOMPoint& aPointToInsert) const; 1200 1201 /** 1202 * GetReplaceRangeDataAtEndOfDeletionRange() and 1203 * GetReplaceRangeDataAtStartOfDeletionRange() return delete range if 1204 * end or start of deleting range splits invisible trailing/leading 1205 * white-spaces and it may become visible, or return replace range if 1206 * end or start of deleting range splits visible white-spaces and it 1207 * causes some ASCII white-spaces become invisible unless replacing 1208 * with an NBSP. 1209 */ 1210 ReplaceRangeData GetReplaceRangeDataAtEndOfDeletionRange( 1211 const TextFragmentData& aTextFragmentDataAtStartToDelete) const; 1212 ReplaceRangeData GetReplaceRangeDataAtStartOfDeletionRange( 1213 const TextFragmentData& aTextFragmentDataAtEndToDelete) const; 1214 1215 /** 1216 * VisibleWhiteSpacesDataRef() returns reference to visible white-spaces 1217 * data. That is zero or more white-spaces which are visible. 1218 * Note that when there is no visible content, it's not initialized. 1219 * Otherwise, even if there is no white-spaces, it's initialized and 1220 * the range is collapsed in such case. 1221 */ 1222 const VisibleWhiteSpacesData& VisibleWhiteSpacesDataRef() const; 1223 1224 private: 1225 /** 1226 * IsPreformattedOrSurrondedByVisibleContent() returns true if the text is 1227 * preformatted or the text fragment is surrounded by visible content. 1228 * When this returns true, all of the text is visible. 1229 */ IsPreformattedOrSurrondedByVisibleContent()1230 bool IsPreformattedOrSurrondedByVisibleContent() const { 1231 return mIsPreformatted || 1232 ((StartsFromNormalText() || StartsFromSpecialContent()) && 1233 (EndsByNormalText() || EndsBySpecialContent() || 1234 EndsByBRElement())); 1235 } 1236 1237 EditorDOMPoint mScanStartPoint; 1238 BoundaryData mStart; 1239 BoundaryData mEnd; 1240 NoBreakingSpaceData mNBSPData; 1241 RefPtr<const dom::Element> mEditingHost; 1242 mutable Maybe<EditorDOMRange> mLeadingWhiteSpaceRange; 1243 mutable Maybe<EditorDOMRange> mTrailingWhiteSpaceRange; 1244 mutable Maybe<VisibleWhiteSpacesData> mVisibleWhiteSpacesData; 1245 // XXX Currently we set mIsPreformatted to `WSRunScanner::mPRE` value 1246 // even if some text nodes between mStart and mEnd are different styled 1247 // nodes. This caused some bugs actually, but we now keep traditional 1248 // behavior for now. 1249 bool mIsPreformatted; 1250 }; 1251 TextFragmentDataAtStartRef()1252 const TextFragmentData& TextFragmentDataAtStartRef() const { 1253 return mTextFragmentDataAtStart; 1254 } 1255 1256 // The node passed to our constructor. 1257 EditorDOMPoint mScanStartPoint; 1258 // Together, the above represent the point at which we are building up ws 1259 // info. 1260 1261 // The editing host when the instance is created. 1262 RefPtr<dom::Element> mEditingHost; 1263 1264 private: 1265 /** 1266 * ComputeRangeInTextNodesContainingInvisibleWhiteSpaces() returns range 1267 * containing invisible white-spaces if deleting between aStart and aEnd 1268 * causes them become visible. 1269 * 1270 * @param aStart TextFragmentData at start of deleting range. 1271 * This must be initialized with DOM point in a text node. 1272 * @param aEnd TextFragmentData at end of deleting range. 1273 * This must be initialized with DOM point in a text node. 1274 */ 1275 static EditorDOMRangeInTexts 1276 ComputeRangeInTextNodesContainingInvisibleWhiteSpaces( 1277 const TextFragmentData& aStart, const TextFragmentData& aEnd); 1278 1279 TextFragmentData mTextFragmentDataAtStart; 1280 1281 friend class WhiteSpaceVisibilityKeeper; 1282 }; 1283 1284 /** 1285 * WhiteSpaceVisibilityKeeper class helps `HTMLEditor` modifying the DOM tree 1286 * with keeps white-space sequence visibility automatically. E.g., invisible 1287 * leading/trailing white-spaces becomes visible, this class members delete 1288 * them. E.g., when splitting visible-white-space sequence, this class may 1289 * replace ASCII white-spaces at split edges with NBSPs. 1290 */ 1291 class WhiteSpaceVisibilityKeeper final { 1292 private: 1293 using AutoTransactionsConserveSelection = 1294 EditorBase::AutoTransactionsConserveSelection; 1295 using EditorType = EditorBase::EditorType; 1296 using PointPosition = WSRunScanner::PointPosition; 1297 using TextFragmentData = WSRunScanner::TextFragmentData; 1298 using VisibleWhiteSpacesData = WSRunScanner::VisibleWhiteSpacesData; 1299 1300 public: 1301 WhiteSpaceVisibilityKeeper() = delete; 1302 explicit WhiteSpaceVisibilityKeeper( 1303 const WhiteSpaceVisibilityKeeper& aOther) = delete; 1304 WhiteSpaceVisibilityKeeper(WhiteSpaceVisibilityKeeper&& aOther) = delete; 1305 1306 /** 1307 * DeleteInvisibleASCIIWhiteSpaces() removes invisible leading white-spaces 1308 * and trailing white-spaces if there are around aPoint. 1309 */ 1310 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult 1311 DeleteInvisibleASCIIWhiteSpaces(HTMLEditor& aHTMLEditor, 1312 const EditorDOMPoint& aPoint); 1313 1314 // PrepareToDeleteRange fixes up ws before aStartPoint and after aEndPoint in 1315 // preperation for content in that range to be deleted. Note that the nodes 1316 // and offsets are adjusted in response to any dom changes we make while 1317 // adjusting ws. 1318 // example of fixup: trailingws before aStartPoint needs to be removed. 1319 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult PrepareToDeleteRangeAndTrackPoints(HTMLEditor & aHTMLEditor,EditorDOMPoint * aStartPoint,EditorDOMPoint * aEndPoint)1320 PrepareToDeleteRangeAndTrackPoints(HTMLEditor& aHTMLEditor, 1321 EditorDOMPoint* aStartPoint, 1322 EditorDOMPoint* aEndPoint) { 1323 MOZ_ASSERT(aStartPoint->IsSetAndValid()); 1324 MOZ_ASSERT(aEndPoint->IsSetAndValid()); 1325 AutoTrackDOMPoint trackerStart(aHTMLEditor.RangeUpdaterRef(), aStartPoint); 1326 AutoTrackDOMPoint trackerEnd(aHTMLEditor.RangeUpdaterRef(), aEndPoint); 1327 return WhiteSpaceVisibilityKeeper::PrepareToDeleteRange( 1328 aHTMLEditor, EditorDOMRange(*aStartPoint, *aEndPoint)); 1329 } PrepareToDeleteRange(HTMLEditor & aHTMLEditor,const EditorDOMPoint & aStartPoint,const EditorDOMPoint & aEndPoint)1330 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult PrepareToDeleteRange( 1331 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aStartPoint, 1332 const EditorDOMPoint& aEndPoint) { 1333 MOZ_ASSERT(aStartPoint.IsSetAndValid()); 1334 MOZ_ASSERT(aEndPoint.IsSetAndValid()); 1335 return WhiteSpaceVisibilityKeeper::PrepareToDeleteRange( 1336 aHTMLEditor, EditorDOMRange(aStartPoint, aEndPoint)); 1337 } PrepareToDeleteRange(HTMLEditor & aHTMLEditor,const EditorDOMRange & aRange)1338 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult PrepareToDeleteRange( 1339 HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange) { 1340 MOZ_ASSERT(aRange.IsPositionedAndValid()); 1341 nsresult rv = WhiteSpaceVisibilityKeeper:: 1342 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(aHTMLEditor, 1343 aRange); 1344 NS_WARNING_ASSERTION( 1345 NS_SUCCEEDED(rv), 1346 "WhiteSpaceVisibilityKeeper::" 1347 "MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() failed"); 1348 return rv; 1349 } 1350 1351 // PrepareToSplitAcrossBlocks fixes up ws before and after 1352 // {aSplitNode,aSplitOffset} in preparation for a block parent to be split. 1353 // Note that the aSplitNode and aSplitOffset are adjusted in response to 1354 // any DOM changes we make while adjusting ws. Example of fixup: normalws 1355 // before {aSplitNode,aSplitOffset} needs to end with nbsp. 1356 MOZ_CAN_RUN_SCRIPT static nsresult PrepareToSplitAcrossBlocks( 1357 HTMLEditor& aHTMLEditor, nsCOMPtr<nsINode>* aSplitNode, 1358 uint32_t* aSplitOffset); 1359 1360 /** 1361 * MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement() merges 1362 * first line in aRightBlockElement into end of aLeftBlockElement which 1363 * is a descendant of aRightBlockElement. 1364 * 1365 * @param aHTMLEditor The HTML editor. 1366 * @param aLeftBlockElement The content will be merged into end of 1367 * this element. 1368 * @param aRightBlockElement The first line in this element will be 1369 * moved to aLeftBlockElement. 1370 * @param aAtRightBlockChild At a child of aRightBlockElement and inclusive 1371 * ancestor of aLeftBlockElement. 1372 * @param aListElementTagName Set some if aRightBlockElement is a list 1373 * element and it'll be merged with another 1374 * list element. 1375 */ 1376 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static EditActionResult 1377 MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement( 1378 HTMLEditor& aHTMLEditor, dom::Element& aLeftBlockElement, 1379 dom::Element& aRightBlockElement, 1380 const EditorDOMPoint& aAtRightBlockChild, 1381 const Maybe<nsAtom*>& aListElementTagName, 1382 const dom::HTMLBRElement* aPrecedingInvisibleBRElement); 1383 1384 /** 1385 * MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement() merges 1386 * first line in aRightBlockElement into end of aLeftBlockElement which 1387 * is an ancestor of aRightBlockElement, then, removes aRightBlockElement 1388 * if it becomes empty. 1389 * 1390 * @param aHTMLEditor The HTML editor. 1391 * @param aLeftBlockElement The content will be merged into end of 1392 * this element. 1393 * @param aRightBlockElement The first line in this element will be 1394 * moved to aLeftBlockElement and maybe 1395 * removed when this becomes empty. 1396 * @param aAtLeftBlockChild At a child of aLeftBlockElement and inclusive 1397 * ancestor of aRightBlockElement. 1398 * @param aLeftContentInBlock The content whose inclusive ancestor is 1399 * aLeftBlockElement. 1400 * @param aListElementTagName Set some if aRightBlockElement is a list 1401 * element and it'll be merged with another 1402 * list element. 1403 */ 1404 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static EditActionResult 1405 MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement( 1406 HTMLEditor& aHTMLEditor, dom::Element& aLeftBlockElement, 1407 dom::Element& aRightBlockElement, const EditorDOMPoint& aAtLeftBlockChild, 1408 nsIContent& aLeftContentInBlock, 1409 const Maybe<nsAtom*>& aListElementTagName, 1410 const dom::HTMLBRElement* aPrecedingInvisibleBRElement); 1411 1412 /** 1413 * MergeFirstLineOfRightBlockElementIntoLeftBlockElement() merges first 1414 * line in aRightBlockElement into end of aLeftBlockElement and removes 1415 * aRightBlockElement when it has only one line. 1416 * 1417 * @param aHTMLEditor The HTML editor. 1418 * @param aLeftBlockElement The content will be merged into end of 1419 * this element. 1420 * @param aRightBlockElement The first line in this element will be 1421 * moved to aLeftBlockElement and maybe 1422 * removed when this becomes empty. 1423 * @param aListElementTagName Set some if aRightBlockElement is a list 1424 * element and its type needs to be changed. 1425 */ 1426 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static EditActionResult 1427 MergeFirstLineOfRightBlockElementIntoLeftBlockElement( 1428 HTMLEditor& aHTMLEditor, dom::Element& aLeftBlockElement, 1429 dom::Element& aRightBlockElement, 1430 const Maybe<nsAtom*>& aListElementTagName, 1431 const dom::HTMLBRElement* aPrecedingInvisibleBRElement); 1432 1433 /** 1434 * InsertBRElement() inserts a <br> node at (before) aPointToInsert and delete 1435 * unnecessary white-spaces around there and/or replaces white-spaces with 1436 * non-breaking spaces. Note that if the point is in a text node, the 1437 * text node will be split and insert new <br> node between the left node 1438 * and the right node. 1439 * 1440 * @param aPointToInsert The point to insert new <br> element. Note that 1441 * it'll be inserted before this point. I.e., the 1442 * point will be the point of new <br>. 1443 * @return The new <br> node. If failed to create new <br> 1444 * node, returns nullptr. 1445 */ 1446 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<RefPtr<dom::Element>, nsresult> 1447 InsertBRElement(HTMLEditor& aHTMLEditor, 1448 const EditorDOMPoint& aPointToInsert); 1449 1450 /** 1451 * InsertText() inserts aStringToInsert to aPointToInsert and makes any needed 1452 * adjustments to white-spaces around the insertion point. 1453 * 1454 * @param aStringToInsert The string to insert. 1455 * @param aRangeToBeReplaced The range to be deleted. 1456 * @param aPointAfterInsertedString 1457 * The point after inserted aStringToInsert. 1458 * So, when this method actually inserts string, 1459 * this is set to a point in the text node. 1460 * Otherwise, this may be set to mScanStartPoint. 1461 * @return When this succeeds to insert the string or 1462 * does nothing during composition, returns NS_OK. 1463 * Otherwise, an error code. 1464 */ 1465 template <typename EditorDOMPointType> 1466 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult InsertText( 1467 HTMLEditor& aHTMLEditor, const nsAString& aStringToInsert, 1468 const EditorDOMPointType& aPointToInsert, 1469 EditorRawDOMPoint* aPointAfterInsertedString = nullptr) { 1470 return WhiteSpaceVisibilityKeeper::ReplaceText( 1471 aHTMLEditor, aStringToInsert, EditorDOMRange(aPointToInsert), 1472 aPointAfterInsertedString); 1473 } 1474 1475 /** 1476 * ReplaceText() repaces aRangeToReplace with aStringToInsert and makes any 1477 * needed adjustments to white-spaces around both start of the range and 1478 * end of the range. 1479 * 1480 * @param aStringToInsert The string to insert. 1481 * @param aRangeToBeReplaced The range to be deleted. 1482 * @param aPointAfterInsertedString 1483 * The point after inserted aStringToInsert. 1484 * So, when this method actually inserts string, 1485 * this is set to a point in the text node. 1486 * Otherwise, this may be set to mScanStartPoint. 1487 * @return When this succeeds to insert the string or 1488 * does nothing during composition, returns NS_OK. 1489 * Otherwise, an error code. 1490 */ 1491 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult ReplaceText( 1492 HTMLEditor& aHTMLEditor, const nsAString& aStringToInsert, 1493 const EditorDOMRange& aRangeToBeReplaced, 1494 EditorRawDOMPoint* aPointAfterInsertedString = nullptr); 1495 1496 /** 1497 * DeletePreviousWhiteSpace() deletes previous white-space of aPoint. 1498 * This automatically keeps visibility of white-spaces around aPoint. 1499 * E.g., may remove invisible leading white-spaces. 1500 */ 1501 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult DeletePreviousWhiteSpace( 1502 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint); 1503 1504 /** 1505 * DeleteInclusiveNextWhiteSpace() delete inclusive next white-space of 1506 * aPoint. This automatically keeps visiblity of white-spaces around aPoint. 1507 * E.g., may remove invisible trailing white-spaces. 1508 */ 1509 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult 1510 DeleteInclusiveNextWhiteSpace(HTMLEditor& aHTMLEditor, 1511 const EditorDOMPoint& aPoint); 1512 1513 /** 1514 * DeleteContentNodeAndJoinTextNodesAroundIt() deletes aContentToDelete and 1515 * may remove/replace white-spaces around it. Then, if deleting content makes 1516 * 2 text nodes around it are adjacent siblings, this joins them and put 1517 * selection at the joined point. 1518 */ 1519 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult 1520 DeleteContentNodeAndJoinTextNodesAroundIt(HTMLEditor& aHTMLEditor, 1521 nsIContent& aContentToDelete, 1522 const EditorDOMPoint& aCaretPoint); 1523 1524 /** 1525 * NormalizeVisibleWhiteSpacesAt() tries to normalize visible white-space 1526 * sequence around aPoint. 1527 */ 1528 template <typename EditorDOMPointType> 1529 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult 1530 NormalizeVisibleWhiteSpacesAt(HTMLEditor& aHTMLEditor, 1531 const EditorDOMPointType& aPoint); 1532 1533 private: 1534 /** 1535 * MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() may delete 1536 * invisible white-spaces for keeping make them invisible and/or may replace 1537 * ASCII white-spaces with NBSPs for making visible white-spaces to keep 1538 * visible. 1539 */ 1540 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult 1541 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange( 1542 HTMLEditor& aHTMLEditor, const EditorDOMRange& aRangeToDelete); 1543 1544 /** 1545 * MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit() replaces ASCII white- 1546 * spaces which becomes invisible after split with NBSPs. 1547 */ 1548 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult 1549 MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit( 1550 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToSplit); 1551 1552 /** 1553 * ReplaceTextAndRemoveEmptyTextNodes() replaces the range between 1554 * aRangeToReplace with aReplaceString simply. Additionally, removes 1555 * empty text nodes in the range. 1556 * 1557 * @param aRangeToReplace Range to replace text. 1558 * @param aReplaceString The new string. Empty string is allowed. 1559 */ 1560 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult 1561 ReplaceTextAndRemoveEmptyTextNodes( 1562 HTMLEditor& aHTMLEditor, const EditorDOMRangeInTexts& aRangeToReplace, 1563 const nsAString& aReplaceString); 1564 }; 1565 1566 } // namespace mozilla 1567 1568 #endif // #ifndef WSRunObject_h 1569