WSRunScanner.cpp (50219B)
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 #include "WSRunScanner.h" 7 8 #include "EditorDOMPoint.h" 9 #include "ErrorList.h" 10 #include "HTMLEditor.h" 11 #include "HTMLEditUtils.h" 12 13 #include "mozilla/Assertions.h" 14 #include "mozilla/Casting.h" // for AssertedCast 15 #include "mozilla/dom/Comment.h" 16 17 #include "nsDebug.h" 18 #include "nsError.h" 19 #include "nsIContent.h" 20 #include "nsIContentInlines.h" 21 #include "nsRange.h" 22 23 namespace mozilla { 24 25 using namespace dom; 26 27 /****************************************************************************** 28 * mozilla::WSScanResult 29 ******************************************************************************/ 30 31 void WSScanResult::AssertIfInvalidData(const WSRunScanner& aScanner) const { 32 #ifdef DEBUG 33 MOZ_ASSERT(mReason == WSType::UnexpectedError || 34 mReason == WSType::InUncomposedDoc || 35 mReason == WSType::NonCollapsibleCharacters || 36 mReason == WSType::CollapsibleWhiteSpaces || 37 mReason == WSType::BRElement || 38 mReason == WSType::PreformattedLineBreak || 39 mReason == WSType::SpecialContent || 40 mReason == WSType::CurrentBlockBoundary || 41 mReason == WSType::OtherBlockBoundary || 42 mReason == WSType::InlineEditingHostBoundary); 43 MOZ_ASSERT_IF(mReason == WSType::UnexpectedError, !mContent); 44 MOZ_ASSERT_IF(mReason != WSType::UnexpectedError, mContent); 45 MOZ_ASSERT_IF(mReason == WSType::InUncomposedDoc, 46 !mContent->IsInComposedDoc()); 47 MOZ_ASSERT_IF(mContent && !mContent->IsInComposedDoc(), 48 mReason == WSType::InUncomposedDoc); 49 MOZ_ASSERT_IF(mReason == WSType::NonCollapsibleCharacters || 50 mReason == WSType::CollapsibleWhiteSpaces || 51 mReason == WSType::PreformattedLineBreak, 52 mContent->IsText()); 53 MOZ_ASSERT_IF(mReason == WSType::NonCollapsibleCharacters || 54 mReason == WSType::CollapsibleWhiteSpaces || 55 mReason == WSType::PreformattedLineBreak, 56 mOffset.isSome()); 57 MOZ_ASSERT_IF(mReason == WSType::NonCollapsibleCharacters || 58 mReason == WSType::CollapsibleWhiteSpaces || 59 mReason == WSType::PreformattedLineBreak, 60 mContent->AsText()->TextDataLength() > 0); 61 MOZ_ASSERT_IF(mDirection == ScanDirection::Backward && 62 (mReason == WSType::NonCollapsibleCharacters || 63 mReason == WSType::CollapsibleWhiteSpaces || 64 mReason == WSType::PreformattedLineBreak), 65 *mOffset > 0); 66 MOZ_ASSERT_IF(mDirection == ScanDirection::Forward && 67 (mReason == WSType::NonCollapsibleCharacters || 68 mReason == WSType::CollapsibleWhiteSpaces || 69 mReason == WSType::PreformattedLineBreak), 70 *mOffset < mContent->AsText()->TextDataLength()); 71 MOZ_ASSERT_IF(mReason == WSType::BRElement, 72 mContent->IsHTMLElement(nsGkAtoms::br)); 73 MOZ_ASSERT_IF(mReason == WSType::PreformattedLineBreak, 74 EditorUtils::IsNewLinePreformatted(*mContent)); 75 MOZ_ASSERT_IF( 76 mReason == WSType::SpecialContent, 77 (mContent->IsText() && !mContent->IsEditable()) || 78 (!mContent->IsHTMLElement(nsGkAtoms::br) && 79 !HTMLEditUtils::IsBlockElement( 80 *mContent, 81 aScanner.ReferredHTMLDefaultStyle() 82 ? BlockInlineCheck::UseHTMLDefaultStyle 83 : BlockInlineCheck::UseComputedDisplayOutsideStyle))); 84 MOZ_ASSERT_IF( 85 mReason == WSType::OtherBlockBoundary, 86 HTMLEditUtils::IsBlockElement( 87 *mContent, aScanner.ReferredHTMLDefaultStyle() 88 ? BlockInlineCheck::UseHTMLDefaultStyle 89 : BlockInlineCheck::UseComputedDisplayOutsideStyle)); 90 MOZ_ASSERT_IF(mReason == WSType::CurrentBlockBoundary, mContent->IsElement()); 91 MOZ_ASSERT_IF(mReason == WSType::CurrentBlockBoundary && 92 aScanner.ScanOptions().contains( 93 WSRunScanner::Option::OnlyEditableNodes), 94 mContent->IsEditable()); 95 MOZ_ASSERT_IF( 96 mReason == WSType::CurrentBlockBoundary, 97 HTMLEditUtils::IsBlockElement( 98 *mContent, aScanner.ReferredHTMLDefaultStyle() 99 ? BlockInlineCheck::UseHTMLDefaultStyle 100 : BlockInlineCheck::UseComputedDisplayStyle)); 101 MOZ_ASSERT_IF(mReason == WSType::InlineEditingHostBoundary, 102 mContent->IsElement()); 103 MOZ_ASSERT_IF(mReason == WSType::InlineEditingHostBoundary && 104 aScanner.ScanOptions().contains( 105 WSRunScanner::Option::OnlyEditableNodes), 106 mContent->IsEditable()); 107 MOZ_ASSERT_IF( 108 mReason == WSType::InlineEditingHostBoundary, 109 !HTMLEditUtils::IsBlockElement( 110 *mContent, aScanner.ReferredHTMLDefaultStyle() 111 ? BlockInlineCheck::UseHTMLDefaultStyle 112 : BlockInlineCheck::UseComputedDisplayStyle)); 113 MOZ_ASSERT_IF(mReason == WSType::InlineEditingHostBoundary, 114 !mContent->GetParentElement() || 115 !mContent->GetParentElement()->IsEditable()); 116 #endif // #ifdef DEBUG 117 } 118 119 /****************************************************************************** 120 * mozilla::WSRunScanner 121 ******************************************************************************/ 122 123 template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom( 124 const EditorDOMPoint& aPoint) const; 125 template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom( 126 const EditorRawDOMPoint& aPoint) const; 127 template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom( 128 const EditorDOMPointInText& aPoint) const; 129 template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom( 130 const EditorRawDOMPointInText& aPoint) const; 131 template WSScanResult 132 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom( 133 const EditorDOMPoint& aPoint) const; 134 template WSScanResult 135 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom( 136 const EditorRawDOMPoint& aPoint) const; 137 template WSScanResult 138 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom( 139 const EditorDOMPointInText& aPoint) const; 140 template WSScanResult 141 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom( 142 const EditorRawDOMPointInText& aPoint) const; 143 template EditorDOMPoint WSRunScanner::GetAfterLastVisiblePoint(Options, Text&, 144 const Element*); 145 template EditorRawDOMPoint WSRunScanner::GetAfterLastVisiblePoint( 146 Options, Text&, const Element*); 147 template EditorDOMPoint WSRunScanner::GetFirstVisiblePoint(Options, Text&, 148 const Element*); 149 template EditorRawDOMPoint WSRunScanner::GetFirstVisiblePoint(Options, Text&, 150 const Element*); 151 152 template <typename PT, typename CT> 153 WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom( 154 const EditorDOMPointBase<PT, CT>& aPoint) const { 155 MOZ_ASSERT(aPoint.IsSet()); 156 MOZ_ASSERT(aPoint.IsInComposedDoc()); 157 158 if (MOZ_UNLIKELY(!aPoint.IsSet())) { 159 return WSScanResult::Error(); 160 } 161 162 // We may not be able to check editable state in uncomposed tree as expected. 163 // For example, only some descendants in an editing host is temporarily 164 // removed from the tree, they are not editable unless nested contenteditable 165 // attribute is set to "true". 166 if (MOZ_UNLIKELY(!aPoint.IsInComposedDoc())) { 167 return WSScanResult(*this, WSScanResult::ScanDirection::Backward, 168 *aPoint.template ContainerAs<nsIContent>(), 169 WSType::InUncomposedDoc); 170 } 171 172 if (!TextFragmentDataAtStartRef().IsInitialized()) { 173 return WSScanResult::Error(); 174 } 175 176 // If the range has visible text and start of the visible text is before 177 // aPoint, return previous character in the text. 178 const VisibleWhiteSpacesData& visibleWhiteSpaces = 179 TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef(); 180 if (visibleWhiteSpaces.IsInitialized() && 181 visibleWhiteSpaces.StartRef().IsBefore(aPoint)) { 182 // If the visible things are not editable, we shouldn't scan "editable" 183 // things now. Whether keep scanning editable things or not should be 184 // considered by the caller. 185 if (ScanOptions().contains(Option::OnlyEditableNodes) && 186 aPoint.GetChild() && 187 !HTMLEditUtils::IsSimplyEditableNode((*aPoint.GetChild()))) { 188 return WSScanResult(*this, WSScanResult::ScanDirection::Backward, 189 *aPoint.GetChild(), WSType::SpecialContent); 190 } 191 const auto atPreviousChar = 192 GetPreviousCharPoint<EditorRawDOMPointInText>(aPoint); 193 // When it's a non-empty text node, return it. 194 if (atPreviousChar.IsSet() && !atPreviousChar.IsContainerEmpty()) { 195 MOZ_ASSERT(!atPreviousChar.IsEndOfContainer()); 196 return WSScanResult(*this, WSScanResult::ScanDirection::Backward, 197 atPreviousChar.template NextPoint<EditorDOMPoint>(), 198 atPreviousChar.IsCharCollapsibleASCIISpaceOrNBSP() 199 ? WSType::CollapsibleWhiteSpaces 200 : atPreviousChar.IsCharPreformattedNewLine() 201 ? WSType::PreformattedLineBreak 202 : WSType::NonCollapsibleCharacters); 203 } 204 } 205 206 if (NS_WARN_IF(TextFragmentDataAtStartRef().StartRawReason() == 207 WSType::UnexpectedError)) { 208 return WSScanResult::Error(); 209 } 210 211 MOZ_ASSERT_IF(!ScanOptions().contains(Option::StopAtComment), 212 !Comment::FromNodeOrNull( 213 TextFragmentDataAtStartRef().GetStartReasonContent())); 214 switch (TextFragmentDataAtStartRef().StartRawReason()) { 215 case WSType::CollapsibleWhiteSpaces: 216 case WSType::NonCollapsibleCharacters: 217 case WSType::PreformattedLineBreak: 218 MOZ_ASSERT(TextFragmentDataAtStartRef().StartRef().IsSet()); 219 // XXX: If we find the character at last of a text node and we started 220 // scanning from following text node of it, some callers may work with the 221 // point in the following text node instead of end of the found text node. 222 return WSScanResult(*this, WSScanResult::ScanDirection::Backward, 223 TextFragmentDataAtStartRef().StartRef(), 224 TextFragmentDataAtStartRef().StartRawReason()); 225 default: 226 break; 227 } 228 229 // Otherwise, return the start of the range. 230 if (TextFragmentDataAtStartRef().GetStartReasonContent() != 231 TextFragmentDataAtStartRef().StartRef().GetContainer()) { 232 if (NS_WARN_IF(!TextFragmentDataAtStartRef().GetStartReasonContent())) { 233 return WSScanResult::Error(); 234 } 235 // In this case, TextFragmentDataAtStartRef().StartRef().Offset() is not 236 // meaningful. 237 return WSScanResult(*this, WSScanResult::ScanDirection::Backward, 238 *TextFragmentDataAtStartRef().GetStartReasonContent(), 239 TextFragmentDataAtStartRef().StartRawReason()); 240 } 241 if (NS_WARN_IF(!TextFragmentDataAtStartRef().StartRef().IsSet())) { 242 return WSScanResult::Error(); 243 } 244 return WSScanResult(*this, WSScanResult::ScanDirection::Backward, 245 TextFragmentDataAtStartRef().StartRef(), 246 TextFragmentDataAtStartRef().StartRawReason()); 247 } 248 249 template <typename PT, typename CT> 250 WSScanResult WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom( 251 const EditorDOMPointBase<PT, CT>& aPoint) const { 252 MOZ_ASSERT(aPoint.IsSet()); 253 MOZ_ASSERT(aPoint.IsInComposedDoc()); 254 255 if (MOZ_UNLIKELY(!aPoint.IsSet())) { 256 return WSScanResult::Error(); 257 } 258 259 // We may not be able to check editable state in uncomposed tree as expected. 260 // For example, only some descendants in an editing host is temporarily 261 // removed from the tree, they are not editable unless nested contenteditable 262 // attribute is set to "true". 263 if (MOZ_UNLIKELY(!aPoint.IsInComposedDoc())) { 264 return WSScanResult(*this, WSScanResult::ScanDirection::Forward, 265 *aPoint.template ContainerAs<nsIContent>(), 266 WSType::InUncomposedDoc); 267 } 268 269 if (!TextFragmentDataAtStartRef().IsInitialized()) { 270 return WSScanResult::Error(); 271 } 272 273 // If the range has visible text and aPoint equals or is before the end of the 274 // visible text, return inclusive next character in the text. 275 const VisibleWhiteSpacesData& visibleWhiteSpaces = 276 TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef(); 277 if (visibleWhiteSpaces.IsInitialized() && 278 aPoint.EqualsOrIsBefore(visibleWhiteSpaces.EndRef())) { 279 // If the visible things are not editable, we shouldn't scan "editable" 280 // things now. Whether keep scanning editable things or not should be 281 // considered by the caller. 282 if (ScanOptions().contains(Option::OnlyEditableNodes) && 283 aPoint.GetChild() && 284 !HTMLEditUtils::IsSimplyEditableNode(*aPoint.GetChild())) { 285 return WSScanResult(*this, WSScanResult::ScanDirection::Forward, 286 *aPoint.GetChild(), WSType::SpecialContent); 287 } 288 const auto atNextChar = GetInclusiveNextCharPoint<EditorDOMPoint>(aPoint); 289 // When it's a non-empty text node, return it. 290 if (atNextChar.IsSet() && !atNextChar.IsContainerEmpty()) { 291 return WSScanResult(*this, WSScanResult::ScanDirection::Forward, 292 atNextChar, 293 !atNextChar.IsEndOfContainer() && 294 atNextChar.IsCharCollapsibleASCIISpaceOrNBSP() 295 ? WSType::CollapsibleWhiteSpaces 296 : !atNextChar.IsEndOfContainer() && 297 atNextChar.IsCharPreformattedNewLine() 298 ? WSType::PreformattedLineBreak 299 : WSType::NonCollapsibleCharacters); 300 } 301 } 302 303 if (NS_WARN_IF(TextFragmentDataAtStartRef().EndRawReason() == 304 WSType::UnexpectedError)) { 305 return WSScanResult::Error(); 306 } 307 308 MOZ_ASSERT_IF(!ScanOptions().contains(Option::StopAtComment), 309 !Comment::FromNodeOrNull( 310 TextFragmentDataAtStartRef().GetEndReasonContent())); 311 switch (TextFragmentDataAtStartRef().EndRawReason()) { 312 case WSType::CollapsibleWhiteSpaces: 313 case WSType::NonCollapsibleCharacters: 314 case WSType::PreformattedLineBreak: 315 MOZ_ASSERT(TextFragmentDataAtStartRef().StartRef().IsSet()); 316 // XXX: If we find the character at start of a text node and we 317 // started scanning from preceding text node of it, some callers may want 318 // to work with the point at end of the preceding text node instead of 319 // start of the found text node. 320 return WSScanResult(*this, WSScanResult::ScanDirection::Forward, 321 TextFragmentDataAtStartRef().EndRef(), 322 TextFragmentDataAtStartRef().EndRawReason()); 323 default: 324 break; 325 } 326 327 // Otherwise, return the end of the range. 328 if (TextFragmentDataAtStartRef().GetEndReasonContent() != 329 TextFragmentDataAtStartRef().EndRef().GetContainer()) { 330 if (NS_WARN_IF(!TextFragmentDataAtStartRef().GetEndReasonContent())) { 331 return WSScanResult::Error(); 332 } 333 // In this case, TextFragmentDataAtStartRef().EndRef().Offset() is not 334 // meaningful. 335 return WSScanResult(*this, WSScanResult::ScanDirection::Forward, 336 *TextFragmentDataAtStartRef().GetEndReasonContent(), 337 TextFragmentDataAtStartRef().EndRawReason()); 338 } 339 if (NS_WARN_IF(!TextFragmentDataAtStartRef().EndRef().IsSet())) { 340 return WSScanResult::Error(); 341 } 342 return WSScanResult(*this, WSScanResult::ScanDirection::Forward, 343 TextFragmentDataAtStartRef().EndRef(), 344 TextFragmentDataAtStartRef().EndRawReason()); 345 } 346 347 // static 348 template <typename EditorDOMPointType> 349 EditorDOMPointType WSRunScanner::GetAfterLastVisiblePoint( 350 Options aOptions, // NOLINT(performance-unnecessary-value-param) 351 Text& aTextNode, const Element* aAncestorLimiter /* = nullptr */) { 352 MOZ_ASSERT(!aOptions.contains(Option::ReferHTMLDefaultStyle)); 353 354 EditorDOMPoint atLastCharOfTextNode( 355 &aTextNode, AssertedCast<uint32_t>(std::max<int64_t>( 356 static_cast<int64_t>(aTextNode.Length()) - 1, 0))); 357 if (!atLastCharOfTextNode.IsContainerEmpty() && 358 !atLastCharOfTextNode.IsCharCollapsibleASCIISpace()) { 359 return EditorDOMPointType::AtEndOf(aTextNode); 360 } 361 const TextFragmentData textFragmentData(aOptions, atLastCharOfTextNode, 362 aAncestorLimiter); 363 if (NS_WARN_IF(!textFragmentData.IsInitialized())) { 364 return EditorDOMPointType(); // TODO: Make here return error with Err. 365 } 366 const EditorDOMRange& invisibleWhiteSpaceRange = 367 textFragmentData.InvisibleTrailingWhiteSpaceRangeRef(); 368 if (!invisibleWhiteSpaceRange.IsPositioned() || 369 invisibleWhiteSpaceRange.Collapsed()) { 370 return EditorDOMPointType::AtEndOf(aTextNode); 371 } 372 return invisibleWhiteSpaceRange.StartRef().To<EditorDOMPointType>(); 373 } 374 375 // static 376 template <typename EditorDOMPointType> 377 EditorDOMPointType WSRunScanner::GetFirstVisiblePoint( 378 Options aOptions, // NOLINT(performance-unnecessary-value-param) 379 Text& aTextNode, const Element* aAncestorLimiter /* = nullptr */) { 380 MOZ_ASSERT(!aOptions.contains(Option::ReferHTMLDefaultStyle)); 381 382 EditorDOMPoint atStartOfTextNode(&aTextNode, 0); 383 if (!atStartOfTextNode.IsContainerEmpty() && 384 !atStartOfTextNode.IsCharCollapsibleASCIISpace()) { 385 return atStartOfTextNode.To<EditorDOMPointType>(); 386 } 387 const TextFragmentData textFragmentData(aOptions, atStartOfTextNode, 388 aAncestorLimiter); 389 if (NS_WARN_IF(!textFragmentData.IsInitialized())) { 390 return EditorDOMPointType(); // TODO: Make here return error with Err. 391 } 392 const EditorDOMRange& invisibleWhiteSpaceRange = 393 textFragmentData.InvisibleLeadingWhiteSpaceRangeRef(); 394 if (!invisibleWhiteSpaceRange.IsPositioned() || 395 invisibleWhiteSpaceRange.Collapsed()) { 396 return atStartOfTextNode.To<EditorDOMPointType>(); 397 } 398 return invisibleWhiteSpaceRange.EndRef().To<EditorDOMPointType>(); 399 } 400 401 /***************************************************************************** 402 * Implementation for new white-space normalizer 403 *****************************************************************************/ 404 405 // static 406 EditorDOMRangeInTexts 407 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces( 408 const TextFragmentData& aStart, const TextFragmentData& aEnd) { 409 MOZ_ASSERT(aStart.ScanOptions() == aEnd.ScanOptions()); 410 411 // Corresponding to handling invisible white-spaces part of 412 // `TextFragmentData::GetReplaceRangeDataAtEndOfDeletionRange()` and 413 // `TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange()` 414 415 MOZ_ASSERT(aStart.ScanStartRef().IsSetAndValid()); 416 MOZ_ASSERT(aEnd.ScanStartRef().IsSetAndValid()); 417 MOZ_ASSERT(aStart.ScanStartRef().EqualsOrIsBefore(aEnd.ScanStartRef())); 418 MOZ_ASSERT(aStart.ScanStartRef().IsInTextNode()); 419 MOZ_ASSERT(aEnd.ScanStartRef().IsInTextNode()); 420 421 // XXX `GetReplaceRangeDataAtEndOfDeletionRange()` and 422 // `GetReplaceRangeDataAtStartOfDeletionRange()` use 423 // `GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt()` and 424 // `GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt()`. 425 // However, they are really odd as mentioned with "XXX" comments 426 // in them. For the new white-space normalizer, we need to treat 427 // invisible white-spaces stricter because the legacy path handles 428 // white-spaces multiple times (e.g., calling `HTMLEditor:: 429 // DeleteNodeIfInvisibleAndEditableTextNode()` later) and that hides 430 // the bug, but in the new path, we should stop doing same things 431 // multiple times for both performance and footprint. Therefore, 432 // even though the result might be different in some edge cases, 433 // we should use clean path for now. Perhaps, we should fix the odd 434 // cases before shipping `beforeinput` event in release channel. 435 436 const EditorDOMRange& invisibleLeadingWhiteSpaceRange = 437 aStart.InvisibleLeadingWhiteSpaceRangeRef(); 438 const EditorDOMRange& invisibleTrailingWhiteSpaceRange = 439 aEnd.InvisibleTrailingWhiteSpaceRangeRef(); 440 const bool hasInvisibleLeadingWhiteSpaces = 441 invisibleLeadingWhiteSpaceRange.IsPositioned() && 442 !invisibleLeadingWhiteSpaceRange.Collapsed(); 443 const bool hasInvisibleTrailingWhiteSpaces = 444 invisibleLeadingWhiteSpaceRange != invisibleTrailingWhiteSpaceRange && 445 invisibleTrailingWhiteSpaceRange.IsPositioned() && 446 !invisibleTrailingWhiteSpaceRange.Collapsed(); 447 448 EditorDOMRangeInTexts result(aStart.ScanStartRef().AsInText(), 449 aEnd.ScanStartRef().AsInText()); 450 MOZ_ASSERT(result.IsPositionedAndValid()); 451 if (!hasInvisibleLeadingWhiteSpaces && !hasInvisibleTrailingWhiteSpaces) { 452 return result; 453 } 454 455 MOZ_ASSERT_IF( 456 hasInvisibleLeadingWhiteSpaces && hasInvisibleTrailingWhiteSpaces, 457 invisibleLeadingWhiteSpaceRange.StartRef().IsBefore( 458 invisibleTrailingWhiteSpaceRange.StartRef())); 459 const EditorDOMPoint& aroundFirstInvisibleWhiteSpace = 460 hasInvisibleLeadingWhiteSpaces 461 ? invisibleLeadingWhiteSpaceRange.StartRef() 462 : invisibleTrailingWhiteSpaceRange.StartRef(); 463 if (aroundFirstInvisibleWhiteSpace.IsBefore(result.StartRef())) { 464 if (aroundFirstInvisibleWhiteSpace.IsInTextNode()) { 465 result.SetStart(aroundFirstInvisibleWhiteSpace.AsInText()); 466 MOZ_ASSERT(result.IsPositionedAndValid()); 467 } else { 468 const auto atFirstInvisibleWhiteSpace = 469 hasInvisibleLeadingWhiteSpaces 470 ? aStart.GetInclusiveNextCharPoint<EditorDOMPointInText>( 471 aroundFirstInvisibleWhiteSpace, 472 ShouldIgnoreNonEditableSiblingsOrDescendants( 473 aStart.ScanOptions())) 474 : aEnd.GetInclusiveNextCharPoint<EditorDOMPointInText>( 475 aroundFirstInvisibleWhiteSpace, 476 ShouldIgnoreNonEditableSiblingsOrDescendants( 477 aEnd.ScanOptions())); 478 MOZ_ASSERT(atFirstInvisibleWhiteSpace.IsSet()); 479 MOZ_ASSERT( 480 atFirstInvisibleWhiteSpace.EqualsOrIsBefore(result.StartRef())); 481 result.SetStart(atFirstInvisibleWhiteSpace); 482 MOZ_ASSERT(result.IsPositionedAndValid()); 483 } 484 } 485 MOZ_ASSERT_IF( 486 hasInvisibleLeadingWhiteSpaces && hasInvisibleTrailingWhiteSpaces, 487 invisibleLeadingWhiteSpaceRange.EndRef().IsBefore( 488 invisibleTrailingWhiteSpaceRange.EndRef())); 489 const EditorDOMPoint& afterLastInvisibleWhiteSpace = 490 hasInvisibleTrailingWhiteSpaces 491 ? invisibleTrailingWhiteSpaceRange.EndRef() 492 : invisibleLeadingWhiteSpaceRange.EndRef(); 493 if (afterLastInvisibleWhiteSpace.EqualsOrIsBefore(result.EndRef())) { 494 MOZ_ASSERT(result.IsPositionedAndValid()); 495 return result; 496 } 497 if (afterLastInvisibleWhiteSpace.IsInTextNode()) { 498 result.SetEnd(afterLastInvisibleWhiteSpace.AsInText()); 499 MOZ_ASSERT(result.IsPositionedAndValid()); 500 return result; 501 } 502 const auto atLastInvisibleWhiteSpace = 503 hasInvisibleTrailingWhiteSpaces 504 ? aEnd.GetPreviousCharPoint<EditorDOMPointInText>( 505 afterLastInvisibleWhiteSpace, 506 ShouldIgnoreNonEditableSiblingsOrDescendants( 507 aEnd.ScanOptions())) 508 : aStart.GetPreviousCharPoint<EditorDOMPointInText>( 509 afterLastInvisibleWhiteSpace, 510 ShouldIgnoreNonEditableSiblingsOrDescendants( 511 aStart.ScanOptions())); 512 MOZ_ASSERT(atLastInvisibleWhiteSpace.IsSet()); 513 MOZ_ASSERT(atLastInvisibleWhiteSpace.IsContainerEmpty() || 514 atLastInvisibleWhiteSpace.IsAtLastContent()); 515 MOZ_ASSERT(result.EndRef().EqualsOrIsBefore(atLastInvisibleWhiteSpace)); 516 result.SetEnd(atLastInvisibleWhiteSpace.IsEndOfContainer() 517 ? atLastInvisibleWhiteSpace 518 : atLastInvisibleWhiteSpace.NextPoint()); 519 MOZ_ASSERT(result.IsPositionedAndValid()); 520 return result; 521 } 522 523 // static 524 Result<EditorDOMRangeInTexts, nsresult> 525 WSRunScanner::GetRangeInTextNodesToBackspaceFrom( 526 Options aOptions, // NOLINT(performance-unnecessary-value-param) 527 const EditorDOMPoint& aPoint, 528 const Element* aAncestorLimiter /* = nullptr */) { 529 // Corresponding to computing delete range part of 530 // `WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace()` 531 MOZ_ASSERT(aPoint.IsSetAndValid()); 532 MOZ_ASSERT(!aOptions.contains(Option::ReferHTMLDefaultStyle)); 533 534 const TextFragmentData textFragmentDataAtCaret(aOptions, aPoint, 535 aAncestorLimiter); 536 if (NS_WARN_IF(!textFragmentDataAtCaret.IsInitialized())) { 537 return Err(NS_ERROR_FAILURE); 538 } 539 auto atPreviousChar = 540 textFragmentDataAtCaret.GetPreviousCharPoint<EditorDOMPointInText>( 541 aPoint, ShouldIgnoreNonEditableSiblingsOrDescendants(aOptions)); 542 if (!atPreviousChar.IsSet()) { 543 return EditorDOMRangeInTexts(); // There is no content in the block. 544 } 545 546 // XXX When previous char point is in an empty text node, we do nothing, 547 // but this must look odd from point of user view. We should delete 548 // something before aPoint. 549 if (atPreviousChar.IsEndOfContainer()) { 550 return EditorDOMRangeInTexts(); 551 } 552 553 // Extend delete range if previous char is a low surrogate following 554 // a high surrogate. 555 EditorDOMPointInText atNextChar = atPreviousChar.NextPoint(); 556 if (!atPreviousChar.IsStartOfContainer()) { 557 if (atPreviousChar.IsCharLowSurrogateFollowingHighSurrogate()) { 558 atPreviousChar = atPreviousChar.PreviousPoint(); 559 } 560 // If caret is in middle of a surrogate pair, delete the surrogate pair 561 // (blink-compat). 562 else if (atPreviousChar.IsCharHighSurrogateFollowedByLowSurrogate()) { 563 atNextChar = atNextChar.NextPoint(); 564 } 565 } 566 567 // If previous char is an collapsible white-spaces, delete all adjacent 568 // white-spaces which are collapsed together. 569 EditorDOMRangeInTexts rangeToDelete; 570 if (atPreviousChar.IsCharCollapsibleASCIISpace() || 571 atPreviousChar.IsCharPreformattedNewLineCollapsedWithWhiteSpaces()) { 572 const auto startToDelete = 573 textFragmentDataAtCaret 574 .GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointInText>( 575 atPreviousChar, nsIEditor::ePrevious, 576 ShouldIgnoreNonEditableSiblingsOrDescendants(aOptions)); 577 if (!startToDelete.IsSet()) { 578 NS_WARNING( 579 "WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed"); 580 return Err(NS_ERROR_FAILURE); 581 } 582 const auto endToDelete = 583 textFragmentDataAtCaret 584 .GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>( 585 atPreviousChar, nsIEditor::ePrevious, 586 ShouldIgnoreNonEditableSiblingsOrDescendants(aOptions)); 587 if (!endToDelete.IsSet()) { 588 NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed"); 589 return Err(NS_ERROR_FAILURE); 590 } 591 rangeToDelete = EditorDOMRangeInTexts(startToDelete, endToDelete); 592 } 593 // if previous char is not a collapsible white-space, remove it. 594 else { 595 rangeToDelete = EditorDOMRangeInTexts(atPreviousChar, atNextChar); 596 } 597 598 // If there is no removable and visible content, we should do nothing. 599 if (rangeToDelete.Collapsed()) { 600 return EditorDOMRangeInTexts(); 601 } 602 603 // And also delete invisible white-spaces if they become visible. 604 const TextFragmentData textFragmentDataAtStart = 605 rangeToDelete.StartRef() != aPoint 606 ? TextFragmentData(aOptions, rangeToDelete.StartRef(), 607 aAncestorLimiter) 608 : textFragmentDataAtCaret; 609 const TextFragmentData textFragmentDataAtEnd = 610 rangeToDelete.EndRef() != aPoint 611 ? TextFragmentData(aOptions, rangeToDelete.EndRef(), aAncestorLimiter) 612 : textFragmentDataAtCaret; 613 if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized()) || 614 NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) { 615 return Err(NS_ERROR_FAILURE); 616 } 617 EditorDOMRangeInTexts extendedRangeToDelete = 618 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces( 619 textFragmentDataAtStart, textFragmentDataAtEnd); 620 MOZ_ASSERT(extendedRangeToDelete.IsPositionedAndValid()); 621 return extendedRangeToDelete.IsPositioned() ? extendedRangeToDelete 622 : rangeToDelete; 623 } 624 625 // static 626 Result<EditorDOMRangeInTexts, nsresult> 627 WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom( 628 Options aOptions, // NOLINT(performance-unnecessary-value-param) 629 const EditorDOMPoint& aPoint, 630 const Element* aAncestorLimiter /* = nullptr */) { 631 // Corresponding to computing delete range part of 632 // `WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace()` 633 MOZ_ASSERT(aPoint.IsSetAndValid()); 634 MOZ_ASSERT(!aOptions.contains(Option::ReferHTMLDefaultStyle)); 635 636 const TextFragmentData textFragmentDataAtCaret(aOptions, aPoint, 637 aAncestorLimiter); 638 if (NS_WARN_IF(!textFragmentDataAtCaret.IsInitialized())) { 639 return Err(NS_ERROR_FAILURE); 640 } 641 auto atCaret = 642 textFragmentDataAtCaret.GetInclusiveNextCharPoint<EditorDOMPointInText>( 643 aPoint, ShouldIgnoreNonEditableSiblingsOrDescendants(aOptions)); 644 if (!atCaret.IsSet()) { 645 return EditorDOMRangeInTexts(); // There is no content in the block. 646 } 647 // If caret is in middle of a surrogate pair, we should remove next 648 // character (blink-compat). 649 if (!atCaret.IsEndOfContainer() && 650 atCaret.IsCharLowSurrogateFollowingHighSurrogate()) { 651 atCaret = atCaret.NextPoint(); 652 } 653 654 // XXX When next char point is in an empty text node, we do nothing, 655 // but this must look odd from point of user view. We should delete 656 // something after aPoint. 657 if (atCaret.IsEndOfContainer()) { 658 return EditorDOMRangeInTexts(); 659 } 660 661 // Extend delete range if previous char is a low surrogate following 662 // a high surrogate. 663 EditorDOMPointInText atNextChar = atCaret.NextPoint(); 664 if (atCaret.IsCharHighSurrogateFollowedByLowSurrogate()) { 665 atNextChar = atNextChar.NextPoint(); 666 } 667 668 // If next char is a collapsible white-space, delete all adjacent white-spaces 669 // which are collapsed together. 670 EditorDOMRangeInTexts rangeToDelete; 671 if (atCaret.IsCharCollapsibleASCIISpace() || 672 atCaret.IsCharPreformattedNewLineCollapsedWithWhiteSpaces()) { 673 const auto startToDelete = 674 textFragmentDataAtCaret 675 .GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointInText>( 676 atCaret, nsIEditor::eNext, 677 ShouldIgnoreNonEditableSiblingsOrDescendants(aOptions)); 678 if (!startToDelete.IsSet()) { 679 NS_WARNING( 680 "WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed"); 681 return Err(NS_ERROR_FAILURE); 682 } 683 const EditorDOMPointInText endToDelete = 684 textFragmentDataAtCaret 685 .GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>( 686 atCaret, nsIEditor::eNext, 687 ShouldIgnoreNonEditableSiblingsOrDescendants(aOptions)); 688 if (!endToDelete.IsSet()) { 689 NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed"); 690 return Err(NS_ERROR_FAILURE); 691 } 692 rangeToDelete = EditorDOMRangeInTexts(startToDelete, endToDelete); 693 } 694 // if next char is not a collapsible white-space, remove it. 695 else { 696 rangeToDelete = EditorDOMRangeInTexts(atCaret, atNextChar); 697 } 698 699 // If there is no removable and visible content, we should do nothing. 700 if (rangeToDelete.Collapsed()) { 701 return EditorDOMRangeInTexts(); 702 } 703 704 // And also delete invisible white-spaces if they become visible. 705 const TextFragmentData textFragmentDataAtStart = 706 rangeToDelete.StartRef() != aPoint 707 ? TextFragmentData(aOptions, rangeToDelete.StartRef(), 708 aAncestorLimiter) 709 : textFragmentDataAtCaret; 710 const TextFragmentData textFragmentDataAtEnd = 711 rangeToDelete.EndRef() != aPoint 712 ? TextFragmentData(aOptions, rangeToDelete.EndRef(), aAncestorLimiter) 713 : textFragmentDataAtCaret; 714 if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized()) || 715 NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) { 716 return Err(NS_ERROR_FAILURE); 717 } 718 EditorDOMRangeInTexts extendedRangeToDelete = 719 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces( 720 textFragmentDataAtStart, textFragmentDataAtEnd); 721 MOZ_ASSERT(extendedRangeToDelete.IsPositionedAndValid()); 722 return extendedRangeToDelete.IsPositioned() ? extendedRangeToDelete 723 : rangeToDelete; 724 } 725 726 // static 727 EditorDOMRange WSRunScanner::GetRangesForDeletingAtomicContent( 728 Options aOptions, // NOLINT(performance-unnecessary-value-param) 729 const nsIContent& aAtomicContent, 730 const Element* aAncestorLimiter /* = nullptr */) { 731 MOZ_ASSERT(!aOptions.contains(Option::ReferHTMLDefaultStyle)); 732 733 if (aAtomicContent.IsHTMLElement(nsGkAtoms::br)) { 734 // Preceding white-spaces should be preserved, but the following 735 // white-spaces should be invisible around `<br>` element. 736 const TextFragmentData textFragmentDataAfterBRElement( 737 aOptions, EditorDOMPoint::After(aAtomicContent), aAncestorLimiter); 738 if (NS_WARN_IF(!textFragmentDataAfterBRElement.IsInitialized())) { 739 return EditorDOMRange(); // TODO: Make here return error with Err. 740 } 741 const EditorDOMRangeInTexts followingInvisibleWhiteSpaces = 742 textFragmentDataAfterBRElement.GetNonCollapsedRangeInTexts( 743 textFragmentDataAfterBRElement 744 .InvisibleLeadingWhiteSpaceRangeRef()); 745 return followingInvisibleWhiteSpaces.IsPositioned() && 746 !followingInvisibleWhiteSpaces.Collapsed() 747 ? EditorDOMRange( 748 EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)), 749 followingInvisibleWhiteSpaces.EndRef()) 750 : EditorDOMRange( 751 EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)), 752 EditorDOMPoint::After(aAtomicContent)); 753 } 754 755 if (!HTMLEditUtils::IsBlockElement( 756 aAtomicContent, BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 757 // Both preceding and following white-spaces around it should be preserved 758 // around inline elements like `<img>`. 759 return EditorDOMRange( 760 EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)), 761 EditorDOMPoint::After(aAtomicContent)); 762 } 763 764 // Both preceding and following white-spaces can be invisible around a 765 // block element. 766 const TextFragmentData textFragmentDataBeforeAtomicContent( 767 aOptions, EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)), 768 aAncestorLimiter); 769 if (NS_WARN_IF(!textFragmentDataBeforeAtomicContent.IsInitialized())) { 770 return EditorDOMRange(); // TODO: Make here return error with Err. 771 } 772 const EditorDOMRangeInTexts precedingInvisibleWhiteSpaces = 773 textFragmentDataBeforeAtomicContent.GetNonCollapsedRangeInTexts( 774 textFragmentDataBeforeAtomicContent 775 .InvisibleTrailingWhiteSpaceRangeRef()); 776 const TextFragmentData textFragmentDataAfterAtomicContent( 777 aOptions, EditorDOMPoint::After(aAtomicContent), aAncestorLimiter); 778 if (NS_WARN_IF(!textFragmentDataAfterAtomicContent.IsInitialized())) { 779 return EditorDOMRange(); // TODO: Make here return error with Err. 780 } 781 const EditorDOMRangeInTexts followingInvisibleWhiteSpaces = 782 textFragmentDataAfterAtomicContent.GetNonCollapsedRangeInTexts( 783 textFragmentDataAfterAtomicContent 784 .InvisibleLeadingWhiteSpaceRangeRef()); 785 if (precedingInvisibleWhiteSpaces.StartRef().IsSet() && 786 followingInvisibleWhiteSpaces.EndRef().IsSet()) { 787 return EditorDOMRange(precedingInvisibleWhiteSpaces.StartRef(), 788 followingInvisibleWhiteSpaces.EndRef()); 789 } 790 if (precedingInvisibleWhiteSpaces.StartRef().IsSet()) { 791 return EditorDOMRange(precedingInvisibleWhiteSpaces.StartRef(), 792 EditorDOMPoint::After(aAtomicContent)); 793 } 794 if (followingInvisibleWhiteSpaces.EndRef().IsSet()) { 795 return EditorDOMRange( 796 EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)), 797 followingInvisibleWhiteSpaces.EndRef()); 798 } 799 return EditorDOMRange( 800 EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)), 801 EditorDOMPoint::After(aAtomicContent)); 802 } 803 804 // static 805 EditorDOMRange WSRunScanner::GetRangeForDeletingBlockElementBoundaries( 806 Options aOptions, // NOLINT(performance-unnecessary-value-param) 807 const Element& aLeftBlockElement, const Element& aRightBlockElement, 808 const EditorDOMPoint& aPointContainingTheOtherBlock, 809 const Element* aAncestorLimiter /* = nullptr */) { 810 MOZ_ASSERT(&aLeftBlockElement != &aRightBlockElement); 811 MOZ_ASSERT_IF( 812 aPointContainingTheOtherBlock.IsSet(), 813 aPointContainingTheOtherBlock.GetContainer() == &aLeftBlockElement || 814 aPointContainingTheOtherBlock.GetContainer() == &aRightBlockElement); 815 MOZ_ASSERT_IF( 816 aPointContainingTheOtherBlock.GetContainer() == &aLeftBlockElement, 817 aRightBlockElement.IsInclusiveDescendantOf( 818 aPointContainingTheOtherBlock.GetChild())); 819 MOZ_ASSERT_IF( 820 aPointContainingTheOtherBlock.GetContainer() == &aRightBlockElement, 821 aLeftBlockElement.IsInclusiveDescendantOf( 822 aPointContainingTheOtherBlock.GetChild())); 823 MOZ_ASSERT_IF( 824 !aPointContainingTheOtherBlock.IsSet(), 825 !aRightBlockElement.IsInclusiveDescendantOf(&aLeftBlockElement)); 826 MOZ_ASSERT_IF( 827 !aPointContainingTheOtherBlock.IsSet(), 828 !aLeftBlockElement.IsInclusiveDescendantOf(&aRightBlockElement)); 829 MOZ_ASSERT_IF(!aPointContainingTheOtherBlock.IsSet(), 830 EditorRawDOMPoint(const_cast<Element*>(&aLeftBlockElement)) 831 .IsBefore(EditorRawDOMPoint( 832 const_cast<Element*>(&aRightBlockElement)))); 833 MOZ_ASSERT_IF(aAncestorLimiter, 834 aLeftBlockElement.IsInclusiveDescendantOf(aAncestorLimiter)); 835 MOZ_ASSERT_IF(aAncestorLimiter, 836 aRightBlockElement.IsInclusiveDescendantOf(aAncestorLimiter)); 837 MOZ_ASSERT_IF(aOptions.contains(Option::OnlyEditableNodes), 838 const_cast<Element&>(aLeftBlockElement).GetEditingHost() == 839 const_cast<Element&>(aRightBlockElement).GetEditingHost()); 840 MOZ_ASSERT(!aOptions.contains(Option::ReferHTMLDefaultStyle)); 841 842 EditorDOMRange range; 843 // Include trailing invisible white-spaces in aLeftBlockElement. 844 const TextFragmentData textFragmentDataAtEndOfLeftBlockElement( 845 aOptions, 846 aPointContainingTheOtherBlock.GetContainer() == &aLeftBlockElement 847 ? aPointContainingTheOtherBlock 848 : EditorDOMPoint::AtEndOf(const_cast<Element&>(aLeftBlockElement)), 849 aAncestorLimiter); 850 if (NS_WARN_IF(!textFragmentDataAtEndOfLeftBlockElement.IsInitialized())) { 851 return EditorDOMRange(); // TODO: Make here return error with Err. 852 } 853 if (textFragmentDataAtEndOfLeftBlockElement.StartsFromInvisibleBRElement()) { 854 // If the left block element ends with an invisible `<br>` element, 855 // it'll be deleted (and it means there is no invisible trailing 856 // white-spaces). Therefore, the range should start from the invisible 857 // `<br>` element. 858 range.SetStart(EditorDOMPoint( 859 textFragmentDataAtEndOfLeftBlockElement.StartReasonBRElementPtr())); 860 } else { 861 const EditorDOMRange& trailingWhiteSpaceRange = 862 textFragmentDataAtEndOfLeftBlockElement 863 .InvisibleTrailingWhiteSpaceRangeRef(); 864 if (trailingWhiteSpaceRange.StartRef().IsSet()) { 865 range.SetStart(trailingWhiteSpaceRange.StartRef()); 866 } else { 867 range.SetStart(textFragmentDataAtEndOfLeftBlockElement.ScanStartRef()); 868 } 869 } 870 // Include leading invisible white-spaces in aRightBlockElement. 871 const TextFragmentData textFragmentDataAtStartOfRightBlockElement( 872 aOptions, 873 aPointContainingTheOtherBlock.GetContainer() == &aRightBlockElement && 874 !aPointContainingTheOtherBlock.IsEndOfContainer() 875 ? aPointContainingTheOtherBlock.NextPoint() 876 : EditorDOMPoint(const_cast<Element*>(&aRightBlockElement), 0u), 877 aAncestorLimiter); 878 if (NS_WARN_IF(!textFragmentDataAtStartOfRightBlockElement.IsInitialized())) { 879 return EditorDOMRange(); // TODO: Make here return error with Err. 880 } 881 const EditorDOMRange& leadingWhiteSpaceRange = 882 textFragmentDataAtStartOfRightBlockElement 883 .InvisibleLeadingWhiteSpaceRangeRef(); 884 if (leadingWhiteSpaceRange.EndRef().IsSet()) { 885 range.SetEnd(leadingWhiteSpaceRange.EndRef()); 886 } else { 887 range.SetEnd(textFragmentDataAtStartOfRightBlockElement.ScanStartRef()); 888 } 889 return range; 890 } 891 892 // static 893 EditorDOMRange 894 WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries( 895 Options aOptions, // NOLINT(performance-unnecessary-value-param) 896 const EditorDOMRange& aRange, 897 const Element* aAncestorLimiter /* = nullptr */) { 898 MOZ_ASSERT(aRange.IsPositionedAndValid()); 899 MOZ_ASSERT(aRange.EndRef().IsSetAndValid()); 900 MOZ_ASSERT(aRange.StartRef().IsSetAndValid()); 901 MOZ_ASSERT(!aOptions.contains(Option::ReferHTMLDefaultStyle)); 902 903 EditorDOMRange result; 904 const TextFragmentData textFragmentDataAtStart(aOptions, aRange.StartRef(), 905 aAncestorLimiter); 906 if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized())) { 907 return EditorDOMRange(); // TODO: Make here return error with Err. 908 } 909 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtStart = 910 textFragmentDataAtStart.GetNonCollapsedRangeInTexts( 911 textFragmentDataAtStart.InvisibleLeadingWhiteSpaceRangeRef()); 912 if (invisibleLeadingWhiteSpacesAtStart.IsPositioned() && 913 !invisibleLeadingWhiteSpacesAtStart.Collapsed()) { 914 result.SetStart(invisibleLeadingWhiteSpacesAtStart.StartRef()); 915 } else { 916 const EditorDOMRangeInTexts invisibleTrailingWhiteSpacesAtStart = 917 textFragmentDataAtStart.GetNonCollapsedRangeInTexts( 918 textFragmentDataAtStart.InvisibleTrailingWhiteSpaceRangeRef()); 919 if (invisibleTrailingWhiteSpacesAtStart.IsPositioned() && 920 !invisibleTrailingWhiteSpacesAtStart.Collapsed()) { 921 MOZ_ASSERT( 922 invisibleTrailingWhiteSpacesAtStart.StartRef().EqualsOrIsBefore( 923 aRange.StartRef())); 924 result.SetStart(invisibleTrailingWhiteSpacesAtStart.StartRef()); 925 } 926 // If there is no invisible white-space and the line starts with a 927 // text node, shrink the range to start of the text node. 928 else if (!aRange.StartRef().IsInTextNode() && 929 (textFragmentDataAtStart.StartsFromBlockBoundary() || 930 textFragmentDataAtStart.StartsFromInlineEditingHostBoundary()) && 931 textFragmentDataAtStart.EndRef().IsInTextNode()) { 932 result.SetStart(textFragmentDataAtStart.EndRef()); 933 } 934 } 935 if (!result.StartRef().IsSet()) { 936 result.SetStart(aRange.StartRef()); 937 } 938 939 const TextFragmentData textFragmentDataAtEnd(aOptions, aRange.EndRef(), 940 aAncestorLimiter); 941 if (NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) { 942 return EditorDOMRange(); // TODO: Make here return error with Err. 943 } 944 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd = 945 textFragmentDataAtEnd.GetNonCollapsedRangeInTexts( 946 textFragmentDataAtEnd.InvisibleTrailingWhiteSpaceRangeRef()); 947 if (invisibleLeadingWhiteSpacesAtEnd.IsPositioned() && 948 !invisibleLeadingWhiteSpacesAtEnd.Collapsed()) { 949 result.SetEnd(invisibleLeadingWhiteSpacesAtEnd.EndRef()); 950 } else { 951 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd = 952 textFragmentDataAtEnd.GetNonCollapsedRangeInTexts( 953 textFragmentDataAtEnd.InvisibleLeadingWhiteSpaceRangeRef()); 954 if (invisibleLeadingWhiteSpacesAtEnd.IsPositioned() && 955 !invisibleLeadingWhiteSpacesAtEnd.Collapsed()) { 956 MOZ_ASSERT(aRange.EndRef().EqualsOrIsBefore( 957 invisibleLeadingWhiteSpacesAtEnd.EndRef())); 958 result.SetEnd(invisibleLeadingWhiteSpacesAtEnd.EndRef()); 959 } 960 // If there is no invisible white-space and the line ends with a text 961 // node, shrink the range to end of the text node. 962 else if (!aRange.EndRef().IsInTextNode() && 963 (textFragmentDataAtEnd.EndsByBlockBoundary() || 964 textFragmentDataAtEnd.EndsByInlineEditingHostBoundary()) && 965 textFragmentDataAtEnd.StartRef().IsInTextNode()) { 966 result.SetEnd(EditorDOMPoint::AtEndOf( 967 *textFragmentDataAtEnd.StartRef().ContainerAs<Text>())); 968 } 969 } 970 if (!result.EndRef().IsSet()) { 971 result.SetEnd(aRange.EndRef()); 972 } 973 MOZ_ASSERT(result.IsPositionedAndValid()); 974 return result; 975 } 976 977 /****************************************************************************** 978 * Utilities for other things. 979 ******************************************************************************/ 980 981 // static 982 Result<bool, nsresult> 983 WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent( 984 Options aOptions, // NOLINT(performance-unnecessary-value-param) 985 nsRange& aRange, const Element* aAncestorLimiter /* = nullptr */) { 986 MOZ_ASSERT(aRange.IsPositioned()); 987 MOZ_ASSERT(!aRange.IsInAnySelection(), 988 "Changing range in selection may cause running script"); 989 MOZ_ASSERT(!aOptions.contains(Option::ReferHTMLDefaultStyle)); 990 991 if (NS_WARN_IF(!aRange.GetStartContainer()) || 992 NS_WARN_IF(!aRange.GetEndContainer())) { 993 return Err(NS_ERROR_FAILURE); 994 } 995 996 if (!aRange.GetStartContainer()->IsContent() || 997 !aRange.GetEndContainer()->IsContent()) { 998 return false; 999 } 1000 1001 // If the range crosses a block boundary, we should do nothing for now 1002 // because it hits a bug of inserting a padding `<br>` element after 1003 // joining the blocks. 1004 if (HTMLEditUtils::GetInclusiveAncestorElement( 1005 *aRange.GetStartContainer()->AsContent(), 1006 aOptions.contains(Option::OnlyEditableNodes) 1007 ? HTMLEditUtils::ClosestEditableBlockElementExceptHRElement 1008 : HTMLEditUtils::ClosestBlockElementExceptHRElement, 1009 BlockInlineCheck::UseComputedDisplayStyle) != 1010 HTMLEditUtils::GetInclusiveAncestorElement( 1011 *aRange.GetEndContainer()->AsContent(), 1012 aOptions.contains(Option::OnlyEditableNodes) 1013 ? HTMLEditUtils::ClosestEditableBlockElementExceptHRElement 1014 : HTMLEditUtils::ClosestBlockElementExceptHRElement, 1015 BlockInlineCheck::UseComputedDisplayStyle)) { 1016 return false; 1017 } 1018 1019 nsIContent* startContent = nullptr; 1020 if (aRange.GetStartContainer() && aRange.GetStartContainer()->IsText() && 1021 aRange.GetStartContainer()->AsText()->Length() == aRange.StartOffset()) { 1022 // If next content is a visible `<br>` element, special inline content 1023 // (e.g., `<img>`, non-editable text node, etc) or a block level void 1024 // element like `<hr>`, the range should start with it. 1025 const TextFragmentData textFragmentDataAtStart( 1026 aOptions, EditorRawDOMPoint(aRange.StartRef()), aAncestorLimiter); 1027 if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized())) { 1028 return Err(NS_ERROR_FAILURE); 1029 } 1030 if (textFragmentDataAtStart.EndsByVisibleBRElement()) { 1031 startContent = textFragmentDataAtStart.EndReasonBRElementPtr(); 1032 } else if (textFragmentDataAtStart.EndsBySpecialContent() || 1033 (textFragmentDataAtStart.EndsByOtherBlockElement() && 1034 !HTMLEditUtils::IsContainerNode( 1035 *textFragmentDataAtStart 1036 .EndReasonOtherBlockElementPtr()))) { 1037 startContent = textFragmentDataAtStart.GetEndReasonContent(); 1038 } 1039 } 1040 1041 nsIContent* endContent = nullptr; 1042 if (aRange.GetEndContainer() && aRange.GetEndContainer()->IsText() && 1043 !aRange.EndOffset()) { 1044 // If previous content is a visible `<br>` element, special inline content 1045 // (e.g., `<img>`, non-editable text node, etc) or a block level void 1046 // element like `<hr>`, the range should end after it. 1047 const TextFragmentData textFragmentDataAtEnd( 1048 aOptions, EditorRawDOMPoint(aRange.EndRef()), aAncestorLimiter); 1049 if (NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) { 1050 return Err(NS_ERROR_FAILURE); 1051 } 1052 if (textFragmentDataAtEnd.StartsFromVisibleBRElement()) { 1053 endContent = textFragmentDataAtEnd.StartReasonBRElementPtr(); 1054 } else if (textFragmentDataAtEnd.StartsFromSpecialContent() || 1055 (textFragmentDataAtEnd.StartsFromOtherBlockElement() && 1056 !HTMLEditUtils::IsContainerNode( 1057 *textFragmentDataAtEnd 1058 .StartReasonOtherBlockElementPtr()))) { 1059 endContent = textFragmentDataAtEnd.GetStartReasonContent(); 1060 } 1061 } 1062 1063 if (!startContent && !endContent) { 1064 return false; 1065 } 1066 1067 nsresult rv = aRange.SetStartAndEnd( 1068 startContent ? RangeBoundary( 1069 startContent->GetParentNode(), 1070 startContent->GetPreviousSibling()) // at startContent 1071 : aRange.StartRef(), 1072 endContent ? RangeBoundary(endContent->GetParentNode(), 1073 endContent) // after endContent 1074 : aRange.EndRef()); 1075 if (NS_FAILED(rv)) { 1076 NS_WARNING("nsRange::SetStartAndEnd() failed"); 1077 return Err(rv); 1078 } 1079 return true; 1080 } 1081 1082 } // namespace mozilla