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