WSRunScannerNestedClasses.cpp (63962B)
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 "EditorUtils.h" 10 #include "HTMLEditUtils.h" 11 12 #include "mozilla/Assertions.h" 13 #include "mozilla/dom/AncestorIterator.h" 14 #include "mozilla/dom/CharacterDataBuffer.h" 15 16 #include "nsCRT.h" 17 #include "nsDebug.h" 18 #include "nsIContent.h" 19 #include "nsIContentInlines.h" 20 21 namespace mozilla { 22 23 using namespace dom; 24 25 using AncestorType = HTMLEditUtils::AncestorType; 26 using AncestorTypes = HTMLEditUtils::AncestorTypes; 27 using LeafNodeType = HTMLEditUtils::LeafNodeType; 28 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; 29 30 template WSRunScanner::TextFragmentData::TextFragmentData(Options, 31 const EditorDOMPoint&, 32 const Element*); 33 template WSRunScanner::TextFragmentData::TextFragmentData( 34 Options, const EditorRawDOMPoint&, const Element*); 35 template WSRunScanner::TextFragmentData::TextFragmentData( 36 Options, const EditorDOMPointInText&, const Element*); 37 template WSRunScanner::TextFragmentData::TextFragmentData( 38 Options, const EditorRawDOMPointInText&, const Element*); 39 40 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT( 41 WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint, 42 const EditorDOMPoint&, Options, IgnoreNonEditableNodes, const nsIContent*); 43 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT( 44 WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint, 45 const EditorRawDOMPoint&, Options, IgnoreNonEditableNodes, 46 const nsIContent*); 47 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT( 48 WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint, 49 const EditorDOMPointInText&, Options, IgnoreNonEditableNodes, 50 const nsIContent*); 51 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT( 52 WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint, 53 const EditorRawDOMPointInText&, Options, IgnoreNonEditableNodes, 54 const nsIContent*); 55 56 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT( 57 WSRunScanner::TextFragmentData::GetPreviousCharPoint, const EditorDOMPoint&, 58 Options, IgnoreNonEditableNodes, const nsIContent*); 59 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT( 60 WSRunScanner::TextFragmentData::GetPreviousCharPoint, 61 const EditorRawDOMPoint&, Options, IgnoreNonEditableNodes, 62 const nsIContent*); 63 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT( 64 WSRunScanner::TextFragmentData::GetPreviousCharPoint, 65 const EditorDOMPointInText&, Options, IgnoreNonEditableNodes, 66 const nsIContent*); 67 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT( 68 WSRunScanner::TextFragmentData::GetPreviousCharPoint, 69 const EditorRawDOMPointInText&, Options, IgnoreNonEditableNodes, 70 const nsIContent*); 71 72 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT( 73 WSRunScanner::TextFragmentData::GetEndOfCollapsibleASCIIWhiteSpaces, 74 const EditorDOMPointInText&, nsIEditor::EDirection, Options, 75 IgnoreNonEditableNodes, const nsIContent*); 76 77 NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT( 78 WSRunScanner::TextFragmentData::GetFirstASCIIWhiteSpacePointCollapsedTo, 79 const EditorDOMPointInText&, nsIEditor::EDirection, Options, 80 IgnoreNonEditableNodes, const nsIContent*); 81 82 // FIXME: I think the scanner should not cross the <button> element boundaries. 83 constexpr static const AncestorTypes kScanAnyRootAncestorTypes = { 84 // If the point is in a block, we need to scan only in the block 85 AncestorType::ClosestBlockElement, 86 // So, we want a root element of the (shadow) tree root element of the 87 // point if there is no parent block 88 AncestorType::ReturnAncestorLimiterIfNoProperAncestor, 89 // Basically, given point shouldn't be a void element, so, ignore 90 // ancestor 91 // void elements 92 AncestorType::IgnoreHRElement}; 93 constexpr static const AncestorTypes kScanEditableRootAncestorTypes = { 94 // Only editable elements 95 AncestorType::EditableElement, 96 // And the others are same as kScanAnyRootAncestorTypes 97 AncestorType::ClosestBlockElement, 98 AncestorType::ReturnAncestorLimiterIfNoProperAncestor, 99 AncestorType::IgnoreHRElement}; 100 101 template <typename EditorDOMPointType> 102 WSRunScanner::TextFragmentData::TextFragmentData( 103 Options aOptions, // NOLINT(performance-unnecessary-value-param) 104 const EditorDOMPointType& aPoint, 105 const Element* aAncestorLimiter /* = nullptr */) 106 : mAncestorLimiter(aAncestorLimiter), mOptions(aOptions) { 107 if (NS_WARN_IF(!aPoint.IsInContentNodeAndValidInComposedDoc()) || 108 NS_WARN_IF(!aPoint.GetContainerOrContainerParentElement())) { 109 // We don't need to support composing in uncomposed tree. 110 return; 111 } 112 113 MOZ_ASSERT_IF( 114 aAncestorLimiter, 115 aPoint.template ContainerAs<nsIContent>()->IsInclusiveDescendantOf( 116 aAncestorLimiter)); 117 118 mScanStartPoint = aPoint.template To<EditorDOMPoint>(); 119 const Element* const 120 editableBlockElementOrInlineEditingHostOrNonEditableRootElement = 121 HTMLEditUtils::GetInclusiveAncestorElement( 122 *mScanStartPoint.ContainerAs<nsIContent>(), 123 mOptions.contains(Option::OnlyEditableNodes) 124 ? kScanEditableRootAncestorTypes 125 : kScanAnyRootAncestorTypes, 126 ReferredHTMLDefaultStyle() 127 ? BlockInlineCheck::UseHTMLDefaultStyle 128 : BlockInlineCheck::UseComputedDisplayOutsideStyle, 129 aAncestorLimiter); 130 if (NS_WARN_IF( 131 !editableBlockElementOrInlineEditingHostOrNonEditableRootElement)) { 132 return; 133 } 134 mStart = BoundaryData::ScanCollapsibleWhiteSpaceStartFrom( 135 mOptions, mScanStartPoint, &mNBSPData, 136 *editableBlockElementOrInlineEditingHostOrNonEditableRootElement); 137 MOZ_ASSERT_IF(mStart.IsNonCollapsibleCharacters(), 138 !mStart.PointRef().IsPreviousCharPreformattedNewLine()); 139 MOZ_ASSERT_IF(mStart.IsPreformattedLineBreak(), 140 mStart.PointRef().IsPreviousCharPreformattedNewLine()); 141 mEnd = BoundaryData::ScanCollapsibleWhiteSpaceEndFrom( 142 mOptions, mScanStartPoint, &mNBSPData, 143 *editableBlockElementOrInlineEditingHostOrNonEditableRootElement); 144 MOZ_ASSERT_IF(mEnd.IsNonCollapsibleCharacters(), 145 !mEnd.PointRef().IsCharPreformattedNewLine()); 146 MOZ_ASSERT_IF(mEnd.IsPreformattedLineBreak(), 147 mEnd.PointRef().IsCharPreformattedNewLine()); 148 } 149 150 // static 151 template <typename EditorDOMPointType> 152 Maybe<WSRunScanner::TextFragmentData::BoundaryData> WSRunScanner:: 153 TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode( 154 const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData) { 155 MOZ_ASSERT(aPoint.IsSetAndValid()); 156 MOZ_DIAGNOSTIC_ASSERT(aPoint.IsInTextNode()); 157 158 const bool isWhiteSpaceCollapsible = !EditorUtils::IsWhiteSpacePreformatted( 159 *aPoint.template ContainerAs<Text>()); 160 const bool isNewLineCollapsible = 161 isWhiteSpaceCollapsible && 162 !EditorUtils::IsNewLinePreformatted(*aPoint.template ContainerAs<Text>()); 163 const bool isNewLineLineBreak = 164 EditorUtils::IsNewLinePreformatted(*aPoint.template ContainerAs<Text>()); 165 const CharacterDataBuffer& characterDataBuffer = 166 aPoint.template ContainerAs<Text>()->DataBuffer(); 167 for (uint32_t i = std::min(aPoint.Offset(), characterDataBuffer.GetLength()); 168 i; i--) { 169 WSType wsTypeOfNonCollapsibleChar; 170 switch (characterDataBuffer.CharAt(i - 1)) { 171 case HTMLEditUtils::kSpace: 172 case HTMLEditUtils::kCarriageReturn: 173 case HTMLEditUtils::kTab: 174 if (isWhiteSpaceCollapsible) { 175 continue; // collapsible white-space or invisible white-space. 176 } 177 // preformatted white-space. 178 wsTypeOfNonCollapsibleChar = WSType::NonCollapsibleCharacters; 179 break; 180 case HTMLEditUtils::kNewLine: 181 if (isNewLineCollapsible) { 182 continue; // collapsible linefeed. 183 } 184 // preformatted linefeed or replaced with a non-collapsible white-space. 185 wsTypeOfNonCollapsibleChar = isNewLineLineBreak 186 ? WSType::PreformattedLineBreak 187 : WSType::NonCollapsibleCharacters; 188 break; 189 case HTMLEditUtils::kNBSP: 190 if (isWhiteSpaceCollapsible) { 191 if (aNBSPData) { 192 aNBSPData->NotifyNBSP( 193 EditorDOMPointInText(aPoint.template ContainerAs<Text>(), 194 i - 1), 195 NoBreakingSpaceData::Scanning::Backward); 196 } 197 continue; 198 } 199 // NBSP is never converted from collapsible white-space/linefeed. 200 wsTypeOfNonCollapsibleChar = WSType::NonCollapsibleCharacters; 201 break; 202 default: 203 MOZ_ASSERT(!nsCRT::IsAsciiSpace(characterDataBuffer.CharAt(i - 1))); 204 wsTypeOfNonCollapsibleChar = WSType::NonCollapsibleCharacters; 205 break; 206 } 207 208 return Some(BoundaryData( 209 EditorDOMPoint(aPoint.template ContainerAs<Text>(), i), 210 *aPoint.template ContainerAs<Text>(), wsTypeOfNonCollapsibleChar)); 211 } 212 213 return Nothing(); 214 } 215 216 // static 217 template <typename EditorDOMPointType> 218 WSRunScanner::TextFragmentData::BoundaryData WSRunScanner::TextFragmentData:: 219 BoundaryData::ScanCollapsibleWhiteSpaceStartFrom( 220 Options aOptions, // NOLINT(performance-unnecessary-value-param) 221 const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData, 222 const Element& aAncestorLimiter) { 223 MOZ_ASSERT(aPoint.IsSetAndValid()); 224 MOZ_ASSERT_IF(aOptions.contains(Option::OnlyEditableNodes), 225 // FIXME: Both values should be true here. 226 HTMLEditUtils::IsSimplyEditableNode(*aPoint.GetContainer()) == 227 HTMLEditUtils::IsSimplyEditableNode(aAncestorLimiter)); 228 229 if (aPoint.IsInTextNode() && !aPoint.IsStartOfContainer()) { 230 Maybe<BoundaryData> startInTextNode = 231 BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(aPoint, 232 aNBSPData); 233 if (startInTextNode.isSome()) { 234 return startInTextNode.ref(); 235 } 236 // The text node does not have visible character, let's keep scanning 237 // preceding nodes. 238 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom( 239 aOptions, EditorDOMPoint(aPoint.template ContainerAs<Text>(), 0), 240 aNBSPData, aAncestorLimiter); 241 } 242 243 const BlockInlineCheck blockInlineCheck = 244 aOptions.contains(Option::ReferHTMLDefaultStyle) 245 ? BlockInlineCheck::UseHTMLDefaultStyle 246 : BlockInlineCheck::Auto; 247 // Then, we need to check previous leaf node. 248 const auto leafNodeTypes = [&]() -> LeafNodeTypes { 249 auto types = aOptions.contains(Option::OnlyEditableNodes) 250 ? LeafNodeTypes{LeafNodeType::LeafNodeOrNonEditableNode} 251 : LeafNodeTypes{LeafNodeType::OnlyLeafNode}; 252 if (aOptions.contains(Option::StopAtComment)) { 253 types += LeafNodeType::TreatCommentAsLeafNode; 254 } 255 return types; 256 }(); 257 nsIContent* previousLeafContentOrBlock = 258 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( 259 aPoint, leafNodeTypes, blockInlineCheck, &aAncestorLimiter); 260 if (!previousLeafContentOrBlock) { 261 // No previous content means that we reached the aAncestorLimiter boundary. 262 return BoundaryData( 263 aPoint, const_cast<Element&>(aAncestorLimiter), 264 HTMLEditUtils::IsBlockElement( 265 aAncestorLimiter, UseComputedDisplayStyleIfAuto(blockInlineCheck)) 266 ? WSType::CurrentBlockBoundary 267 : WSType::InlineEditingHostBoundary); 268 } 269 270 if (HTMLEditUtils::IsBlockElement( 271 *previousLeafContentOrBlock, 272 UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck))) { 273 return BoundaryData(aPoint, *previousLeafContentOrBlock, 274 WSType::OtherBlockBoundary); 275 } 276 277 if (!previousLeafContentOrBlock->IsText() || 278 (aOptions.contains(Option::OnlyEditableNodes) && 279 HTMLEditUtils::IsSimplyEditableNode(*previousLeafContentOrBlock) != 280 HTMLEditUtils::IsSimplyEditableNode(aAncestorLimiter))) { 281 // it's a break or a special node, like <img>, that is not a block and 282 // not a break but still serves as a terminator to ws runs. 283 return BoundaryData(aPoint, *previousLeafContentOrBlock, 284 previousLeafContentOrBlock->IsHTMLElement(nsGkAtoms::br) 285 ? WSType::BRElement 286 : WSType::SpecialContent); 287 } 288 289 if (!previousLeafContentOrBlock->AsText()->TextLength()) { 290 // If it's an empty text node, keep looking for its previous leaf content. 291 // Note that even if the empty text node is preformatted, we should keep 292 // looking for the previous one. 293 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom( 294 aOptions, EditorDOMPointInText(previousLeafContentOrBlock->AsText(), 0), 295 aNBSPData, aAncestorLimiter); 296 } 297 298 Maybe<BoundaryData> startInTextNode = 299 BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode( 300 EditorDOMPointInText::AtEndOf(*previousLeafContentOrBlock->AsText()), 301 aNBSPData); 302 if (startInTextNode.isSome()) { 303 return startInTextNode.ref(); 304 } 305 306 // The text node does not have visible character, let's keep scanning 307 // preceding nodes. 308 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom( 309 aOptions, EditorDOMPointInText(previousLeafContentOrBlock->AsText(), 0), 310 aNBSPData, aAncestorLimiter); 311 } 312 313 // static 314 template <typename EditorDOMPointType> 315 Maybe<WSRunScanner::TextFragmentData::BoundaryData> WSRunScanner:: 316 TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode( 317 const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData) { 318 MOZ_ASSERT(aPoint.IsSetAndValid()); 319 MOZ_DIAGNOSTIC_ASSERT(aPoint.IsInTextNode()); 320 321 const bool isWhiteSpaceCollapsible = !EditorUtils::IsWhiteSpacePreformatted( 322 *aPoint.template ContainerAs<Text>()); 323 const bool isNewLineCollapsible = 324 isWhiteSpaceCollapsible && 325 !EditorUtils::IsNewLinePreformatted(*aPoint.template ContainerAs<Text>()); 326 const bool isNewLineLineBreak = 327 EditorUtils::IsNewLinePreformatted(*aPoint.template ContainerAs<Text>()); 328 const CharacterDataBuffer& characterDataBuffer = 329 aPoint.template ContainerAs<Text>()->DataBuffer(); 330 for (uint32_t i = aPoint.Offset(); i < characterDataBuffer.GetLength(); i++) { 331 WSType wsTypeOfNonCollapsibleChar; 332 switch (characterDataBuffer.CharAt(i)) { 333 case HTMLEditUtils::kSpace: 334 case HTMLEditUtils::kCarriageReturn: 335 case HTMLEditUtils::kTab: 336 if (isWhiteSpaceCollapsible) { 337 continue; // collapsible white-space or invisible white-space. 338 } 339 // preformatted white-space. 340 wsTypeOfNonCollapsibleChar = WSType::NonCollapsibleCharacters; 341 break; 342 case HTMLEditUtils::kNewLine: 343 if (isNewLineCollapsible) { 344 continue; // collapsible linefeed. 345 } 346 // preformatted linefeed or replaced with a non-collapsible white-space. 347 wsTypeOfNonCollapsibleChar = isNewLineLineBreak 348 ? WSType::PreformattedLineBreak 349 : WSType::NonCollapsibleCharacters; 350 break; 351 case HTMLEditUtils::kNBSP: 352 if (isWhiteSpaceCollapsible) { 353 if (aNBSPData) { 354 aNBSPData->NotifyNBSP( 355 EditorDOMPointInText(aPoint.template ContainerAs<Text>(), i), 356 NoBreakingSpaceData::Scanning::Forward); 357 } 358 continue; 359 } 360 // NBSP is never converted from collapsible white-space/linefeed. 361 wsTypeOfNonCollapsibleChar = WSType::NonCollapsibleCharacters; 362 break; 363 default: 364 MOZ_ASSERT(!nsCRT::IsAsciiSpace(characterDataBuffer.CharAt(i))); 365 wsTypeOfNonCollapsibleChar = WSType::NonCollapsibleCharacters; 366 break; 367 } 368 369 return Some(BoundaryData( 370 EditorDOMPoint(aPoint.template ContainerAs<Text>(), i), 371 *aPoint.template ContainerAs<Text>(), wsTypeOfNonCollapsibleChar)); 372 } 373 374 return Nothing(); 375 } 376 377 // static 378 template <typename EditorDOMPointType> 379 WSRunScanner::TextFragmentData::BoundaryData 380 WSRunScanner::TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndFrom( 381 Options aOptions, // NOLINT(performance-unnecessary-value-param) 382 const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData, 383 const Element& aAncestorLimiter) { 384 MOZ_ASSERT(aPoint.IsSetAndValid()); 385 MOZ_ASSERT_IF(aOptions.contains(Option::OnlyEditableNodes), 386 // FIXME: Both values should be true here. 387 HTMLEditUtils::IsSimplyEditableNode(*aPoint.GetContainer()) == 388 HTMLEditUtils::IsSimplyEditableNode(aAncestorLimiter)); 389 390 if (aPoint.IsInTextNode() && !aPoint.IsEndOfContainer()) { 391 Maybe<BoundaryData> endInTextNode = 392 BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(aPoint, aNBSPData); 393 if (endInTextNode.isSome()) { 394 return endInTextNode.ref(); 395 } 396 // The text node does not have visible character, let's keep scanning 397 // following nodes. 398 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom( 399 aOptions, 400 EditorDOMPointInText::AtEndOf(*aPoint.template ContainerAs<Text>()), 401 aNBSPData, aAncestorLimiter); 402 } 403 404 const BlockInlineCheck blockInlineCheck = 405 aOptions.contains(Option::ReferHTMLDefaultStyle) 406 ? BlockInlineCheck::UseHTMLDefaultStyle 407 : BlockInlineCheck::Auto; 408 409 // Then, we need to check next leaf node. 410 const auto leafNodeTypes = [&]() -> LeafNodeTypes { 411 auto types = aOptions.contains(Option::OnlyEditableNodes) 412 ? LeafNodeTypes{LeafNodeType::LeafNodeOrNonEditableNode} 413 : LeafNodeTypes{LeafNodeType::OnlyLeafNode}; 414 if (aOptions.contains(Option::StopAtComment)) { 415 types += LeafNodeType::TreatCommentAsLeafNode; 416 } 417 return types; 418 }(); 419 nsIContent* nextLeafContentOrBlock = 420 HTMLEditUtils::GetNextLeafContentOrNextBlockElement( 421 aPoint, leafNodeTypes, blockInlineCheck, &aAncestorLimiter); 422 if (!nextLeafContentOrBlock) { 423 // No next content means that we reached aAncestorLimiter boundary. 424 return BoundaryData( 425 aPoint.template To<EditorDOMPoint>(), 426 const_cast<Element&>(aAncestorLimiter), 427 HTMLEditUtils::IsBlockElement( 428 aAncestorLimiter, UseComputedDisplayStyleIfAuto(blockInlineCheck)) 429 ? WSType::CurrentBlockBoundary 430 : WSType::InlineEditingHostBoundary); 431 } 432 433 if (HTMLEditUtils::IsBlockElement( 434 *nextLeafContentOrBlock, 435 UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck))) { 436 // we encountered a new block. therefore no more ws. 437 return BoundaryData(aPoint, *nextLeafContentOrBlock, 438 WSType::OtherBlockBoundary); 439 } 440 441 if (!nextLeafContentOrBlock->IsText() || 442 (aOptions.contains(Option::OnlyEditableNodes) && 443 HTMLEditUtils::IsSimplyEditableNode(*nextLeafContentOrBlock) != 444 HTMLEditUtils::IsSimplyEditableNode(aAncestorLimiter))) { 445 // we encountered a break or a special node, like <img>, 446 // that is not a block and not a break but still 447 // serves as a terminator to ws runs. 448 return BoundaryData(aPoint, *nextLeafContentOrBlock, 449 nextLeafContentOrBlock->IsHTMLElement(nsGkAtoms::br) 450 ? WSType::BRElement 451 : WSType::SpecialContent); 452 } 453 454 if (!nextLeafContentOrBlock->AsText()->DataBuffer().GetLength()) { 455 // If it's an empty text node, keep looking for its next leaf content. 456 // Note that even if the empty text node is preformatted, we should keep 457 // looking for the next one. 458 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom( 459 aOptions, EditorDOMPointInText(nextLeafContentOrBlock->AsText(), 0), 460 aNBSPData, aAncestorLimiter); 461 } 462 463 Maybe<BoundaryData> endInTextNode = 464 BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode( 465 EditorDOMPointInText(nextLeafContentOrBlock->AsText(), 0), aNBSPData); 466 if (endInTextNode.isSome()) { 467 return endInTextNode.ref(); 468 } 469 470 // The text node does not have visible character, let's keep scanning 471 // following nodes. 472 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom( 473 aOptions, 474 EditorDOMPointInText::AtEndOf(*nextLeafContentOrBlock->AsText()), 475 aNBSPData, aAncestorLimiter); 476 } 477 478 const EditorDOMRange& 479 WSRunScanner::TextFragmentData::InvisibleLeadingWhiteSpaceRangeRef() const { 480 if (mLeadingWhiteSpaceRange.isSome()) { 481 return mLeadingWhiteSpaceRange.ref(); 482 } 483 484 // If it's start of line, there is no invisible leading white-spaces. 485 if (!StartsFromHardLineBreak() && !StartsFromInlineEditingHostBoundary()) { 486 mLeadingWhiteSpaceRange.emplace(); 487 return mLeadingWhiteSpaceRange.ref(); 488 } 489 490 // If there is no NBSP, all of the given range is leading white-spaces. 491 // Note that this result may be collapsed if there is no leading white-spaces. 492 if (!mNBSPData.FoundNBSP()) { 493 MOZ_ASSERT(mStart.PointRef().IsSet() || mEnd.PointRef().IsSet()); 494 mLeadingWhiteSpaceRange.emplace(mStart.PointRef(), mEnd.PointRef()); 495 return mLeadingWhiteSpaceRange.ref(); 496 } 497 498 MOZ_ASSERT(mNBSPData.LastPointRef().IsSetAndValid()); 499 500 // Even if the first NBSP is the start, i.e., there is no invisible leading 501 // white-space, return collapsed range. 502 mLeadingWhiteSpaceRange.emplace(mStart.PointRef(), mNBSPData.FirstPointRef()); 503 return mLeadingWhiteSpaceRange.ref(); 504 } 505 506 const EditorDOMRange& 507 WSRunScanner::TextFragmentData::InvisibleTrailingWhiteSpaceRangeRef() const { 508 if (mTrailingWhiteSpaceRange.isSome()) { 509 return mTrailingWhiteSpaceRange.ref(); 510 } 511 512 // If it's not immediately before a block boundary, there is no invisible 513 // trailing white-spaces. Note that a collapsible white-space before a <br> 514 // element or a preformatted linefeed is visible. 515 if (!EndsByBlockBoundary() && !EndsByInlineEditingHostBoundary()) { 516 mTrailingWhiteSpaceRange.emplace(); 517 return mTrailingWhiteSpaceRange.ref(); 518 } 519 520 // If there is no NBSP, all of the given range is trailing white-spaces. 521 // Note that this result may be collapsed if there is no trailing white- 522 // spaces. 523 if (!mNBSPData.FoundNBSP()) { 524 MOZ_ASSERT(mStart.PointRef().IsSet() || mEnd.PointRef().IsSet()); 525 mTrailingWhiteSpaceRange.emplace(mStart.PointRef(), mEnd.PointRef()); 526 return mTrailingWhiteSpaceRange.ref(); 527 } 528 529 MOZ_ASSERT(mNBSPData.LastPointRef().IsSetAndValid()); 530 531 // If last NBSP is immediately before the end, there is no trailing white- 532 // spaces. 533 if (mEnd.PointRef().IsSet() && 534 mNBSPData.LastPointRef().GetContainer() == 535 mEnd.PointRef().GetContainer() && 536 mNBSPData.LastPointRef().Offset() == mEnd.PointRef().Offset() - 1) { 537 mTrailingWhiteSpaceRange.emplace(); 538 return mTrailingWhiteSpaceRange.ref(); 539 } 540 541 // Otherwise, the may be some trailing white-spaces. 542 MOZ_ASSERT(!mNBSPData.LastPointRef().IsEndOfContainer()); 543 mTrailingWhiteSpaceRange.emplace(mNBSPData.LastPointRef().NextPoint(), 544 mEnd.PointRef()); 545 return mTrailingWhiteSpaceRange.ref(); 546 } 547 548 EditorDOMRangeInTexts 549 WSRunScanner::TextFragmentData::GetNonCollapsedRangeInTexts( 550 const EditorDOMRange& aRange) const { 551 if (!aRange.IsPositioned()) { 552 return EditorDOMRangeInTexts(); 553 } 554 if (aRange.Collapsed()) { 555 // If collapsed, we can do nothing. 556 return EditorDOMRangeInTexts(); 557 } 558 if (aRange.IsInTextNodes()) { 559 // Note that this may return a range which don't include any invisible 560 // white-spaces due to empty text nodes. 561 return aRange.GetAsInTexts(); 562 } 563 564 const auto firstPoint = 565 aRange.StartRef().IsInTextNode() 566 ? aRange.StartRef().AsInText() 567 : GetInclusiveNextCharPoint<EditorDOMPointInText>( 568 aRange.StartRef(), 569 ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions)); 570 if (!firstPoint.IsSet()) { 571 return EditorDOMRangeInTexts(); 572 } 573 EditorDOMPointInText endPoint; 574 if (aRange.EndRef().IsInTextNode()) { 575 endPoint = aRange.EndRef().AsInText(); 576 } else { 577 // FYI: GetPreviousCharPoint() returns last character's point of preceding 578 // text node if it's not empty, but we need end of the text node here. 579 endPoint = GetPreviousCharPoint<EditorDOMPointInText>( 580 aRange.EndRef(), 581 ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions)); 582 if (endPoint.IsSet() && endPoint.IsAtLastContent()) { 583 MOZ_ALWAYS_TRUE(endPoint.AdvanceOffset()); 584 } 585 } 586 if (!endPoint.IsSet() || firstPoint == endPoint) { 587 return EditorDOMRangeInTexts(); 588 } 589 return EditorDOMRangeInTexts(firstPoint, endPoint); 590 } 591 592 const WSRunScanner::VisibleWhiteSpacesData& 593 WSRunScanner::TextFragmentData::VisibleWhiteSpacesDataRef() const { 594 if (mVisibleWhiteSpacesData.isSome()) { 595 return mVisibleWhiteSpacesData.ref(); 596 } 597 598 { 599 // If all things are obviously visible, we can return range for all of the 600 // things quickly. 601 const bool mayHaveInvisibleLeadingSpace = 602 !StartsFromNonCollapsibleCharacters() && !StartsFromSpecialContent(); 603 const bool mayHaveInvisibleTrailingWhiteSpace = 604 !EndsByNonCollapsibleCharacters() && !EndsBySpecialContent() && 605 !EndsByBRElement() && !EndsByInvisiblePreformattedLineBreak(); 606 607 if (!mayHaveInvisibleLeadingSpace && !mayHaveInvisibleTrailingWhiteSpace) { 608 VisibleWhiteSpacesData visibleWhiteSpaces; 609 if (mStart.PointRef().IsSet()) { 610 visibleWhiteSpaces.SetStartPoint(mStart.PointRef()); 611 } 612 visibleWhiteSpaces.SetStartFrom(mStart.RawReason()); 613 if (mEnd.PointRef().IsSet()) { 614 visibleWhiteSpaces.SetEndPoint(mEnd.PointRef()); 615 } 616 visibleWhiteSpaces.SetEndBy(mEnd.RawReason()); 617 mVisibleWhiteSpacesData.emplace(visibleWhiteSpaces); 618 return mVisibleWhiteSpacesData.ref(); 619 } 620 } 621 622 // If all of the range is invisible leading or trailing white-spaces, 623 // there is no visible content. 624 const EditorDOMRange& leadingWhiteSpaceRange = 625 InvisibleLeadingWhiteSpaceRangeRef(); 626 const bool maybeHaveLeadingWhiteSpaces = 627 leadingWhiteSpaceRange.StartRef().IsSet() || 628 leadingWhiteSpaceRange.EndRef().IsSet(); 629 if (maybeHaveLeadingWhiteSpaces && 630 leadingWhiteSpaceRange.StartRef() == mStart.PointRef() && 631 leadingWhiteSpaceRange.EndRef() == mEnd.PointRef()) { 632 mVisibleWhiteSpacesData.emplace(VisibleWhiteSpacesData()); 633 return mVisibleWhiteSpacesData.ref(); 634 } 635 const EditorDOMRange& trailingWhiteSpaceRange = 636 InvisibleTrailingWhiteSpaceRangeRef(); 637 const bool maybeHaveTrailingWhiteSpaces = 638 trailingWhiteSpaceRange.StartRef().IsSet() || 639 trailingWhiteSpaceRange.EndRef().IsSet(); 640 if (maybeHaveTrailingWhiteSpaces && 641 trailingWhiteSpaceRange.StartRef() == mStart.PointRef() && 642 trailingWhiteSpaceRange.EndRef() == mEnd.PointRef()) { 643 mVisibleWhiteSpacesData.emplace(VisibleWhiteSpacesData()); 644 return mVisibleWhiteSpacesData.ref(); 645 } 646 647 if (!StartsFromHardLineBreak() && !StartsFromInlineEditingHostBoundary()) { 648 VisibleWhiteSpacesData visibleWhiteSpaces; 649 if (mStart.PointRef().IsSet()) { 650 visibleWhiteSpaces.SetStartPoint(mStart.PointRef()); 651 } 652 visibleWhiteSpaces.SetStartFrom(mStart.RawReason()); 653 if (!maybeHaveTrailingWhiteSpaces) { 654 visibleWhiteSpaces.SetEndPoint(mEnd.PointRef()); 655 visibleWhiteSpaces.SetEndBy(mEnd.RawReason()); 656 mVisibleWhiteSpacesData = Some(visibleWhiteSpaces); 657 return mVisibleWhiteSpacesData.ref(); 658 } 659 if (trailingWhiteSpaceRange.StartRef().IsSet()) { 660 visibleWhiteSpaces.SetEndPoint(trailingWhiteSpaceRange.StartRef()); 661 } 662 visibleWhiteSpaces.SetEndByTrailingWhiteSpaces(); 663 mVisibleWhiteSpacesData.emplace(visibleWhiteSpaces); 664 return mVisibleWhiteSpacesData.ref(); 665 } 666 667 MOZ_ASSERT(StartsFromHardLineBreak() || 668 StartsFromInlineEditingHostBoundary()); 669 MOZ_ASSERT(maybeHaveLeadingWhiteSpaces); 670 671 VisibleWhiteSpacesData visibleWhiteSpaces; 672 if (leadingWhiteSpaceRange.EndRef().IsSet()) { 673 visibleWhiteSpaces.SetStartPoint(leadingWhiteSpaceRange.EndRef()); 674 } 675 visibleWhiteSpaces.SetStartFromLeadingWhiteSpaces(); 676 if (!EndsByBlockBoundary() && !EndsByInlineEditingHostBoundary()) { 677 // then no trailing ws. this normal run ends the overall ws run. 678 if (mEnd.PointRef().IsSet()) { 679 visibleWhiteSpaces.SetEndPoint(mEnd.PointRef()); 680 } 681 visibleWhiteSpaces.SetEndBy(mEnd.RawReason()); 682 mVisibleWhiteSpacesData.emplace(visibleWhiteSpaces); 683 return mVisibleWhiteSpacesData.ref(); 684 } 685 686 MOZ_ASSERT(EndsByBlockBoundary() || EndsByInlineEditingHostBoundary()); 687 688 if (!maybeHaveTrailingWhiteSpaces) { 689 // normal ws runs right up to adjacent block (nbsp next to block) 690 visibleWhiteSpaces.SetEndPoint(mEnd.PointRef()); 691 visibleWhiteSpaces.SetEndBy(mEnd.RawReason()); 692 mVisibleWhiteSpacesData.emplace(visibleWhiteSpaces); 693 return mVisibleWhiteSpacesData.ref(); 694 } 695 696 if (trailingWhiteSpaceRange.StartRef().IsSet()) { 697 visibleWhiteSpaces.SetEndPoint(trailingWhiteSpaceRange.StartRef()); 698 } 699 visibleWhiteSpaces.SetEndByTrailingWhiteSpaces(); 700 mVisibleWhiteSpacesData.emplace(visibleWhiteSpaces); 701 return mVisibleWhiteSpacesData.ref(); 702 } 703 704 ReplaceRangeData 705 WSRunScanner::TextFragmentData::GetReplaceRangeDataAtEndOfDeletionRange( 706 const TextFragmentData& aTextFragmentDataAtStartToDelete) const { 707 const EditorDOMPoint& startToDelete = 708 aTextFragmentDataAtStartToDelete.ScanStartRef(); 709 const EditorDOMPoint& endToDelete = mScanStartPoint; 710 711 MOZ_ASSERT(startToDelete.IsSetAndValid()); 712 MOZ_ASSERT(endToDelete.IsSetAndValid()); 713 MOZ_ASSERT(startToDelete.EqualsOrIsBefore(endToDelete)); 714 715 if (EndRef().EqualsOrIsBefore(endToDelete)) { 716 return ReplaceRangeData(); 717 } 718 719 // If deleting range is followed by invisible trailing white-spaces, we need 720 // to remove it for making them not visible. 721 const EditorDOMRange invisibleTrailingWhiteSpaceRangeAtEnd = 722 GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(endToDelete); 723 if (invisibleTrailingWhiteSpaceRangeAtEnd.IsPositioned()) { 724 if (invisibleTrailingWhiteSpaceRangeAtEnd.Collapsed()) { 725 return ReplaceRangeData(); 726 } 727 // XXX Why don't we remove all invisible white-spaces? 728 MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeAtEnd.StartRef() == endToDelete); 729 return ReplaceRangeData(invisibleTrailingWhiteSpaceRangeAtEnd, u""_ns); 730 } 731 732 // If end of the deleting range is followed by visible white-spaces which 733 // is not preformatted, we might need to replace the following ASCII 734 // white-spaces with an NBSP. 735 const VisibleWhiteSpacesData& nonPreformattedVisibleWhiteSpacesAtEnd = 736 VisibleWhiteSpacesDataRef(); 737 if (!nonPreformattedVisibleWhiteSpacesAtEnd.IsInitialized()) { 738 return ReplaceRangeData(); 739 } 740 const PointPosition pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd = 741 nonPreformattedVisibleWhiteSpacesAtEnd.ComparePoint(endToDelete); 742 if (pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd != 743 PointPosition::StartOfFragment && 744 pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd != 745 PointPosition::MiddleOfFragment) { 746 return ReplaceRangeData(); 747 } 748 // If start of deleting range follows white-spaces or end of delete 749 // will be start of a line, the following text cannot start with an 750 // ASCII white-space for keeping it visible. 751 if (!aTextFragmentDataAtStartToDelete 752 .FollowingContentMayBecomeFirstVisibleContent(startToDelete)) { 753 return ReplaceRangeData(); 754 } 755 auto nextCharOfStartOfEnd = GetInclusiveNextCharPoint<EditorDOMPointInText>( 756 endToDelete, ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions)); 757 if (!nextCharOfStartOfEnd.IsSet() || 758 nextCharOfStartOfEnd.IsEndOfContainer() || 759 !nextCharOfStartOfEnd.IsCharCollapsibleASCIISpace()) { 760 return ReplaceRangeData(); 761 } 762 if (nextCharOfStartOfEnd.IsStartOfContainer() || 763 nextCharOfStartOfEnd.IsPreviousCharCollapsibleASCIISpace()) { 764 nextCharOfStartOfEnd = 765 aTextFragmentDataAtStartToDelete 766 .GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointInText>( 767 nextCharOfStartOfEnd, nsIEditor::eNone, 768 ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions)); 769 } 770 const auto endOfCollapsibleASCIIWhiteSpaces = 771 aTextFragmentDataAtStartToDelete 772 .GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>( 773 nextCharOfStartOfEnd, nsIEditor::eNone, 774 ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions)); 775 return ReplaceRangeData(nextCharOfStartOfEnd, 776 endOfCollapsibleASCIIWhiteSpaces, 777 nsDependentSubstring(&HTMLEditUtils::kNBSP, 1)); 778 } 779 780 ReplaceRangeData 781 WSRunScanner::TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange( 782 const TextFragmentData& aTextFragmentDataAtEndToDelete) const { 783 const EditorDOMPoint& startToDelete = mScanStartPoint; 784 const EditorDOMPoint& endToDelete = 785 aTextFragmentDataAtEndToDelete.ScanStartRef(); 786 787 MOZ_ASSERT(startToDelete.IsSetAndValid()); 788 MOZ_ASSERT(endToDelete.IsSetAndValid()); 789 MOZ_ASSERT(startToDelete.EqualsOrIsBefore(endToDelete)); 790 791 if (startToDelete.EqualsOrIsBefore(StartRef())) { 792 return ReplaceRangeData(); 793 } 794 795 const EditorDOMRange invisibleLeadingWhiteSpaceRangeAtStart = 796 GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(startToDelete); 797 798 // If deleting range follows invisible leading white-spaces, we need to 799 // remove them for making them not visible. 800 if (invisibleLeadingWhiteSpaceRangeAtStart.IsPositioned()) { 801 if (invisibleLeadingWhiteSpaceRangeAtStart.Collapsed()) { 802 return ReplaceRangeData(); 803 } 804 805 // XXX Why don't we remove all leading white-spaces? 806 return ReplaceRangeData(invisibleLeadingWhiteSpaceRangeAtStart, u""_ns); 807 } 808 809 // If start of the deleting range follows visible white-spaces which is not 810 // preformatted, we might need to replace previous ASCII white-spaces with 811 // an NBSP. 812 const VisibleWhiteSpacesData& nonPreformattedVisibleWhiteSpacesAtStart = 813 VisibleWhiteSpacesDataRef(); 814 if (!nonPreformattedVisibleWhiteSpacesAtStart.IsInitialized()) { 815 return ReplaceRangeData(); 816 } 817 const PointPosition 818 pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart = 819 nonPreformattedVisibleWhiteSpacesAtStart.ComparePoint(startToDelete); 820 if (pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart != 821 PointPosition::MiddleOfFragment && 822 pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart != 823 PointPosition::EndOfFragment) { 824 return ReplaceRangeData(); 825 } 826 // If end of the deleting range is (was) followed by white-spaces or 827 // previous character of start of deleting range will be immediately 828 // before a block boundary, the text cannot ends with an ASCII white-space 829 // for keeping it visible. 830 if (!aTextFragmentDataAtEndToDelete.PrecedingContentMayBecomeInvisible( 831 endToDelete)) { 832 return ReplaceRangeData(); 833 } 834 auto atPreviousCharOfStart = GetPreviousCharPoint<EditorDOMPointInText>( 835 startToDelete, ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions)); 836 if (!atPreviousCharOfStart.IsSet() || 837 atPreviousCharOfStart.IsEndOfContainer() || 838 !atPreviousCharOfStart.IsCharCollapsibleASCIISpace()) { 839 return ReplaceRangeData(); 840 } 841 if (atPreviousCharOfStart.IsStartOfContainer() || 842 atPreviousCharOfStart.IsPreviousCharASCIISpace()) { 843 atPreviousCharOfStart = 844 GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointInText>( 845 atPreviousCharOfStart, nsIEditor::eNone, 846 ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions)); 847 } 848 const auto endOfCollapsibleASCIIWhiteSpaces = 849 GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>( 850 atPreviousCharOfStart, nsIEditor::eNone, 851 ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions)); 852 return ReplaceRangeData(atPreviousCharOfStart, 853 endOfCollapsibleASCIIWhiteSpaces, 854 nsDependentSubstring(&HTMLEditUtils::kNBSP, 1)); 855 } 856 857 // static 858 template <typename EditorDOMPointType, typename PT, typename CT> 859 EditorDOMPointType WSRunScanner::TextFragmentData::GetInclusiveNextCharPoint( 860 const EditorDOMPointBase<PT, CT>& aPoint, 861 Options aOptions, // NOLINT(performance-unnecessary-value-param) 862 IgnoreNonEditableNodes aIgnoreNonEditableNodes, 863 const nsIContent* aFollowingLimiterContent /* = nullptr */) { 864 MOZ_ASSERT(aPoint.IsSetAndValid()); 865 866 if (NS_WARN_IF(!aPoint.IsInContentNode())) { 867 return EditorDOMPointType(); 868 } 869 870 const BlockInlineCheck blockInlineCheck = 871 aOptions.contains(Option::ReferHTMLDefaultStyle) 872 ? BlockInlineCheck::UseHTMLDefaultStyle 873 : BlockInlineCheck::Auto; 874 const EditorRawDOMPoint point = [&]() MOZ_NEVER_INLINE_DEBUG { 875 nsIContent* const child = [&]() -> nsIContent* { 876 nsIContent* child = 877 aPoint.CanContainerHaveChildren() ? aPoint.GetChild() : nullptr; 878 // XXX Why don't we skip non-editable nodes here? 879 while (child && child->IsComment() && 880 !aOptions.contains(Option::StopAtComment)) { 881 child = child->GetNextSibling(); 882 } 883 return child; 884 }(); 885 if (!child || 886 HTMLEditUtils::IsBlockElement( 887 *child, UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) || 888 HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*child)) { 889 return aPoint.template To<EditorRawDOMPoint>(); 890 } 891 if (!child->HasChildNodes()) { 892 return child->IsText() || HTMLEditUtils::IsContainerNode(*child) 893 ? EditorRawDOMPoint(child, 0) 894 : EditorRawDOMPoint::After(*child); 895 } 896 // FIXME: This may skip aFollowingLimiterContent, so, this utility should 897 // take a stopper param. 898 // FIXME: I think we should stop looking for a leaf node if there is a child 899 // block because end reason content should not be the other side of the 900 // following block boundary. 901 nsIContent* const leafContent = HTMLEditUtils::GetFirstLeafContent( 902 *child, {LeafNodeType::LeafNodeOrChildBlock}, blockInlineCheck); 903 if (!leafContent) { 904 return EditorRawDOMPoint(child, 0); 905 } 906 if (HTMLEditUtils::IsBlockElement( 907 *leafContent, 908 UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) || 909 HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*leafContent)) { 910 return EditorRawDOMPoint(); 911 } 912 return EditorRawDOMPoint(leafContent, 0); 913 }(); 914 if (!point.IsSet()) { 915 return EditorDOMPointType(); 916 } 917 918 // If it points a character in a text node, return it. 919 // XXX For the performance, this does not check whether the container 920 // is outside of our range. 921 if (point.IsInTextNode() && 922 (aIgnoreNonEditableNodes == IgnoreNonEditableNodes::No || 923 HTMLEditUtils::IsSimplyEditableNode(*point.GetContainer())) && 924 !point.IsEndOfContainer()) { 925 return EditorDOMPointType(point.ContainerAs<Text>(), point.Offset()); 926 } 927 928 if (point.GetContainer() == aFollowingLimiterContent) { 929 return EditorDOMPointType(); 930 } 931 932 const Element* const 933 editableBlockElementOrInlineEditingHostOrNonEditableRootElement = 934 HTMLEditUtils::GetInclusiveAncestorElement( 935 *aPoint.template ContainerAs<nsIContent>(), 936 HTMLEditUtils::IsSimplyEditableNode( 937 *aPoint.template ContainerAs<nsIContent>()) 938 ? kScanEditableRootAncestorTypes 939 : kScanAnyRootAncestorTypes, 940 blockInlineCheck); 941 if (NS_WARN_IF( 942 !editableBlockElementOrInlineEditingHostOrNonEditableRootElement)) { 943 return EditorDOMPointType(); 944 } 945 946 const auto leafNodeTypes = [&]() -> LeafNodeTypes { 947 auto types = aIgnoreNonEditableNodes == IgnoreNonEditableNodes::Yes 948 ? LeafNodeTypes(LeafNodeType::LeafNodeOrNonEditableNode, 949 LeafNodeType::LeafNodeOrChildBlock) 950 : LeafNodeTypes(LeafNodeType::LeafNodeOrChildBlock); 951 if (aOptions.contains(Option::StopAtComment)) { 952 types += LeafNodeType::TreatCommentAsLeafNode; 953 } 954 return types; 955 }(); 956 for (nsIContent* nextContent = 957 HTMLEditUtils::GetNextLeafContentOrNextBlockElement( 958 *point.ContainerAs<nsIContent>(), leafNodeTypes, 959 blockInlineCheck, 960 editableBlockElementOrInlineEditingHostOrNonEditableRootElement); 961 nextContent; 962 nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( 963 *nextContent, leafNodeTypes, blockInlineCheck, 964 editableBlockElementOrInlineEditingHostOrNonEditableRootElement)) { 965 if (!nextContent->IsText() || 966 (aIgnoreNonEditableNodes == IgnoreNonEditableNodes::Yes && 967 !HTMLEditUtils::IsSimplyEditableNode(*nextContent))) { 968 if (nextContent == aFollowingLimiterContent || 969 HTMLEditUtils::IsBlockElement( 970 *nextContent, 971 UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) || 972 HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*nextContent)) { 973 break; // Reached end of current runs. 974 } 975 continue; 976 } 977 return EditorDOMPointType(nextContent->AsText(), 0); 978 } 979 return EditorDOMPointType(); 980 } 981 982 // static 983 template <typename EditorDOMPointType, typename PT, typename CT> 984 EditorDOMPointType WSRunScanner::TextFragmentData::GetPreviousCharPoint( 985 const EditorDOMPointBase<PT, CT>& aPoint, 986 Options aOptions, // NOLINT(performance-unnecessary-value-param) 987 IgnoreNonEditableNodes aIgnoreNonEditableNodes, 988 const nsIContent* aPrecedingLimiterContent /* = nullptr */) { 989 MOZ_ASSERT(aPoint.IsSetAndValid()); 990 991 if (NS_WARN_IF(!aPoint.IsInContentNode())) { 992 return EditorDOMPointType(); 993 } 994 995 const BlockInlineCheck blockInlineCheck = 996 aOptions.contains(Option::ReferHTMLDefaultStyle) 997 ? BlockInlineCheck::UseHTMLDefaultStyle 998 : BlockInlineCheck::Auto; 999 const EditorRawDOMPoint point = [&]() MOZ_NEVER_INLINE_DEBUG { 1000 nsIContent* const previousChild = [&]() -> nsIContent* { 1001 nsIContent* previousChild = aPoint.CanContainerHaveChildren() 1002 ? aPoint.GetPreviousSiblingOfChild() 1003 : nullptr; 1004 // XXX Why don't we skip non-editable nodes here? 1005 while (previousChild && previousChild->IsComment() && 1006 !aOptions.contains(Option::StopAtComment)) { 1007 previousChild = previousChild->GetPreviousSibling(); 1008 } 1009 return previousChild; 1010 }(); 1011 if (!previousChild || 1012 HTMLEditUtils::IsBlockElement( 1013 *previousChild, 1014 UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) || 1015 HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousChild)) { 1016 return aPoint.template To<EditorRawDOMPoint>(); 1017 } 1018 if (!previousChild->HasChildren()) { 1019 return previousChild->IsText() || 1020 HTMLEditUtils::IsContainerNode(*previousChild) 1021 ? EditorRawDOMPoint::AtEndOf(*previousChild) 1022 : EditorRawDOMPoint::After(*previousChild); 1023 } 1024 // FIXME: This may skip aPrecedingLimiterContent, so, this utility should 1025 // take a stopper param. 1026 // FIXME: I think we should stop looking for a leaf node if there is a child 1027 // block because end reason content should not be the other side of the 1028 // following block boundary. 1029 nsIContent* const leafContent = HTMLEditUtils::GetLastLeafContent( 1030 *previousChild, {LeafNodeType::LeafNodeOrChildBlock}, blockInlineCheck); 1031 if (!leafContent) { 1032 return EditorRawDOMPoint::AtEndOf(*previousChild); 1033 } 1034 if (HTMLEditUtils::IsBlockElement( 1035 *leafContent, 1036 UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) || 1037 HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*leafContent)) { 1038 return EditorRawDOMPoint(); 1039 } 1040 return EditorRawDOMPoint::AtEndOf(*leafContent); 1041 }(); 1042 if (!point.IsSet()) { 1043 return EditorDOMPointType(); 1044 } 1045 1046 // If it points a character in a text node and it's not first character 1047 // in it, return its previous point. 1048 // XXX For the performance, this does not check whether the container 1049 // is outside of our range. 1050 if (point.IsInTextNode() && 1051 (aIgnoreNonEditableNodes == IgnoreNonEditableNodes::No || 1052 HTMLEditUtils::IsSimplyEditableNode(*point.GetContainer())) && 1053 !point.IsStartOfContainer()) { 1054 return EditorDOMPointType(point.ContainerAs<Text>(), point.Offset() - 1); 1055 } 1056 1057 if (point.GetContainer() == aPrecedingLimiterContent) { 1058 return EditorDOMPointType(); 1059 } 1060 1061 const Element* const 1062 editableBlockElementOrInlineEditingHostOrNonEditableRootElement = 1063 HTMLEditUtils::GetInclusiveAncestorElement( 1064 *aPoint.template ContainerAs<nsIContent>(), 1065 HTMLEditUtils::IsSimplyEditableNode( 1066 *aPoint.template ContainerAs<nsIContent>()) 1067 ? kScanEditableRootAncestorTypes 1068 : kScanAnyRootAncestorTypes, 1069 blockInlineCheck); 1070 if (NS_WARN_IF( 1071 !editableBlockElementOrInlineEditingHostOrNonEditableRootElement)) { 1072 return EditorDOMPointType(); 1073 } 1074 1075 const auto leafNodeTypes = [&]() -> LeafNodeTypes { 1076 auto types = aIgnoreNonEditableNodes == IgnoreNonEditableNodes::Yes 1077 ? LeafNodeTypes(LeafNodeType::LeafNodeOrNonEditableNode, 1078 LeafNodeType::LeafNodeOrChildBlock) 1079 : LeafNodeTypes(LeafNodeType::LeafNodeOrChildBlock); 1080 if (aOptions.contains(Option::StopAtComment)) { 1081 types += LeafNodeType::TreatCommentAsLeafNode; 1082 } 1083 return types; 1084 }(); 1085 for ( 1086 nsIContent* previousContent = 1087 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( 1088 *point.ContainerAs<nsIContent>(), leafNodeTypes, blockInlineCheck, 1089 editableBlockElementOrInlineEditingHostOrNonEditableRootElement); 1090 previousContent; 1091 previousContent = 1092 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( 1093 *previousContent, leafNodeTypes, blockInlineCheck, 1094 editableBlockElementOrInlineEditingHostOrNonEditableRootElement)) { 1095 if (!previousContent->IsText() || 1096 (aIgnoreNonEditableNodes == IgnoreNonEditableNodes::Yes && 1097 !HTMLEditUtils::IsSimplyEditableNode(*previousContent))) { 1098 if (previousContent == aPrecedingLimiterContent || 1099 HTMLEditUtils::IsBlockElement( 1100 *previousContent, 1101 UseComputedDisplayOutsideStyleIfAuto(blockInlineCheck)) || 1102 HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousContent)) { 1103 break; // Reached start of current runs. 1104 } 1105 continue; 1106 } 1107 return EditorDOMPointType(previousContent->AsText(), 1108 previousContent->AsText()->TextLength() 1109 ? previousContent->AsText()->TextLength() - 1 1110 : 0); 1111 } 1112 return EditorDOMPointType(); 1113 } 1114 1115 // static 1116 template <typename EditorDOMPointType> 1117 EditorDOMPointType 1118 WSRunScanner::TextFragmentData::GetEndOfCollapsibleASCIIWhiteSpaces( 1119 const EditorDOMPointInText& aPointAtASCIIWhiteSpace, 1120 nsIEditor::EDirection aDirectionToDelete, 1121 Options aOptions, // NOLINT(performance-unnecessary-value-param) 1122 IgnoreNonEditableNodes aIgnoreNonEditableNodes, 1123 const nsIContent* aFollowingLimiterContent /* = nullptr */) { 1124 MOZ_ASSERT(aDirectionToDelete == nsIEditor::eNone || 1125 aDirectionToDelete == nsIEditor::eNext || 1126 aDirectionToDelete == nsIEditor::ePrevious); 1127 MOZ_ASSERT(aPointAtASCIIWhiteSpace.IsSet()); 1128 MOZ_ASSERT(!aPointAtASCIIWhiteSpace.IsEndOfContainer()); 1129 MOZ_ASSERT_IF(!EditorUtils::IsNewLinePreformatted( 1130 *aPointAtASCIIWhiteSpace.ContainerAs<Text>()), 1131 aPointAtASCIIWhiteSpace.IsCharCollapsibleASCIISpace()); 1132 MOZ_ASSERT_IF(EditorUtils::IsNewLinePreformatted( 1133 *aPointAtASCIIWhiteSpace.ContainerAs<Text>()), 1134 aPointAtASCIIWhiteSpace.IsCharASCIISpace()); 1135 1136 // If we're deleting text forward and the next visible character is first 1137 // preformatted new line but white-spaces can be collapsed, we need to 1138 // delete its following collapsible white-spaces too. 1139 bool hasSeenPreformattedNewLine = 1140 aPointAtASCIIWhiteSpace.IsCharPreformattedNewLine(); 1141 auto NeedToScanFollowingWhiteSpaces = 1142 [&hasSeenPreformattedNewLine, 1143 &aDirectionToDelete](const EditorDOMPointInText& aAtNextVisibleCharacter) 1144 MOZ_NEVER_INLINE_DEBUG -> bool { 1145 MOZ_ASSERT(!aAtNextVisibleCharacter.IsEndOfContainer()); 1146 return !hasSeenPreformattedNewLine && 1147 aDirectionToDelete == nsIEditor::eNext && 1148 aAtNextVisibleCharacter 1149 .IsCharPreformattedNewLineCollapsedWithWhiteSpaces(); 1150 }; 1151 auto ScanNextNonCollapsibleChar = 1152 [&hasSeenPreformattedNewLine, 1153 &NeedToScanFollowingWhiteSpaces](const EditorDOMPointInText& aPoint) 1154 MOZ_NEVER_INLINE_DEBUG -> EditorDOMPointInText { 1155 Maybe<uint32_t> nextVisibleCharOffset = 1156 HTMLEditUtils::GetNextNonCollapsibleCharOffset(aPoint); 1157 if (!nextVisibleCharOffset.isSome()) { 1158 return EditorDOMPointInText(); // Keep scanning following text nodes 1159 } 1160 EditorDOMPointInText atNextVisibleChar(aPoint.ContainerAs<Text>(), 1161 nextVisibleCharOffset.value()); 1162 if (!NeedToScanFollowingWhiteSpaces(atNextVisibleChar)) { 1163 return atNextVisibleChar; 1164 } 1165 hasSeenPreformattedNewLine |= atNextVisibleChar.IsCharPreformattedNewLine(); 1166 nextVisibleCharOffset = 1167 HTMLEditUtils::GetNextNonCollapsibleCharOffset(atNextVisibleChar); 1168 if (nextVisibleCharOffset.isSome()) { 1169 MOZ_ASSERT(aPoint.ContainerAs<Text>() == 1170 atNextVisibleChar.ContainerAs<Text>()); 1171 return EditorDOMPointInText(atNextVisibleChar.ContainerAs<Text>(), 1172 nextVisibleCharOffset.value()); 1173 } 1174 return EditorDOMPointInText(); // Keep scanning following text nodes 1175 }; 1176 1177 // If it's not the last character in the text node, let's scan following 1178 // characters in it. 1179 if (!aPointAtASCIIWhiteSpace.IsAtLastContent()) { 1180 const EditorDOMPointInText atNextVisibleChar( 1181 ScanNextNonCollapsibleChar(aPointAtASCIIWhiteSpace)); 1182 if (atNextVisibleChar.IsSet()) { 1183 return atNextVisibleChar.To<EditorDOMPointType>(); 1184 } 1185 } 1186 1187 // Otherwise, i.e., the text node ends with ASCII white-space, keep scanning 1188 // the following text nodes. 1189 // XXX Perhaps, we should stop scanning if there is non-editable and visible 1190 // content. 1191 EditorDOMPointInText afterLastWhiteSpace = EditorDOMPointInText::AtEndOf( 1192 *aPointAtASCIIWhiteSpace.ContainerAs<Text>()); 1193 for (EditorDOMPointInText atEndOfPreviousTextNode = afterLastWhiteSpace;;) { 1194 const auto atStartOfNextTextNode = 1195 TextFragmentData::GetInclusiveNextCharPoint<EditorDOMPointInText>( 1196 atEndOfPreviousTextNode, aOptions, aIgnoreNonEditableNodes, 1197 aFollowingLimiterContent); 1198 if (!atStartOfNextTextNode.IsSet()) { 1199 // There is no more text nodes. Return end of the previous text node. 1200 return afterLastWhiteSpace.To<EditorDOMPointType>(); 1201 } 1202 1203 // We can ignore empty text nodes (even if it's preformatted). 1204 if (atStartOfNextTextNode.IsContainerEmpty()) { 1205 atEndOfPreviousTextNode = atStartOfNextTextNode; 1206 continue; 1207 } 1208 1209 // If next node starts with non-white-space character or next node is 1210 // preformatted, return end of previous text node. However, if it 1211 // starts with a preformatted linefeed but white-spaces are collapsible, 1212 // we need to scan following collapsible white-spaces when we're deleting 1213 // text forward. 1214 if (!atStartOfNextTextNode.IsCharCollapsibleASCIISpace() && 1215 !NeedToScanFollowingWhiteSpaces(atStartOfNextTextNode)) { 1216 return afterLastWhiteSpace.To<EditorDOMPointType>(); 1217 } 1218 1219 // Otherwise, scan the text node. 1220 const EditorDOMPointInText atNextVisibleChar( 1221 ScanNextNonCollapsibleChar(atStartOfNextTextNode)); 1222 if (atNextVisibleChar.IsSet()) { 1223 return atNextVisibleChar.To<EditorDOMPointType>(); 1224 } 1225 1226 // The next text nodes ends with white-space too. Try next one. 1227 afterLastWhiteSpace = atEndOfPreviousTextNode = 1228 EditorDOMPointInText::AtEndOf( 1229 *atStartOfNextTextNode.ContainerAs<Text>()); 1230 } 1231 } 1232 1233 // static 1234 template <typename EditorDOMPointType> 1235 EditorDOMPointType 1236 WSRunScanner::TextFragmentData::GetFirstASCIIWhiteSpacePointCollapsedTo( 1237 const EditorDOMPointInText& aPointAtASCIIWhiteSpace, 1238 nsIEditor::EDirection aDirectionToDelete, 1239 Options aOptions, // NOLINT(performance-unnecessary-value-param) 1240 IgnoreNonEditableNodes aIgnoreNonEditableNodes, 1241 const nsIContent* aPrecedingLimiterContent) { 1242 MOZ_ASSERT(aDirectionToDelete == nsIEditor::eNone || 1243 aDirectionToDelete == nsIEditor::eNext || 1244 aDirectionToDelete == nsIEditor::ePrevious); 1245 MOZ_ASSERT(aPointAtASCIIWhiteSpace.IsSet()); 1246 MOZ_ASSERT(!aPointAtASCIIWhiteSpace.IsEndOfContainer()); 1247 MOZ_ASSERT_IF(!EditorUtils::IsNewLinePreformatted( 1248 *aPointAtASCIIWhiteSpace.ContainerAs<Text>()), 1249 aPointAtASCIIWhiteSpace.IsCharCollapsibleASCIISpace()); 1250 MOZ_ASSERT_IF(EditorUtils::IsNewLinePreformatted( 1251 *aPointAtASCIIWhiteSpace.ContainerAs<Text>()), 1252 aPointAtASCIIWhiteSpace.IsCharASCIISpace()); 1253 1254 // If we're deleting text backward and the previous visible character is first 1255 // preformatted new line but white-spaces can be collapsed, we need to delete 1256 // its preceding collapsible white-spaces too. 1257 bool hasSeenPreformattedNewLine = 1258 aPointAtASCIIWhiteSpace.IsCharPreformattedNewLine(); 1259 auto NeedToScanPrecedingWhiteSpaces = 1260 [&hasSeenPreformattedNewLine, &aDirectionToDelete]( 1261 const EditorDOMPointInText& aAtPreviousVisibleCharacter) 1262 MOZ_NEVER_INLINE_DEBUG -> bool { 1263 MOZ_ASSERT(!aAtPreviousVisibleCharacter.IsEndOfContainer()); 1264 return !hasSeenPreformattedNewLine && 1265 aDirectionToDelete == nsIEditor::ePrevious && 1266 aAtPreviousVisibleCharacter 1267 .IsCharPreformattedNewLineCollapsedWithWhiteSpaces(); 1268 }; 1269 auto ScanPreviousNonCollapsibleChar = 1270 [&hasSeenPreformattedNewLine, 1271 &NeedToScanPrecedingWhiteSpaces](const EditorDOMPointInText& aPoint) 1272 MOZ_NEVER_INLINE_DEBUG -> EditorDOMPointInText { 1273 Maybe<uint32_t> previousVisibleCharOffset = 1274 HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(aPoint); 1275 if (previousVisibleCharOffset.isNothing()) { 1276 return EditorDOMPointInText(); // Keep scanning preceding text nodes 1277 } 1278 EditorDOMPointInText atPreviousVisibleCharacter( 1279 aPoint.ContainerAs<Text>(), previousVisibleCharOffset.value()); 1280 if (!NeedToScanPrecedingWhiteSpaces(atPreviousVisibleCharacter)) { 1281 return atPreviousVisibleCharacter.NextPoint(); 1282 } 1283 hasSeenPreformattedNewLine |= 1284 atPreviousVisibleCharacter.IsCharPreformattedNewLine(); 1285 previousVisibleCharOffset = 1286 HTMLEditUtils::GetPreviousNonCollapsibleCharOffset( 1287 atPreviousVisibleCharacter); 1288 if (previousVisibleCharOffset.isSome()) { 1289 MOZ_ASSERT(aPoint.ContainerAs<Text>() == 1290 atPreviousVisibleCharacter.ContainerAs<Text>()); 1291 return EditorDOMPointInText( 1292 atPreviousVisibleCharacter.ContainerAs<Text>(), 1293 previousVisibleCharOffset.value() + 1); 1294 } 1295 return EditorDOMPointInText(); // Keep scanning preceding text nodes 1296 }; 1297 1298 // If there is some characters before it, scan it in the text node first. 1299 if (!aPointAtASCIIWhiteSpace.IsStartOfContainer()) { 1300 EditorDOMPointInText atFirstASCIIWhiteSpace( 1301 ScanPreviousNonCollapsibleChar(aPointAtASCIIWhiteSpace)); 1302 if (atFirstASCIIWhiteSpace.IsSet()) { 1303 return atFirstASCIIWhiteSpace.To<EditorDOMPointType>(); 1304 } 1305 } 1306 1307 // Otherwise, i.e., the text node starts with ASCII white-space, keep scanning 1308 // the preceding text nodes. 1309 // XXX Perhaps, we should stop scanning if there is non-editable and visible 1310 // content. 1311 EditorDOMPointInText atLastWhiteSpace = 1312 EditorDOMPointInText(aPointAtASCIIWhiteSpace.ContainerAs<Text>(), 0u); 1313 for (EditorDOMPointInText atStartOfPreviousTextNode = atLastWhiteSpace;;) { 1314 const auto atLastCharOfPreviousTextNode = 1315 TextFragmentData::GetPreviousCharPoint<EditorDOMPointInText>( 1316 atStartOfPreviousTextNode, aOptions, aIgnoreNonEditableNodes, 1317 aPrecedingLimiterContent); 1318 if (!atLastCharOfPreviousTextNode.IsSet()) { 1319 // There is no more text nodes. Return end of last text node. 1320 return atLastWhiteSpace.To<EditorDOMPointType>(); 1321 } 1322 1323 // We can ignore empty text nodes (even if it's preformatted). 1324 if (atLastCharOfPreviousTextNode.IsContainerEmpty()) { 1325 atStartOfPreviousTextNode = atLastCharOfPreviousTextNode; 1326 continue; 1327 } 1328 1329 // If next node ends with non-white-space character or next node is 1330 // preformatted, return start of previous text node. 1331 if (!atLastCharOfPreviousTextNode.IsCharCollapsibleASCIISpace() && 1332 !NeedToScanPrecedingWhiteSpaces(atLastCharOfPreviousTextNode)) { 1333 return atLastWhiteSpace.To<EditorDOMPointType>(); 1334 } 1335 1336 // Otherwise, scan the text node. 1337 const EditorDOMPointInText atFirstASCIIWhiteSpace( 1338 ScanPreviousNonCollapsibleChar(atLastCharOfPreviousTextNode)); 1339 if (atFirstASCIIWhiteSpace.IsSet()) { 1340 return atFirstASCIIWhiteSpace.To<EditorDOMPointType>(); 1341 } 1342 1343 // The next text nodes starts with white-space too. Try next one. 1344 atLastWhiteSpace = atStartOfPreviousTextNode = EditorDOMPointInText( 1345 atLastCharOfPreviousTextNode.ContainerAs<Text>(), 0u); 1346 } 1347 } 1348 1349 EditorDOMPointInText WSRunScanner::TextFragmentData:: 1350 GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace( 1351 const EditorDOMPoint& aPointToInsert) const { 1352 MOZ_ASSERT(aPointToInsert.IsSetAndValid()); 1353 MOZ_ASSERT(VisibleWhiteSpacesDataRef().IsInitialized()); 1354 NS_ASSERTION(VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert) == 1355 PointPosition::MiddleOfFragment || 1356 VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert) == 1357 PointPosition::EndOfFragment, 1358 "Previous char of aPoint should be in the visible white-spaces"); 1359 1360 // Try to change an NBSP to a space, if possible, just to prevent NBSP 1361 // proliferation. This routine is called when we are about to make this 1362 // point in the ws abut an inserted break or text, so we don't have to worry 1363 // about what is after it. What is after it now will end up after the 1364 // inserted object. 1365 const auto atPreviousChar = GetPreviousCharPoint<EditorDOMPointInText>( 1366 aPointToInsert, ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions)); 1367 if (!atPreviousChar.IsSet() || atPreviousChar.IsEndOfContainer() || 1368 !atPreviousChar.IsCharNBSP() || 1369 EditorUtils::IsWhiteSpacePreformatted( 1370 *atPreviousChar.ContainerAs<Text>())) { 1371 return EditorDOMPointInText(); 1372 } 1373 1374 const auto atPreviousCharOfPreviousChar = 1375 GetPreviousCharPoint<EditorDOMPointInText>( 1376 atPreviousChar, 1377 ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions)); 1378 if (atPreviousCharOfPreviousChar.IsSet()) { 1379 // If the previous char is in different text node and it's preformatted, 1380 // we shouldn't touch it. 1381 if (atPreviousChar.ContainerAs<Text>() != 1382 atPreviousCharOfPreviousChar.ContainerAs<Text>() && 1383 EditorUtils::IsWhiteSpacePreformatted( 1384 *atPreviousCharOfPreviousChar.ContainerAs<Text>())) { 1385 return EditorDOMPointInText(); 1386 } 1387 // If the previous char of the NBSP at previous position of aPointToInsert 1388 // is an ASCII white-space, we don't need to replace it with same character. 1389 if (!atPreviousCharOfPreviousChar.IsEndOfContainer() && 1390 atPreviousCharOfPreviousChar.IsCharASCIISpace()) { 1391 return EditorDOMPointInText(); 1392 } 1393 return atPreviousChar; 1394 } 1395 1396 // If previous content of the NBSP is block boundary, we cannot replace the 1397 // NBSP with an ASCII white-space to keep it rendered. 1398 const VisibleWhiteSpacesData& visibleWhiteSpaces = 1399 VisibleWhiteSpacesDataRef(); 1400 if (!visibleWhiteSpaces.StartsFromNonCollapsibleCharacters() && 1401 !visibleWhiteSpaces.StartsFromSpecialContent()) { 1402 return EditorDOMPointInText(); 1403 } 1404 return atPreviousChar; 1405 } 1406 1407 EditorDOMPointInText WSRunScanner::TextFragmentData:: 1408 GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace( 1409 const EditorDOMPoint& aPointToInsert) const { 1410 MOZ_ASSERT(aPointToInsert.IsSetAndValid()); 1411 MOZ_ASSERT(VisibleWhiteSpacesDataRef().IsInitialized()); 1412 NS_ASSERTION(VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert) == 1413 PointPosition::StartOfFragment || 1414 VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert) == 1415 PointPosition::MiddleOfFragment, 1416 "Inclusive next char of aPointToInsert should be in the visible " 1417 "white-spaces"); 1418 1419 // Try to change an nbsp to a space, if possible, just to prevent nbsp 1420 // proliferation This routine is called when we are about to make this point 1421 // in the ws abut an inserted text, so we don't have to worry about what is 1422 // before it. What is before it now will end up before the inserted text. 1423 const auto atNextChar = GetInclusiveNextCharPoint<EditorDOMPointInText>( 1424 aPointToInsert, ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions)); 1425 if (!atNextChar.IsSet() || NS_WARN_IF(atNextChar.IsEndOfContainer()) || 1426 !atNextChar.IsCharNBSP() || 1427 EditorUtils::IsWhiteSpacePreformatted(*atNextChar.ContainerAs<Text>())) { 1428 return EditorDOMPointInText(); 1429 } 1430 1431 const auto atNextCharOfNextCharOfNBSP = 1432 GetInclusiveNextCharPoint<EditorDOMPointInText>( 1433 atNextChar.NextPoint<EditorRawDOMPointInText>(), 1434 ShouldIgnoreNonEditableSiblingsOrDescendants(mOptions)); 1435 if (atNextCharOfNextCharOfNBSP.IsSet()) { 1436 // If the next char is in different text node and it's preformatted, 1437 // we shouldn't touch it. 1438 if (atNextChar.ContainerAs<Text>() != 1439 atNextCharOfNextCharOfNBSP.ContainerAs<Text>() && 1440 EditorUtils::IsWhiteSpacePreformatted( 1441 *atNextCharOfNextCharOfNBSP.ContainerAs<Text>())) { 1442 return EditorDOMPointInText(); 1443 } 1444 // If following character of an NBSP is an ASCII white-space, we don't 1445 // need to replace it with same character. 1446 if (!atNextCharOfNextCharOfNBSP.IsEndOfContainer() && 1447 atNextCharOfNextCharOfNBSP.IsCharASCIISpace()) { 1448 return EditorDOMPointInText(); 1449 } 1450 return atNextChar; 1451 } 1452 1453 // If the NBSP is last character in the hard line, we don't need to 1454 // replace it because it's required to render multiple white-spaces. 1455 const VisibleWhiteSpacesData& visibleWhiteSpaces = 1456 VisibleWhiteSpacesDataRef(); 1457 if (!visibleWhiteSpaces.EndsByNonCollapsibleCharacters() && 1458 !visibleWhiteSpaces.EndsBySpecialContent() && 1459 !visibleWhiteSpaces.EndsByBRElement()) { 1460 return EditorDOMPointInText(); 1461 } 1462 1463 return atNextChar; 1464 } 1465 1466 } // namespace mozilla