WhiteSpaceVisibilityKeeper.cpp (120716B)
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 "WhiteSpaceVisibilityKeeper.h" 7 8 #include "EditorDOMPoint.h" 9 #include "EditorUtils.h" 10 #include "ErrorList.h" 11 #include "HTMLEditHelpers.h" // for MoveNodeResult, SplitNodeResult 12 #include "HTMLEditor.h" 13 #include "HTMLEditorNestedClasses.h" // for AutoMoveOneLineHandler 14 #include "HTMLEditUtils.h" 15 #include "SelectionState.h" 16 17 #include "mozilla/Assertions.h" 18 #include "mozilla/SelectionState.h" 19 #include "mozilla/OwningNonNull.h" 20 #include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_* 21 #include "mozilla/dom/AncestorIterator.h" 22 23 #include "nsCRT.h" 24 #include "nsContentUtils.h" 25 #include "nsDebug.h" 26 #include "nsError.h" 27 #include "nsIContent.h" 28 #include "nsIContentInlines.h" 29 #include "nsString.h" 30 31 namespace mozilla { 32 33 using namespace dom; 34 35 using LeafNodeType = HTMLEditUtils::LeafNodeType; 36 using WalkTreeOption = HTMLEditUtils::WalkTreeOption; 37 38 Result<EditorDOMPoint, nsresult> 39 WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement( 40 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToSplit, 41 const Element& aSplittingBlockElement) { 42 if (NS_WARN_IF(!aPointToSplit.IsInContentNodeAndValidInComposedDoc()) || 43 NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(aSplittingBlockElement)) || 44 NS_WARN_IF(!EditorUtils::IsEditableContent( 45 *aPointToSplit.ContainerAs<nsIContent>(), EditorType::HTML))) { 46 return Err(NS_ERROR_FAILURE); 47 } 48 49 // The container of aPointToSplit may be not splittable, e.g., selection 50 // may be collapsed **in** a `<br>` element or a comment node. So, look 51 // for splittable point with climbing the tree up. 52 EditorDOMPoint pointToSplit(aPointToSplit); 53 for (nsIContent* content : aPointToSplit.ContainerAs<nsIContent>() 54 ->InclusiveAncestorsOfType<nsIContent>()) { 55 if (content == &aSplittingBlockElement) { 56 break; 57 } 58 if (HTMLEditUtils::IsSplittableNode(*content)) { 59 break; 60 } 61 pointToSplit.Set(content); 62 } 63 64 // NOTE: Chrome does not normalize white-spaces at splitting `Text` when 65 // inserting a paragraph at least when the surrounding white-spaces being or 66 // end with an NBSP. 67 Result<EditorDOMPoint, nsresult> pointToSplitOrError = 68 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt( 69 aHTMLEditor, pointToSplit, 70 {NormalizeOption::StopIfFollowingWhiteSpacesStartsWithNBSP, 71 NormalizeOption::StopIfPrecedingWhiteSpacesEndsWithNBP}); 72 if (MOZ_UNLIKELY(pointToSplitOrError.isErr())) { 73 NS_WARNING( 74 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt() failed"); 75 return pointToSplitOrError.propagateErr(); 76 } 77 pointToSplit = pointToSplitOrError.unwrap(); 78 79 if (NS_WARN_IF(!pointToSplit.IsInContentNode()) || 80 NS_WARN_IF( 81 !pointToSplit.ContainerAs<nsIContent>()->IsInclusiveDescendantOf( 82 &aSplittingBlockElement)) || 83 NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(aSplittingBlockElement)) || 84 NS_WARN_IF(!HTMLEditUtils::IsSplittableNode( 85 *pointToSplit.ContainerAs<nsIContent>()))) { 86 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 87 } 88 89 return pointToSplit; 90 } 91 92 // static 93 Result<MoveNodeResult, nsresult> WhiteSpaceVisibilityKeeper:: 94 MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement( 95 HTMLEditor& aHTMLEditor, Element& aLeftBlockElement, 96 Element& aRightBlockElement, const EditorDOMPoint& aAtRightBlockChild, 97 const Maybe<nsAtom*>& aListElementTagName, 98 const HTMLBRElement* aPrecedingInvisibleBRElement, 99 const Element& aEditingHost) { 100 MOZ_ASSERT( 101 EditorUtils::IsDescendantOf(aLeftBlockElement, aRightBlockElement)); 102 MOZ_ASSERT(&aRightBlockElement == aAtRightBlockChild.GetContainer()); 103 104 OwningNonNull<Element> rightBlockElement = aRightBlockElement; 105 EditorDOMPoint afterRightBlockChild = aAtRightBlockChild.NextPoint(); 106 { 107 AutoTrackDOMPoint trackAfterRightBlockChild(aHTMLEditor.RangeUpdaterRef(), 108 &afterRightBlockChild); 109 // First, delete invisible white-spaces at start of the right block and 110 // normalize the leading visible white-spaces. 111 nsresult rv = WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter( 112 aHTMLEditor, afterRightBlockChild); 113 if (NS_FAILED(rv)) { 114 NS_WARNING( 115 "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter() " 116 "failed"); 117 return Err(rv); 118 } 119 // Next, delete invisible white-spaces at end of the left block and 120 // normalize the trailing visible white-spaces. 121 rv = WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore( 122 aHTMLEditor, EditorDOMPoint::AtEndOf(aLeftBlockElement)); 123 if (NS_FAILED(rv)) { 124 NS_WARNING( 125 "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore() " 126 "failed"); 127 return Err(rv); 128 } 129 trackAfterRightBlockChild.FlushAndStopTracking(); 130 if (NS_WARN_IF(afterRightBlockChild.GetContainer() != 131 &aRightBlockElement)) { 132 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 133 } 134 } 135 // Finally, make sure that we won't create new invisible white-spaces. 136 { 137 AutoTrackDOMPoint trackAfterRightBlockChild(aHTMLEditor.RangeUpdaterRef(), 138 &afterRightBlockChild); 139 Result<EditorDOMPoint, nsresult> atFirstVisibleThingOrError = 140 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter( 141 aHTMLEditor, afterRightBlockChild, 142 {NormalizeOption::StopIfFollowingWhiteSpacesStartsWithNBSP}); 143 if (MOZ_UNLIKELY(atFirstVisibleThingOrError.isErr())) { 144 NS_WARNING( 145 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() failed"); 146 return atFirstVisibleThingOrError.propagateErr(); 147 } 148 Result<EditorDOMPoint, nsresult> afterLastVisibleThingOrError = 149 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore( 150 aHTMLEditor, EditorDOMPoint::AtEndOf(aLeftBlockElement), {}); 151 if (MOZ_UNLIKELY(afterLastVisibleThingOrError.isErr())) { 152 NS_WARNING( 153 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() failed"); 154 return afterLastVisibleThingOrError.propagateErr(); 155 } 156 } 157 158 // XXX And afterRightBlockChild.GetContainerAs<Element>() always returns 159 // an element pointer so that probably here should not use 160 // accessors of EditorDOMPoint, should use DOM API directly instead. 161 if (afterRightBlockChild.GetContainerAs<Element>()) { 162 rightBlockElement = *afterRightBlockChild.ContainerAs<Element>(); 163 } else if (NS_WARN_IF( 164 !afterRightBlockChild.GetContainerParentAs<Element>())) { 165 return Err(NS_ERROR_UNEXPECTED); 166 } else { 167 rightBlockElement = *afterRightBlockChild.GetContainerParentAs<Element>(); 168 } 169 170 auto atStartOfRightText = [&]() MOZ_NEVER_INLINE_DEBUG -> EditorDOMPoint { 171 const WSRunScanner scanner({}, EditorRawDOMPoint(&aRightBlockElement, 0u)); 172 for (EditorRawDOMPointInText atFirstChar = 173 scanner.GetInclusiveNextCharPoint<EditorRawDOMPointInText>( 174 EditorRawDOMPoint(&aRightBlockElement, 0u)); 175 atFirstChar.IsSet(); 176 atFirstChar = 177 scanner.GetInclusiveNextCharPoint<EditorRawDOMPointInText>( 178 atFirstChar.AfterContainer<EditorRawDOMPoint>())) { 179 if (atFirstChar.IsContainerEmpty()) { 180 continue; // Ignore empty text node. 181 } 182 if (atFirstChar.IsCharASCIISpaceOrNBSP() && 183 HTMLEditUtils::IsSimplyEditableNode( 184 *atFirstChar.ContainerAs<Text>())) { 185 return atFirstChar.To<EditorDOMPoint>(); 186 } 187 break; 188 } 189 return EditorDOMPoint(); 190 }(); 191 AutoTrackDOMPoint trackStartOfRightText(aHTMLEditor.RangeUpdaterRef(), 192 &atStartOfRightText); 193 194 // Do br adjustment. 195 // XXX Why don't we delete the <br> first? If so, we can skip to track the 196 // MoveNodeResult at last. 197 const RefPtr<HTMLBRElement> invisibleBRElementAtEndOfLeftBlockElement = 198 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound( 199 {WSRunScanner::Option::OnlyEditableNodes}, 200 EditorDOMPoint::AtEndOf(aLeftBlockElement)); 201 NS_ASSERTION( 202 aPrecedingInvisibleBRElement == invisibleBRElementAtEndOfLeftBlockElement, 203 "The preceding invisible BR element computation was different"); 204 auto moveContentResult = [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT 205 -> Result<MoveNodeResult, nsresult> { 206 // NOTE: Keep syncing with CanMergeLeftAndRightBlockElements() of 207 // AutoInclusiveAncestorBlockElementsJoiner. 208 if (NS_WARN_IF(aListElementTagName.isSome())) { 209 // Since 2002, here was the following comment: 210 // > The idea here is to take all children in rightListElement that are 211 // > past offset, and pull them into leftlistElement. 212 // However, this has never been performed because we are here only when 213 // neither left list nor right list is a descendant of the other but 214 // in such case, getting a list item in the right list node almost 215 // always failed since a variable for offset of 216 // rightListElement->GetChildAt() was not initialized. So, it might be 217 // a bug, but we should keep this traditional behavior for now. If you 218 // find when we get here, please remove this comment if we don't need to 219 // do it. Otherwise, please move children of the right list node to the 220 // end of the left list node. 221 222 // XXX Although, we do nothing here, but for keeping traditional 223 // behavior, we should mark as handled. 224 return MoveNodeResult::HandledResult( 225 EditorDOMPoint::AtEndOf(aLeftBlockElement)); 226 } 227 228 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor); 229 // XXX Why do we ignore the result of AutoMoveOneLineHandler::Run()? 230 NS_ASSERTION(rightBlockElement == afterRightBlockChild.GetContainer(), 231 "The relation is not guaranteed but assumed"); 232 #ifdef DEBUG 233 Result<bool, nsresult> firstLineHasContent = 234 HTMLEditor::AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine( 235 EditorDOMPoint(rightBlockElement, afterRightBlockChild.Offset()), 236 aEditingHost); 237 #endif // #ifdef DEBUG 238 HTMLEditor::AutoMoveOneLineHandler lineMoverToEndOfLeftBlock( 239 aLeftBlockElement); 240 nsresult rv = lineMoverToEndOfLeftBlock.Prepare( 241 aHTMLEditor, 242 EditorDOMPoint(rightBlockElement, afterRightBlockChild.Offset()), 243 aEditingHost); 244 if (NS_FAILED(rv)) { 245 NS_WARNING("AutoMoveOneLineHandler::Prepare() failed"); 246 return Err(rv); 247 } 248 MoveNodeResult moveResult = MoveNodeResult::IgnoredResult( 249 EditorDOMPoint::AtEndOf(aLeftBlockElement)); 250 AutoTrackDOMMoveNodeResult trackMoveResult(aHTMLEditor.RangeUpdaterRef(), 251 &moveResult); 252 Result<MoveNodeResult, nsresult> moveFirstLineResult = 253 lineMoverToEndOfLeftBlock.Run(aHTMLEditor, aEditingHost); 254 if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) { 255 NS_WARNING("AutoMoveOneLineHandler::Run() failed"); 256 return moveFirstLineResult.propagateErr(); 257 } 258 trackMoveResult.FlushAndStopTracking(); 259 260 #ifdef DEBUG 261 MOZ_ASSERT(!firstLineHasContent.isErr()); 262 if (firstLineHasContent.inspect()) { 263 NS_ASSERTION(moveFirstLineResult.inspect().Handled(), 264 "Failed to consider whether moving or not something"); 265 } else { 266 NS_ASSERTION(moveFirstLineResult.inspect().Ignored(), 267 "Failed to consider whether moving or not something"); 268 } 269 #endif // #ifdef DEBUG 270 271 moveResult |= moveFirstLineResult.unwrap(); 272 // Now, all children of rightBlockElement were moved to leftBlockElement. 273 // So, afterRightBlockChild is now invalid. 274 afterRightBlockChild.Clear(); 275 276 return std::move(moveResult); 277 }(); 278 if (MOZ_UNLIKELY(moveContentResult.isErr())) { 279 return moveContentResult; 280 } 281 282 MoveNodeResult unwrappedMoveContentResult = moveContentResult.unwrap(); 283 284 trackStartOfRightText.FlushAndStopTracking(); 285 if (atStartOfRightText.IsInTextNode() && 286 atStartOfRightText.IsSetAndValidInComposedDoc() && 287 atStartOfRightText.IsMiddleOfContainer()) { 288 AutoTrackDOMMoveNodeResult trackMoveContentResult( 289 aHTMLEditor.RangeUpdaterRef(), &unwrappedMoveContentResult); 290 Result<EditorDOMPoint, nsresult> startOfRightTextOrError = 291 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt( 292 aHTMLEditor, atStartOfRightText.AsInText()); 293 if (MOZ_UNLIKELY(startOfRightTextOrError.isErr())) { 294 NS_WARNING("WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt() failed"); 295 return startOfRightTextOrError.propagateErr(); 296 } 297 } 298 299 if (!invisibleBRElementAtEndOfLeftBlockElement || 300 !invisibleBRElementAtEndOfLeftBlockElement->IsInComposedDoc()) { 301 return std::move(unwrappedMoveContentResult); 302 } 303 304 { 305 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor); 306 AutoTrackDOMMoveNodeResult trackMoveContentResult( 307 aHTMLEditor.RangeUpdaterRef(), &unwrappedMoveContentResult); 308 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction( 309 *invisibleBRElementAtEndOfLeftBlockElement); 310 if (NS_FAILED(rv)) { 311 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed, but ignored"); 312 unwrappedMoveContentResult.IgnoreCaretPointSuggestion(); 313 return Err(rv); 314 } 315 } 316 return std::move(unwrappedMoveContentResult); 317 } 318 319 // static 320 Result<MoveNodeResult, nsresult> WhiteSpaceVisibilityKeeper:: 321 MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement( 322 HTMLEditor& aHTMLEditor, Element& aLeftBlockElement, 323 Element& aRightBlockElement, const EditorDOMPoint& aAtLeftBlockChild, 324 nsIContent& aLeftContentInBlock, 325 const Maybe<nsAtom*>& aListElementTagName, 326 const HTMLBRElement* aPrecedingInvisibleBRElement, 327 const Element& aEditingHost) { 328 MOZ_ASSERT( 329 EditorUtils::IsDescendantOf(aRightBlockElement, aLeftBlockElement)); 330 MOZ_ASSERT( 331 &aLeftBlockElement == &aLeftContentInBlock || 332 EditorUtils::IsDescendantOf(aLeftContentInBlock, aLeftBlockElement)); 333 MOZ_ASSERT(&aLeftBlockElement == aAtLeftBlockChild.GetContainer()); 334 335 OwningNonNull<Element> originalLeftBlockElement = aLeftBlockElement; 336 OwningNonNull<Element> leftBlockElement = aLeftBlockElement; 337 EditorDOMPoint atLeftBlockChild(aAtLeftBlockChild); 338 // First, delete invisible white-spaces before the right block. 339 { 340 AutoTrackDOMPoint tracker(aHTMLEditor.RangeUpdaterRef(), &atLeftBlockChild); 341 nsresult rv = 342 WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore( 343 aHTMLEditor, EditorDOMPoint(&aRightBlockElement)); 344 if (NS_FAILED(rv)) { 345 NS_WARNING( 346 "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore() " 347 "failed"); 348 return Err(rv); 349 } 350 // Next, delete invisible white-spaces at start of the right block. 351 rv = WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter( 352 aHTMLEditor, EditorDOMPoint(&aRightBlockElement, 0u)); 353 if (NS_FAILED(rv)) { 354 NS_WARNING( 355 "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter() " 356 "failed"); 357 return Err(rv); 358 } 359 tracker.FlushAndStopTracking(); 360 if (NS_WARN_IF(!atLeftBlockChild.IsInContentNodeAndValidInComposedDoc())) { 361 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 362 } 363 } 364 // Finally, make sure that we won't create new invisible white-spaces. 365 AutoTrackDOMPoint tracker(aHTMLEditor.RangeUpdaterRef(), &atLeftBlockChild); 366 Result<EditorDOMPoint, nsresult> afterLastVisibleThingOrError = 367 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter( 368 aHTMLEditor, EditorDOMPoint(&aRightBlockElement, 0u), 369 {NormalizeOption::StopIfPrecedingWhiteSpacesEndsWithNBP}); 370 if (MOZ_UNLIKELY(afterLastVisibleThingOrError.isErr())) { 371 NS_WARNING( 372 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() failed"); 373 return afterLastVisibleThingOrError.propagateErr(); 374 } 375 Result<EditorDOMPoint, nsresult> atFirstVisibleThingOrError = 376 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore( 377 aHTMLEditor, atLeftBlockChild, {}); 378 if (MOZ_UNLIKELY(atFirstVisibleThingOrError.isErr())) { 379 NS_WARNING( 380 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() failed"); 381 return atFirstVisibleThingOrError.propagateErr(); 382 } 383 tracker.FlushAndStopTracking(); 384 if (NS_WARN_IF(!atLeftBlockChild.IsInContentNodeAndValidInComposedDoc())) { 385 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 386 } 387 388 // XXX atLeftBlockChild.GetContainerAs<Element>() should always return 389 // an element pointer so that probably here should not use 390 // accessors of EditorDOMPoint, should use DOM API directly instead. 391 if (Element* nearestAncestor = 392 atLeftBlockChild.GetContainerOrContainerParentElement()) { 393 leftBlockElement = *nearestAncestor; 394 } else { 395 return Err(NS_ERROR_UNEXPECTED); 396 } 397 398 auto atStartOfRightText = [&]() MOZ_NEVER_INLINE_DEBUG -> EditorDOMPoint { 399 const WSRunScanner scanner({}, EditorRawDOMPoint(&aRightBlockElement, 0u)); 400 for (EditorRawDOMPointInText atFirstChar = 401 scanner.GetInclusiveNextCharPoint<EditorRawDOMPointInText>( 402 EditorRawDOMPoint(&aRightBlockElement, 0u)); 403 atFirstChar.IsSet(); 404 atFirstChar = 405 scanner.GetInclusiveNextCharPoint<EditorRawDOMPointInText>( 406 atFirstChar.AfterContainer<EditorRawDOMPoint>())) { 407 if (atFirstChar.IsContainerEmpty()) { 408 continue; // Ignore empty text node. 409 } 410 if (atFirstChar.IsCharASCIISpaceOrNBSP() && 411 HTMLEditUtils::IsSimplyEditableNode( 412 *atFirstChar.ContainerAs<Text>())) { 413 return atFirstChar.To<EditorDOMPoint>(); 414 } 415 break; 416 } 417 return EditorDOMPoint(); 418 }(); 419 AutoTrackDOMPoint trackStartOfRightText(aHTMLEditor.RangeUpdaterRef(), 420 &atStartOfRightText); 421 422 // Do br adjustment. 423 // XXX Why don't we delete the <br> first? If so, we can skip to track the 424 // MoveNodeResult at last. 425 const RefPtr<HTMLBRElement> invisibleBRElementBeforeLeftBlockElement = 426 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound( 427 {WSRunScanner::Option::OnlyEditableNodes}, atLeftBlockChild); 428 NS_ASSERTION( 429 aPrecedingInvisibleBRElement == invisibleBRElementBeforeLeftBlockElement, 430 "The preceding invisible BR element computation was different"); 431 auto moveContentResult = [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT 432 -> Result<MoveNodeResult, nsresult> { 433 // NOTE: Keep syncing with CanMergeLeftAndRightBlockElements() of 434 // AutoInclusiveAncestorBlockElementsJoiner. 435 if (aListElementTagName.isSome()) { 436 // XXX Why do we ignore the error from MoveChildrenWithTransaction()? 437 MOZ_ASSERT(originalLeftBlockElement == atLeftBlockChild.GetContainer(), 438 "This is not guaranteed, but assumed"); 439 #ifdef DEBUG 440 Result<bool, nsresult> rightBlockHasContent = 441 aHTMLEditor.CanMoveChildren(aRightBlockElement, aLeftBlockElement); 442 #endif // #ifdef DEBUG 443 MoveNodeResult moveResult = MoveNodeResult::IgnoredResult(EditorDOMPoint( 444 atLeftBlockChild.GetContainer(), atLeftBlockChild.Offset())); 445 AutoTrackDOMMoveNodeResult trackMoveResult(aHTMLEditor.RangeUpdaterRef(), 446 &moveResult); 447 // TODO: Stop using HTMLEditor::PreserveWhiteSpaceStyle::No due to no 448 // tests. 449 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor); 450 Result<MoveNodeResult, nsresult> moveChildrenResult = 451 aHTMLEditor.MoveChildrenWithTransaction( 452 aRightBlockElement, moveResult.NextInsertionPointRef(), 453 HTMLEditor::PreserveWhiteSpaceStyle::No, 454 HTMLEditor::RemoveIfCommentNode::Yes); 455 if (MOZ_UNLIKELY(moveChildrenResult.isErr())) { 456 if (NS_WARN_IF(moveChildrenResult.inspectErr() == 457 NS_ERROR_EDITOR_DESTROYED)) { 458 return moveChildrenResult; 459 } 460 NS_WARNING( 461 "HTMLEditor::MoveChildrenWithTransaction() failed, but ignored"); 462 } else { 463 #ifdef DEBUG 464 MOZ_ASSERT(!rightBlockHasContent.isErr()); 465 if (rightBlockHasContent.inspect()) { 466 NS_ASSERTION(moveChildrenResult.inspect().Handled(), 467 "Failed to consider whether moving or not children"); 468 } else { 469 NS_ASSERTION(moveChildrenResult.inspect().Ignored(), 470 "Failed to consider whether moving or not children"); 471 } 472 #endif // #ifdef DEBUG 473 trackMoveResult.FlushAndStopTracking(); 474 moveResult |= moveChildrenResult.unwrap(); 475 } 476 // atLeftBlockChild was moved to rightListElement. So, it's invalid now. 477 atLeftBlockChild.Clear(); 478 479 return std::move(moveResult); 480 } 481 482 // Left block is a parent of right block, and the parent of the previous 483 // visible content. Right block is a child and contains the contents we 484 // want to move. 485 EditorDOMPoint pointToMoveFirstLineContent; 486 if (&aLeftContentInBlock == leftBlockElement) { 487 // We are working with valid HTML, aLeftContentInBlock is a block 488 // element, and is therefore allowed to contain aRightBlockElement. This 489 // is the simple case, we will simply move the content in 490 // aRightBlockElement out of its block. 491 pointToMoveFirstLineContent = atLeftBlockChild; 492 MOZ_ASSERT(pointToMoveFirstLineContent.GetContainer() == 493 &aLeftBlockElement); 494 } else { 495 if (NS_WARN_IF(!aLeftContentInBlock.IsInComposedDoc())) { 496 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 497 } 498 // We try to work as well as possible with HTML that's already invalid. 499 // Although "right block" is a block, and a block must not be contained 500 // in inline elements, reality is that broken documents do exist. The 501 // DIRECT parent of "left NODE" might be an inline element. Previous 502 // versions of this code skipped inline parents until the first block 503 // parent was found (and used "left block" as the destination). 504 // However, in some situations this strategy moves the content to an 505 // unexpected position. (see bug 200416) The new idea is to make the 506 // moving content a sibling, next to the previous visible content. 507 pointToMoveFirstLineContent.SetAfter(&aLeftContentInBlock); 508 if (NS_WARN_IF(!pointToMoveFirstLineContent.IsInContentNode())) { 509 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 510 } 511 } 512 513 MOZ_ASSERT(pointToMoveFirstLineContent.IsSetAndValid()); 514 515 // Because we don't want the moving content to receive the style of the 516 // previous content, we split the previous content's style. 517 518 #ifdef DEBUG 519 Result<bool, nsresult> firstLineHasContent = 520 HTMLEditor::AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine( 521 EditorDOMPoint(&aRightBlockElement, 0u), aEditingHost); 522 #endif // #ifdef DEBUG 523 524 if (&aLeftContentInBlock != &aEditingHost) { 525 Result<SplitNodeResult, nsresult> splitNodeResult = 526 aHTMLEditor.SplitAncestorStyledInlineElementsAt( 527 pointToMoveFirstLineContent, EditorInlineStyle::RemoveAllStyles(), 528 HTMLEditor::SplitAtEdges::eDoNotCreateEmptyContainer); 529 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 530 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed"); 531 return splitNodeResult.propagateErr(); 532 } 533 SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap(); 534 nsresult rv = unwrappedSplitNodeResult.SuggestCaretPointTo( 535 aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, 536 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 537 if (NS_FAILED(rv)) { 538 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed"); 539 return Err(rv); 540 } 541 if (!unwrappedSplitNodeResult.DidSplit()) { 542 // If nothing was split, we should move the first line content to 543 // after the parent inline elements. 544 for (EditorDOMPoint parentPoint = pointToMoveFirstLineContent; 545 pointToMoveFirstLineContent.IsEndOfContainer() && 546 pointToMoveFirstLineContent.IsInContentNode(); 547 pointToMoveFirstLineContent = EditorDOMPoint::After( 548 *pointToMoveFirstLineContent.ContainerAs<nsIContent>())) { 549 if (pointToMoveFirstLineContent.GetContainer() == 550 &aLeftBlockElement || 551 NS_WARN_IF(pointToMoveFirstLineContent.GetContainer() == 552 &aEditingHost)) { 553 break; 554 } 555 } 556 if (NS_WARN_IF(!pointToMoveFirstLineContent.IsInContentNode())) { 557 return Err(NS_ERROR_FAILURE); 558 } 559 } else if (unwrappedSplitNodeResult.Handled()) { 560 // If se split something, we should move the first line contents 561 // before the right elements. 562 if (nsIContent* nextContentAtSplitPoint = 563 unwrappedSplitNodeResult.GetNextContent()) { 564 pointToMoveFirstLineContent.Set(nextContentAtSplitPoint); 565 if (NS_WARN_IF(!pointToMoveFirstLineContent.IsInContentNode())) { 566 return Err(NS_ERROR_FAILURE); 567 } 568 } else { 569 pointToMoveFirstLineContent = 570 unwrappedSplitNodeResult.AtSplitPoint<EditorDOMPoint>(); 571 if (NS_WARN_IF(!pointToMoveFirstLineContent.IsInContentNode())) { 572 return Err(NS_ERROR_FAILURE); 573 } 574 } 575 } 576 MOZ_DIAGNOSTIC_ASSERT(pointToMoveFirstLineContent.IsSetAndValid()); 577 } 578 579 MoveNodeResult moveResult = 580 MoveNodeResult::IgnoredResult(pointToMoveFirstLineContent); 581 HTMLEditor::AutoMoveOneLineHandler lineMoverToPoint( 582 pointToMoveFirstLineContent); 583 nsresult rv = lineMoverToPoint.Prepare( 584 aHTMLEditor, EditorDOMPoint(&aRightBlockElement, 0u), aEditingHost); 585 if (NS_FAILED(rv)) { 586 NS_WARNING("AutoMoveOneLineHandler::Prepare() failed"); 587 return Err(rv); 588 } 589 AutoTrackDOMMoveNodeResult trackMoveResult(aHTMLEditor.RangeUpdaterRef(), 590 &moveResult); 591 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor); 592 Result<MoveNodeResult, nsresult> moveFirstLineResult = 593 lineMoverToPoint.Run(aHTMLEditor, aEditingHost); 594 if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) { 595 NS_WARNING("AutoMoveOneLineHandler::Run() failed"); 596 return moveFirstLineResult.propagateErr(); 597 } 598 599 #ifdef DEBUG 600 MOZ_ASSERT(!firstLineHasContent.isErr()); 601 if (firstLineHasContent.inspect()) { 602 NS_ASSERTION(moveFirstLineResult.inspect().Handled(), 603 "Failed to consider whether moving or not something"); 604 } else { 605 NS_ASSERTION(moveFirstLineResult.inspect().Ignored(), 606 "Failed to consider whether moving or not something"); 607 } 608 #endif // #ifdef DEBUG 609 610 trackMoveResult.FlushAndStopTracking(); 611 moveResult |= moveFirstLineResult.unwrap(); 612 return std::move(moveResult); 613 }(); 614 if (MOZ_UNLIKELY(moveContentResult.isErr())) { 615 return moveContentResult; 616 } 617 618 MoveNodeResult unwrappedMoveContentResult = moveContentResult.unwrap(); 619 620 trackStartOfRightText.FlushAndStopTracking(); 621 if (atStartOfRightText.IsInTextNode() && 622 atStartOfRightText.IsSetAndValidInComposedDoc() && 623 atStartOfRightText.IsMiddleOfContainer()) { 624 AutoTrackDOMMoveNodeResult trackMoveContentResult( 625 aHTMLEditor.RangeUpdaterRef(), &unwrappedMoveContentResult); 626 Result<EditorDOMPoint, nsresult> startOfRightTextOrError = 627 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt( 628 aHTMLEditor, atStartOfRightText.AsInText()); 629 if (MOZ_UNLIKELY(startOfRightTextOrError.isErr())) { 630 NS_WARNING("WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt() failed"); 631 return startOfRightTextOrError.propagateErr(); 632 } 633 } 634 635 if (!invisibleBRElementBeforeLeftBlockElement || 636 !invisibleBRElementBeforeLeftBlockElement->IsInComposedDoc()) { 637 return std::move(unwrappedMoveContentResult); 638 } 639 640 { 641 AutoTrackDOMMoveNodeResult trackMoveContentResult( 642 aHTMLEditor.RangeUpdaterRef(), &unwrappedMoveContentResult); 643 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor); 644 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction( 645 *invisibleBRElementBeforeLeftBlockElement); 646 if (NS_FAILED(rv)) { 647 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed, but ignored"); 648 unwrappedMoveContentResult.IgnoreCaretPointSuggestion(); 649 return Err(rv); 650 } 651 } 652 return std::move(unwrappedMoveContentResult); 653 } 654 655 // static 656 Result<MoveNodeResult, nsresult> WhiteSpaceVisibilityKeeper:: 657 MergeFirstLineOfRightBlockElementIntoLeftBlockElement( 658 HTMLEditor& aHTMLEditor, Element& aLeftBlockElement, 659 Element& aRightBlockElement, const Maybe<nsAtom*>& aListElementTagName, 660 const HTMLBRElement* aPrecedingInvisibleBRElement, 661 const Element& aEditingHost) { 662 MOZ_ASSERT( 663 !EditorUtils::IsDescendantOf(aLeftBlockElement, aRightBlockElement)); 664 MOZ_ASSERT( 665 !EditorUtils::IsDescendantOf(aRightBlockElement, aLeftBlockElement)); 666 667 // First, delete invisible white-spaces at end of the left block 668 nsresult rv = WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore( 669 aHTMLEditor, EditorDOMPoint::AtEndOf(aLeftBlockElement)); 670 if (NS_FAILED(rv)) { 671 NS_WARNING( 672 "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore() " 673 "failed"); 674 return Err(rv); 675 } 676 // Next, delete invisible white-spaces at start of the right block and 677 // normalize the leading visible white-spaces. 678 rv = WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter( 679 aHTMLEditor, EditorDOMPoint(&aRightBlockElement, 0u)); 680 if (NS_FAILED(rv)) { 681 NS_WARNING( 682 "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter() " 683 "failed"); 684 return Err(rv); 685 } 686 // Finally, make sure to that we won't create new invisible white-spaces. 687 Result<EditorDOMPoint, nsresult> atFirstVisibleThingOrError = 688 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter( 689 aHTMLEditor, EditorDOMPoint(&aRightBlockElement, 0u), 690 {NormalizeOption::StopIfFollowingWhiteSpacesStartsWithNBSP}); 691 if (MOZ_UNLIKELY(atFirstVisibleThingOrError.isErr())) { 692 NS_WARNING( 693 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() failed"); 694 return atFirstVisibleThingOrError.propagateErr(); 695 } 696 Result<EditorDOMPoint, nsresult> afterLastVisibleThingOrError = 697 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore( 698 aHTMLEditor, EditorDOMPoint::AtEndOf(aLeftBlockElement), {}); 699 if (MOZ_UNLIKELY(afterLastVisibleThingOrError.isErr())) { 700 NS_WARNING( 701 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore() failed"); 702 return afterLastVisibleThingOrError.propagateErr(); 703 } 704 auto atStartOfRightText = [&]() MOZ_NEVER_INLINE_DEBUG -> EditorDOMPoint { 705 const WSRunScanner scanner({}, EditorRawDOMPoint(&aRightBlockElement, 0u)); 706 for (EditorRawDOMPointInText atFirstChar = 707 scanner.GetInclusiveNextCharPoint<EditorRawDOMPointInText>( 708 EditorRawDOMPoint(&aRightBlockElement, 0u)); 709 atFirstChar.IsSet(); 710 atFirstChar = 711 scanner.GetInclusiveNextCharPoint<EditorRawDOMPointInText>( 712 atFirstChar.AfterContainer<EditorRawDOMPoint>())) { 713 if (atFirstChar.IsContainerEmpty()) { 714 continue; // Ignore empty text node. 715 } 716 if (atFirstChar.IsCharASCIISpaceOrNBSP() && 717 HTMLEditUtils::IsSimplyEditableNode( 718 *atFirstChar.ContainerAs<Text>())) { 719 return atFirstChar.To<EditorDOMPoint>(); 720 } 721 break; 722 } 723 return EditorDOMPoint(); 724 }(); 725 AutoTrackDOMPoint trackStartOfRightText(aHTMLEditor.RangeUpdaterRef(), 726 &atStartOfRightText); 727 // Do br adjustment. 728 // XXX Why don't we delete the <br> first? If so, we can skip to track the 729 // MoveNodeResult at last. 730 const RefPtr<HTMLBRElement> invisibleBRElementAtEndOfLeftBlockElement = 731 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound( 732 {WSRunScanner::Option::OnlyEditableNodes}, 733 EditorDOMPoint::AtEndOf(aLeftBlockElement)); 734 NS_ASSERTION( 735 aPrecedingInvisibleBRElement == invisibleBRElementAtEndOfLeftBlockElement, 736 "The preceding invisible BR element computation was different"); 737 auto moveContentResult = [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT 738 -> Result<MoveNodeResult, nsresult> { 739 if (aListElementTagName.isSome() || 740 // TODO: We should stop merging entire blocks even if they have same 741 // white-space style because Chrome behave so. However, it's risky to 742 // change our behavior in the major cases so that we should do it in 743 // a bug to manage only the change. 744 (aLeftBlockElement.NodeInfo()->NameAtom() == 745 aRightBlockElement.NodeInfo()->NameAtom() && 746 EditorUtils::GetComputedWhiteSpaceStyles(aLeftBlockElement) == 747 EditorUtils::GetComputedWhiteSpaceStyles(aRightBlockElement))) { 748 MoveNodeResult moveResult = MoveNodeResult::IgnoredResult( 749 EditorDOMPoint::AtEndOf(aLeftBlockElement)); 750 AutoTrackDOMMoveNodeResult trackMoveResult(aHTMLEditor.RangeUpdaterRef(), 751 &moveResult); 752 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor); 753 // Nodes are same type. merge them. 754 EditorDOMPoint atFirstChildOfRightNode; 755 nsresult rv = aHTMLEditor.JoinNearestEditableNodesWithTransaction( 756 aLeftBlockElement, aRightBlockElement, &atFirstChildOfRightNode); 757 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 758 return Err(NS_ERROR_EDITOR_DESTROYED); 759 } 760 NS_WARNING_ASSERTION( 761 NS_SUCCEEDED(rv), 762 "HTMLEditor::JoinNearestEditableNodesWithTransaction()" 763 " failed, but ignored"); 764 if (aListElementTagName.isSome() && atFirstChildOfRightNode.IsSet()) { 765 Result<CreateElementResult, nsresult> convertListTypeResult = 766 aHTMLEditor.ChangeListElementType( 767 // XXX Shouldn't be aLeftBlockElement here? 768 aRightBlockElement, MOZ_KnownLive(*aListElementTagName.ref()), 769 *nsGkAtoms::li); 770 if (MOZ_UNLIKELY(convertListTypeResult.isErr())) { 771 if (NS_WARN_IF(convertListTypeResult.inspectErr() == 772 NS_ERROR_EDITOR_DESTROYED)) { 773 return Err(NS_ERROR_EDITOR_DESTROYED); 774 } 775 NS_WARNING("HTMLEditor::ChangeListElementType() failed, but ignored"); 776 } else { 777 // There is AutoTransactionConserveSelection above, therefore, we 778 // don't need to update selection here. 779 convertListTypeResult.inspect().IgnoreCaretPointSuggestion(); 780 } 781 } 782 trackMoveResult.FlushAndStopTracking(); 783 moveResult |= MoveNodeResult::HandledResult( 784 EditorDOMPoint::AtEndOf(aLeftBlockElement)); 785 return std::move(moveResult); 786 } 787 788 #ifdef DEBUG 789 Result<bool, nsresult> firstLineHasContent = 790 HTMLEditor::AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine( 791 EditorDOMPoint(&aRightBlockElement, 0u), aEditingHost); 792 #endif // #ifdef DEBUG 793 794 MoveNodeResult moveResult = MoveNodeResult::IgnoredResult( 795 EditorDOMPoint::AtEndOf(aLeftBlockElement)); 796 // Nodes are dissimilar types. 797 HTMLEditor::AutoMoveOneLineHandler lineMoverToEndOfLeftBlock( 798 aLeftBlockElement); 799 nsresult rv = lineMoverToEndOfLeftBlock.Prepare( 800 aHTMLEditor, EditorDOMPoint(&aRightBlockElement, 0u), aEditingHost); 801 if (NS_FAILED(rv)) { 802 NS_WARNING("AutoMoveOneLineHandler::Prepare() failed"); 803 return Err(rv); 804 } 805 AutoTrackDOMMoveNodeResult trackMoveResult(aHTMLEditor.RangeUpdaterRef(), 806 &moveResult); 807 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor); 808 Result<MoveNodeResult, nsresult> moveFirstLineResult = 809 lineMoverToEndOfLeftBlock.Run(aHTMLEditor, aEditingHost); 810 if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) { 811 NS_WARNING("AutoMoveOneLineHandler::Run() failed"); 812 return moveFirstLineResult.propagateErr(); 813 } 814 815 #ifdef DEBUG 816 MOZ_ASSERT(!firstLineHasContent.isErr()); 817 if (firstLineHasContent.inspect()) { 818 NS_ASSERTION(moveFirstLineResult.inspect().Handled(), 819 "Failed to consider whether moving or not something"); 820 } else { 821 NS_ASSERTION(moveFirstLineResult.inspect().Ignored(), 822 "Failed to consider whether moving or not something"); 823 } 824 #endif // #ifdef DEBUG 825 826 trackMoveResult.FlushAndStopTracking(); 827 moveResult |= moveFirstLineResult.unwrap(); 828 return std::move(moveResult); 829 }(); 830 if (MOZ_UNLIKELY(moveContentResult.isErr())) { 831 return moveContentResult; 832 } 833 834 MoveNodeResult unwrappedMoveContentResult = moveContentResult.unwrap(); 835 836 trackStartOfRightText.FlushAndStopTracking(); 837 if (atStartOfRightText.IsInTextNode() && 838 atStartOfRightText.IsSetAndValidInComposedDoc() && 839 atStartOfRightText.IsMiddleOfContainer()) { 840 AutoTrackDOMMoveNodeResult trackMoveContentResult( 841 aHTMLEditor.RangeUpdaterRef(), &unwrappedMoveContentResult); 842 Result<EditorDOMPoint, nsresult> startOfRightTextOrError = 843 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt( 844 aHTMLEditor, atStartOfRightText.AsInText()); 845 if (MOZ_UNLIKELY(startOfRightTextOrError.isErr())) { 846 NS_WARNING("WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt() failed"); 847 return startOfRightTextOrError.propagateErr(); 848 } 849 } 850 851 if (!invisibleBRElementAtEndOfLeftBlockElement || 852 !invisibleBRElementAtEndOfLeftBlockElement->IsInComposedDoc()) { 853 unwrappedMoveContentResult.ForceToMarkAsHandled(); 854 return std::move(unwrappedMoveContentResult); 855 } 856 857 { 858 AutoTrackDOMMoveNodeResult trackMoveContentResult( 859 aHTMLEditor.RangeUpdaterRef(), &unwrappedMoveContentResult); 860 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor); 861 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction( 862 *invisibleBRElementAtEndOfLeftBlockElement); 863 // XXX In other top level if blocks, the result of 864 // DeleteNodeWithTransaction() is ignored. Why does only this result 865 // is respected? 866 if (NS_FAILED(rv)) { 867 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 868 unwrappedMoveContentResult.IgnoreCaretPointSuggestion(); 869 return Err(rv); 870 } 871 } 872 return std::move(unwrappedMoveContentResult); 873 } 874 875 // static 876 Result<EditorDOMPoint, nsresult> 877 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt( 878 HTMLEditor& aHTMLEditor, const EditorDOMPointInText& aPoint) { 879 MOZ_ASSERT(aPoint.IsSet()); 880 MOZ_ASSERT(!aPoint.IsEndOfContainer()); 881 882 if (!aPoint.IsCharCollapsibleASCIISpaceOrNBSP()) { 883 return aPoint.To<EditorDOMPoint>(); 884 } 885 886 const HTMLEditor::ReplaceWhiteSpacesData normalizedWhiteSpaces = 887 aHTMLEditor.GetNormalizedStringAt(aPoint).GetMinimizedData( 888 *aPoint.ContainerAs<Text>()); 889 if (!normalizedWhiteSpaces.ReplaceLength()) { 890 return aPoint.To<EditorDOMPoint>(); 891 } 892 893 const OwningNonNull<Text> textNode = *aPoint.ContainerAs<Text>(); 894 Result<InsertTextResult, nsresult> insertTextResultOrError = 895 aHTMLEditor.ReplaceTextWithTransaction(textNode, normalizedWhiteSpaces); 896 if (MOZ_UNLIKELY(insertTextResultOrError.isErr())) { 897 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed"); 898 return insertTextResultOrError.propagateErr(); 899 } 900 return insertTextResultOrError.unwrap().UnwrapCaretPoint(); 901 } 902 903 // static 904 Result<EditorDOMPoint, nsresult> 905 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore( 906 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint, 907 NormalizeOptions aOptions // NOLINT(performance-unnecessary-value-param) 908 ) { 909 MOZ_ASSERT(aPoint.IsSetAndValid()); 910 MOZ_ASSERT_IF(aPoint.IsInTextNode(), !aPoint.IsMiddleOfContainer()); 911 MOZ_ASSERT( 912 !aOptions.contains(NormalizeOption::HandleOnlyFollowingWhiteSpaces)); 913 914 const RefPtr<Element> colsetBlockElement = 915 aPoint.IsInContentNode() ? HTMLEditUtils::GetInclusiveAncestorElement( 916 *aPoint.ContainerAs<nsIContent>(), 917 HTMLEditUtils::ClosestEditableBlockElement, 918 BlockInlineCheck::UseComputedDisplayStyle) 919 : nullptr; 920 EditorDOMPoint afterLastVisibleThing(aPoint); 921 AutoTArray<OwningNonNull<nsIContent>, 32> unnecessaryContents; 922 for (nsIContent* previousContent = 923 aPoint.IsInTextNode() && aPoint.IsEndOfContainer() 924 ? aPoint.ContainerAs<Text>() 925 : HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( 926 aPoint, 927 {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock}, 928 BlockInlineCheck::UseComputedDisplayStyle, 929 colsetBlockElement); 930 previousContent; 931 previousContent = 932 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( 933 EditorRawDOMPoint(previousContent), 934 {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock}, 935 BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement)) { 936 if (!HTMLEditUtils::IsSimplyEditableNode(*previousContent)) { 937 // XXX Assume non-editable nodes are visible. 938 break; 939 } 940 const RefPtr<Text> precedingTextNode = Text::FromNode(previousContent); 941 if (!precedingTextNode && 942 HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousContent)) { 943 afterLastVisibleThing.SetAfter(previousContent); 944 break; 945 } 946 if (!precedingTextNode || !precedingTextNode->TextDataLength()) { 947 // If it's an empty inline element like `<b></b>` or an empty `Text`, 948 // delete it. 949 nsIContent* emptyInlineContent = 950 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 951 *previousContent, BlockInlineCheck::UseComputedDisplayStyle); 952 if (!emptyInlineContent) { 953 emptyInlineContent = previousContent; 954 } 955 unnecessaryContents.AppendElement(*emptyInlineContent); 956 continue; 957 } 958 const auto atLastChar = 959 EditorRawDOMPointInText::AtLastContentOf(*precedingTextNode); 960 if (!atLastChar.IsCharCollapsibleASCIISpaceOrNBSP()) { 961 afterLastVisibleThing.SetAfter(precedingTextNode); 962 break; 963 } 964 if (aOptions.contains( 965 NormalizeOption::StopIfPrecedingWhiteSpacesEndsWithNBP) && 966 atLastChar.IsCharNBSP()) { 967 afterLastVisibleThing.SetAfter(precedingTextNode); 968 break; 969 } 970 const HTMLEditor::ReplaceWhiteSpacesData replaceData = 971 aHTMLEditor.GetNormalizedStringAt(atLastChar.AsInText()) 972 .GetMinimizedData(*precedingTextNode); 973 if (!replaceData.ReplaceLength()) { 974 afterLastVisibleThing.SetAfter(precedingTextNode); 975 break; 976 } 977 // If the Text node has only invisible white-spaces, delete the node itself. 978 if (replaceData.ReplaceLength() == precedingTextNode->TextDataLength() && 979 replaceData.mNormalizedString.IsEmpty()) { 980 nsIContent* emptyInlineContent = 981 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 982 *precedingTextNode, BlockInlineCheck::UseComputedDisplayStyle); 983 if (!emptyInlineContent) { 984 emptyInlineContent = precedingTextNode; 985 } 986 unnecessaryContents.AppendElement(*emptyInlineContent); 987 continue; 988 } 989 Result<InsertTextResult, nsresult> replaceWhiteSpacesResultOrError = 990 aHTMLEditor.ReplaceTextWithTransaction(*precedingTextNode, replaceData); 991 if (MOZ_UNLIKELY(replaceWhiteSpacesResultOrError.isErr())) { 992 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed"); 993 return replaceWhiteSpacesResultOrError.propagateErr(); 994 } 995 InsertTextResult replaceWhiteSpacesResult = 996 replaceWhiteSpacesResultOrError.unwrap(); 997 replaceWhiteSpacesResult.IgnoreCaretPointSuggestion(); 998 afterLastVisibleThing = replaceWhiteSpacesResult.EndOfInsertedTextRef(); 999 } 1000 1001 AutoTrackDOMPoint trackAfterLastVisibleThing(aHTMLEditor.RangeUpdaterRef(), 1002 &afterLastVisibleThing); 1003 for (const auto& contentToDelete : unnecessaryContents) { 1004 if (MOZ_UNLIKELY(!contentToDelete->IsInComposedDoc())) { 1005 continue; 1006 } 1007 nsresult rv = 1008 aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(contentToDelete)); 1009 if (NS_FAILED(rv)) { 1010 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 1011 return Err(rv); 1012 } 1013 } 1014 trackAfterLastVisibleThing.FlushAndStopTracking(); 1015 if (NS_WARN_IF( 1016 !afterLastVisibleThing.IsInContentNodeAndValidInComposedDoc())) { 1017 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1018 } 1019 return std::move(afterLastVisibleThing); 1020 } 1021 1022 // static 1023 Result<EditorDOMPoint, nsresult> 1024 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter( 1025 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint, 1026 NormalizeOptions aOptions // NOLINT(performance-unnecessary-value-param) 1027 ) { 1028 MOZ_ASSERT(aPoint.IsSetAndValid()); 1029 MOZ_ASSERT_IF(aPoint.IsInTextNode(), !aPoint.IsMiddleOfContainer()); 1030 MOZ_ASSERT( 1031 !aOptions.contains(NormalizeOption::HandleOnlyPrecedingWhiteSpaces)); 1032 1033 const RefPtr<Element> colsetBlockElement = 1034 aPoint.IsInContentNode() ? HTMLEditUtils::GetInclusiveAncestorElement( 1035 *aPoint.ContainerAs<nsIContent>(), 1036 HTMLEditUtils::ClosestEditableBlockElement, 1037 BlockInlineCheck::UseComputedDisplayStyle) 1038 : nullptr; 1039 EditorDOMPoint atFirstVisibleThing(aPoint); 1040 AutoTArray<OwningNonNull<nsIContent>, 32> unnecessaryContents; 1041 for (nsIContent* nextContent = 1042 aPoint.IsInTextNode() && aPoint.IsStartOfContainer() 1043 ? aPoint.ContainerAs<Text>() 1044 : HTMLEditUtils::GetNextLeafContentOrNextBlockElement( 1045 aPoint, 1046 {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock}, 1047 BlockInlineCheck::UseComputedDisplayStyle, 1048 colsetBlockElement); 1049 nextContent; 1050 nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( 1051 EditorRawDOMPoint::After(*nextContent), 1052 {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock}, 1053 BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement)) { 1054 if (!HTMLEditUtils::IsSimplyEditableNode(*nextContent)) { 1055 // XXX Assume non-editable nodes are visible. 1056 break; 1057 } 1058 const RefPtr<Text> followingTextNode = Text::FromNode(nextContent); 1059 if (!followingTextNode && 1060 HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*nextContent)) { 1061 atFirstVisibleThing.Set(nextContent); 1062 break; 1063 } 1064 if (!followingTextNode || !followingTextNode->TextDataLength()) { 1065 // If it's an empty inline element like `<b></b>` or an empty `Text`, 1066 // delete it. 1067 nsIContent* emptyInlineContent = 1068 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 1069 *nextContent, BlockInlineCheck::UseComputedDisplayStyle); 1070 if (!emptyInlineContent) { 1071 emptyInlineContent = nextContent; 1072 } 1073 unnecessaryContents.AppendElement(*emptyInlineContent); 1074 continue; 1075 } 1076 const auto atFirstChar = EditorRawDOMPointInText(followingTextNode, 0u); 1077 if (!atFirstChar.IsCharCollapsibleASCIISpaceOrNBSP()) { 1078 atFirstVisibleThing.Set(followingTextNode); 1079 break; 1080 } 1081 if (aOptions.contains( 1082 NormalizeOption::StopIfPrecedingWhiteSpacesEndsWithNBP) && 1083 atFirstChar.IsCharNBSP()) { 1084 atFirstVisibleThing.Set(followingTextNode); 1085 break; 1086 } 1087 const HTMLEditor::ReplaceWhiteSpacesData replaceData = 1088 aHTMLEditor.GetNormalizedStringAt(atFirstChar.AsInText()) 1089 .GetMinimizedData(*followingTextNode); 1090 if (!replaceData.ReplaceLength()) { 1091 atFirstVisibleThing.Set(followingTextNode); 1092 break; 1093 } 1094 // If the Text node has only invisible white-spaces, delete the node itself. 1095 if (replaceData.ReplaceLength() == followingTextNode->TextDataLength() && 1096 replaceData.mNormalizedString.IsEmpty()) { 1097 nsIContent* emptyInlineContent = 1098 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 1099 *followingTextNode, BlockInlineCheck::UseComputedDisplayStyle); 1100 if (!emptyInlineContent) { 1101 emptyInlineContent = followingTextNode; 1102 } 1103 unnecessaryContents.AppendElement(*emptyInlineContent); 1104 continue; 1105 } 1106 Result<InsertTextResult, nsresult> replaceWhiteSpacesResultOrError = 1107 aHTMLEditor.ReplaceTextWithTransaction(*followingTextNode, replaceData); 1108 if (MOZ_UNLIKELY(replaceWhiteSpacesResultOrError.isErr())) { 1109 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed"); 1110 return replaceWhiteSpacesResultOrError.propagateErr(); 1111 } 1112 replaceWhiteSpacesResultOrError.unwrap().IgnoreCaretPointSuggestion(); 1113 atFirstVisibleThing.Set(followingTextNode, 0u); 1114 break; 1115 } 1116 1117 AutoTrackDOMPoint trackAtFirstVisibleThing(aHTMLEditor.RangeUpdaterRef(), 1118 &atFirstVisibleThing); 1119 for (const auto& contentToDelete : unnecessaryContents) { 1120 if (MOZ_UNLIKELY(!contentToDelete->IsInComposedDoc())) { 1121 continue; 1122 } 1123 nsresult rv = 1124 aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(contentToDelete)); 1125 if (NS_FAILED(rv)) { 1126 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 1127 return Err(rv); 1128 } 1129 } 1130 trackAtFirstVisibleThing.FlushAndStopTracking(); 1131 if (NS_WARN_IF(!atFirstVisibleThing.IsInContentNodeAndValidInComposedDoc())) { 1132 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1133 } 1134 return std::move(atFirstVisibleThing); 1135 } 1136 1137 // static 1138 Result<EditorDOMPoint, nsresult> 1139 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt( 1140 HTMLEditor& aHTMLEditor, const EditorDOMPointInText& aPointToSplit, 1141 NormalizeOptions aOptions // NOLINT(performance-unnecessary-value-param) 1142 ) { 1143 MOZ_ASSERT(aPointToSplit.IsSetAndValid()); 1144 1145 if (EditorUtils::IsWhiteSpacePreformatted( 1146 *aPointToSplit.ContainerAs<Text>())) { 1147 return aPointToSplit.To<EditorDOMPoint>(); 1148 } 1149 1150 const OwningNonNull<Text> textNode = *aPointToSplit.ContainerAs<Text>(); 1151 if (!textNode->TextDataLength()) { 1152 // Delete if it's an empty `Text` node and removable. 1153 if (!HTMLEditUtils::IsRemovableNode(*textNode)) { 1154 // It's logically odd to call this for non-editable `Text`, but it may 1155 // happen if surrounding white-space sequence contains empty non-editable 1156 // `Text`. In that case, the caller needs to normalize its preceding 1157 // `Text` nodes too. 1158 return EditorDOMPoint(); 1159 } 1160 const nsCOMPtr<nsINode> parentNode = textNode->GetParentNode(); 1161 const nsCOMPtr<nsIContent> nextSibling = textNode->GetNextSibling(); 1162 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(textNode); 1163 if (NS_FAILED(rv)) { 1164 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 1165 return Err(rv); 1166 } 1167 if (NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parentNode)) { 1168 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1169 } 1170 return nextSibling ? EditorDOMPoint(nextSibling) 1171 : EditorDOMPoint::AtEndOf(*parentNode); 1172 } 1173 1174 const HTMLEditor::ReplaceWhiteSpacesData replacePrecedingWhiteSpacesData = 1175 aPointToSplit.IsStartOfContainer() || 1176 aOptions.contains( 1177 NormalizeOption::HandleOnlyFollowingWhiteSpaces) || 1178 (aOptions.contains( 1179 NormalizeOption::StopIfPrecedingWhiteSpacesEndsWithNBP) && 1180 aPointToSplit.IsPreviousCharNBSP()) 1181 ? HTMLEditor::ReplaceWhiteSpacesData() 1182 : aHTMLEditor.GetPrecedingNormalizedStringToSplitAt(aPointToSplit); 1183 const HTMLEditor::ReplaceWhiteSpacesData replaceFollowingWhiteSpaceData = 1184 aPointToSplit.IsEndOfContainer() || 1185 aOptions.contains( 1186 NormalizeOption::HandleOnlyPrecedingWhiteSpaces) || 1187 (aOptions.contains( 1188 NormalizeOption::StopIfFollowingWhiteSpacesStartsWithNBSP) && 1189 aPointToSplit.IsCharNBSP()) 1190 ? HTMLEditor::ReplaceWhiteSpacesData() 1191 : aHTMLEditor.GetFollowingNormalizedStringToSplitAt(aPointToSplit); 1192 const HTMLEditor::ReplaceWhiteSpacesData replaceWhiteSpacesData = 1193 (replacePrecedingWhiteSpacesData + replaceFollowingWhiteSpaceData) 1194 .GetMinimizedData(*textNode); 1195 if (!replaceWhiteSpacesData.ReplaceLength()) { 1196 return aPointToSplit.To<EditorDOMPoint>(); 1197 } 1198 if (replaceWhiteSpacesData.mNormalizedString.IsEmpty() && 1199 replaceWhiteSpacesData.ReplaceLength() == textNode->TextDataLength()) { 1200 // If there is only invisible white-spaces, mNormalizedString is empty 1201 // string but replace length is same the the `Text` length. In this case, we 1202 // should delete the `Text` to avoid empty `Text` to stay in the DOM tree. 1203 const nsCOMPtr<nsINode> parentNode = textNode->GetParentNode(); 1204 const nsCOMPtr<nsIContent> nextSibling = textNode->GetNextSibling(); 1205 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(textNode); 1206 if (NS_FAILED(rv)) { 1207 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 1208 return Err(rv); 1209 } 1210 if (NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parentNode)) { 1211 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1212 } 1213 return nextSibling ? EditorDOMPoint(nextSibling) 1214 : EditorDOMPoint::AtEndOf(*parentNode); 1215 } 1216 Result<InsertTextResult, nsresult> replaceWhiteSpacesResultOrError = 1217 aHTMLEditor.ReplaceTextWithTransaction(textNode, replaceWhiteSpacesData); 1218 if (MOZ_UNLIKELY(replaceWhiteSpacesResultOrError.isErr())) { 1219 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed"); 1220 return replaceWhiteSpacesResultOrError.propagateErr(); 1221 } 1222 replaceWhiteSpacesResultOrError.unwrap().IgnoreCaretPointSuggestion(); 1223 const uint32_t offsetToSplit = 1224 aPointToSplit.Offset() - replacePrecedingWhiteSpacesData.ReplaceLength() + 1225 replacePrecedingWhiteSpacesData.mNormalizedString.Length(); 1226 if (NS_WARN_IF(textNode->TextDataLength() < offsetToSplit)) { 1227 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1228 } 1229 return EditorDOMPoint(textNode, offsetToSplit); 1230 } 1231 1232 // static 1233 Result<EditorDOMPoint, nsresult> 1234 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt( 1235 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToSplit, 1236 NormalizeOptions aOptions // NOLINT(performance-unnecessary-value-param) 1237 ) { 1238 MOZ_ASSERT(aPointToSplit.IsSet()); 1239 1240 // If the insertion point is not in composed doc, we're probably initializing 1241 // an element which will be inserted. In such case, the caller should own the 1242 // responsibility for normalizing the white-spaces. 1243 if (!aPointToSplit.IsInComposedDoc()) { 1244 return aPointToSplit; 1245 } 1246 1247 EditorDOMPoint pointToSplit(aPointToSplit); 1248 { 1249 AutoTrackDOMPoint trackPointToSplit(aHTMLEditor.RangeUpdaterRef(), 1250 &pointToSplit); 1251 Result<EditorDOMPoint, nsresult> pointToSplitOrError = 1252 WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces(aHTMLEditor, 1253 pointToSplit); 1254 if (MOZ_UNLIKELY(pointToSplitOrError.isErr())) { 1255 NS_WARNING( 1256 "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces() failed"); 1257 return pointToSplitOrError.propagateErr(); 1258 } 1259 } 1260 1261 if (NS_WARN_IF(!pointToSplit.IsInContentNode())) { 1262 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1263 } 1264 1265 if (pointToSplit.IsInTextNode()) { 1266 Result<EditorDOMPoint, nsresult> pointToSplitOrError = 1267 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt( 1268 aHTMLEditor, pointToSplit.AsInText(), aOptions); 1269 if (MOZ_UNLIKELY(pointToSplitOrError.isErr())) { 1270 NS_WARNING( 1271 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt() " 1272 "failed"); 1273 return pointToSplitOrError.propagateErr(); 1274 } 1275 pointToSplit = pointToSplitOrError.unwrap().To<EditorDOMPoint>(); 1276 if (NS_WARN_IF(!pointToSplit.IsInContentNode())) { 1277 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1278 } 1279 // If we normalize white-spaces in middle of the `Text`, we don't need to 1280 // touch surrounding `Text` nodes. 1281 if (pointToSplit.IsMiddleOfContainer()) { 1282 return pointToSplit; 1283 } 1284 } 1285 1286 // Preceding and/or following white-space sequence may be across multiple 1287 // `Text` nodes. Then, they may become unexpectedly visible without 1288 // normalizing the white-spaces. Therefore, we need to list up all possible 1289 // `Text` nodes first. Then, normalize them unless the `Text` is not 1290 const RefPtr<Element> closestBlockElement = 1291 HTMLEditUtils::GetInclusiveAncestorElement( 1292 *pointToSplit.ContainerAs<nsIContent>(), 1293 HTMLEditUtils::ClosestBlockElement, 1294 BlockInlineCheck::UseComputedDisplayStyle); 1295 AutoTArray<OwningNonNull<Text>, 3> precedingTextNodes, followingTextNodes; 1296 if (!pointToSplit.IsInTextNode() || pointToSplit.IsStartOfContainer()) { 1297 for (nsCOMPtr<nsIContent> previousContent = 1298 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( 1299 pointToSplit, {LeafNodeType::LeafNodeOrChildBlock}, 1300 BlockInlineCheck::UseComputedDisplayStyle, 1301 closestBlockElement); 1302 previousContent; 1303 previousContent = 1304 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( 1305 *previousContent, {LeafNodeType::LeafNodeOrChildBlock}, 1306 BlockInlineCheck::UseComputedDisplayStyle, 1307 closestBlockElement)) { 1308 if (auto* const textNode = Text::FromNode(previousContent)) { 1309 if (!HTMLEditUtils::IsSimplyEditableNode(*textNode) && 1310 textNode->TextDataLength()) { 1311 break; 1312 } 1313 if (aOptions.contains( 1314 NormalizeOption::StopIfPrecedingWhiteSpacesEndsWithNBP) && 1315 textNode->DataBuffer().SafeLastChar() == HTMLEditUtils::kNBSP) { 1316 break; 1317 } 1318 precedingTextNodes.AppendElement(*textNode); 1319 if (textNode->TextIsOnlyWhitespace()) { 1320 // white-space only `Text` will be removed, so, we need to check 1321 // preceding one too. 1322 continue; 1323 } 1324 break; 1325 } 1326 if (auto* const element = Element::FromNode(previousContent)) { 1327 if (HTMLEditUtils::IsBlockElement( 1328 *element, BlockInlineCheck::UseComputedDisplayStyle) || 1329 !HTMLEditUtils::IsContainerNode(*element) || 1330 HTMLEditUtils::IsReplacedElement(*element)) { 1331 break; 1332 } 1333 // Ignore invisible inline elements 1334 } 1335 } 1336 } 1337 if (!pointToSplit.IsInTextNode() || pointToSplit.IsEndOfContainer()) { 1338 for (nsCOMPtr<nsIContent> nextContent = 1339 HTMLEditUtils::GetNextLeafContentOrNextBlockElement( 1340 pointToSplit, {LeafNodeType::LeafNodeOrChildBlock}, 1341 BlockInlineCheck::UseComputedDisplayStyle, 1342 closestBlockElement); 1343 nextContent; 1344 nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( 1345 *nextContent, {LeafNodeType::LeafNodeOrChildBlock}, 1346 BlockInlineCheck::UseComputedDisplayStyle, closestBlockElement)) { 1347 if (auto* const textNode = Text::FromNode(nextContent)) { 1348 if (!HTMLEditUtils::IsSimplyEditableNode(*textNode) && 1349 textNode->TextDataLength()) { 1350 break; 1351 } 1352 if (aOptions.contains( 1353 NormalizeOption::StopIfFollowingWhiteSpacesStartsWithNBSP) && 1354 textNode->DataBuffer().SafeFirstChar() == HTMLEditUtils::kNBSP) { 1355 break; 1356 } 1357 followingTextNodes.AppendElement(*textNode); 1358 if (textNode->TextIsOnlyWhitespace() && 1359 EditorUtils::IsWhiteSpacePreformatted(*textNode)) { 1360 // white-space only `Text` will be removed, so, we need to check next 1361 // one too. 1362 continue; 1363 } 1364 break; 1365 } 1366 if (auto* const element = Element::FromNode(nextContent)) { 1367 if (HTMLEditUtils::IsBlockElement( 1368 *element, BlockInlineCheck::UseComputedDisplayStyle) || 1369 !HTMLEditUtils::IsContainerNode(*element) || 1370 HTMLEditUtils::IsReplacedElement(*element)) { 1371 break; 1372 } 1373 // Ignore invisible inline elements 1374 } 1375 } 1376 } 1377 AutoTrackDOMPoint trackPointToSplit(aHTMLEditor.RangeUpdaterRef(), 1378 &pointToSplit); 1379 for (const auto& textNode : precedingTextNodes) { 1380 Result<EditorDOMPoint, nsresult> normalizeWhiteSpacesResultOrError = 1381 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt( 1382 aHTMLEditor, EditorDOMPointInText::AtEndOf(textNode), aOptions); 1383 if (MOZ_UNLIKELY(normalizeWhiteSpacesResultOrError.isErr())) { 1384 NS_WARNING( 1385 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt() " 1386 "failed"); 1387 return normalizeWhiteSpacesResultOrError.propagateErr(); 1388 } 1389 if (normalizeWhiteSpacesResultOrError.inspect().IsInTextNode() && 1390 !normalizeWhiteSpacesResultOrError.inspect().IsStartOfContainer()) { 1391 // The white-space sequence started from middle of this node, so, we need 1392 // to do this for the preceding nodes. 1393 break; 1394 } 1395 } 1396 for (const auto& textNode : followingTextNodes) { 1397 Result<EditorDOMPoint, nsresult> normalizeWhiteSpacesResultOrError = 1398 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt( 1399 aHTMLEditor, EditorDOMPointInText(textNode, 0u), aOptions); 1400 if (MOZ_UNLIKELY(normalizeWhiteSpacesResultOrError.isErr())) { 1401 NS_WARNING( 1402 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt() " 1403 "failed"); 1404 return normalizeWhiteSpacesResultOrError.propagateErr(); 1405 } 1406 if (normalizeWhiteSpacesResultOrError.inspect().IsInTextNode() && 1407 !normalizeWhiteSpacesResultOrError.inspect().IsEndOfContainer()) { 1408 // The white-space sequence ended in middle of this node, so, we need 1409 // to do this for the following nodes. 1410 break; 1411 } 1412 } 1413 trackPointToSplit.FlushAndStopTracking(); 1414 if (NS_WARN_IF(!pointToSplit.IsInContentNode())) { 1415 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1416 } 1417 return std::move(pointToSplit); 1418 } 1419 1420 Result<EditorDOMRange, nsresult> 1421 WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToJoin( 1422 HTMLEditor& aHTMLEditor, const EditorDOMRange& aRangeToDelete) { 1423 MOZ_ASSERT(!aRangeToDelete.Collapsed()); 1424 1425 // Special case if the range for deleting text in same `Text`. In the case, 1426 // we need to normalize the white-space sequence which may be joined after 1427 // deletion. 1428 if (aRangeToDelete.StartRef().IsInTextNode() && 1429 aRangeToDelete.InSameContainer()) { 1430 const RefPtr<Text> textNode = aRangeToDelete.StartRef().ContainerAs<Text>(); 1431 Result<EditorDOMRange, nsresult> rangeToDeleteOrError = 1432 WhiteSpaceVisibilityKeeper:: 1433 NormalizeSurroundingWhiteSpacesToDeleteCharacters( 1434 aHTMLEditor, *textNode, aRangeToDelete.StartRef().Offset(), 1435 aRangeToDelete.EndRef().Offset() - 1436 aRangeToDelete.StartRef().Offset()); 1437 NS_WARNING_ASSERTION( 1438 rangeToDeleteOrError.isOk(), 1439 "WhiteSpaceVisibilityKeeper::" 1440 "NormalizeSurroundingWhiteSpacesToDeleteCharacters() failed"); 1441 return rangeToDeleteOrError; 1442 } 1443 1444 EditorDOMRange rangeToDelete(aRangeToDelete); 1445 // First, delete all invisible white-spaces around the end boundary. 1446 // The end boundary may be middle of invisible white-spaces. If so, 1447 // NormalizeWhiteSpacesToSplitTextNodeAt() won't work well for this. 1448 { 1449 AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(), 1450 &rangeToDelete); 1451 const WSScanResult nextThing = 1452 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 1453 {}, rangeToDelete.StartRef()); 1454 if (nextThing.ReachedLineBoundary()) { 1455 nsresult rv = 1456 WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore( 1457 aHTMLEditor, nextThing.PointAtReachedContent<EditorDOMPoint>()); 1458 if (NS_FAILED(rv)) { 1459 NS_WARNING( 1460 "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore() " 1461 "failed"); 1462 return Err(rv); 1463 } 1464 } else { 1465 Result<EditorDOMPoint, nsresult> 1466 deleteInvisibleLeadingWhiteSpaceResultOrError = 1467 WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces( 1468 aHTMLEditor, rangeToDelete.EndRef()); 1469 if (MOZ_UNLIKELY(deleteInvisibleLeadingWhiteSpaceResultOrError.isErr())) { 1470 NS_WARNING( 1471 "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces() " 1472 "failed"); 1473 return deleteInvisibleLeadingWhiteSpaceResultOrError.propagateErr(); 1474 } 1475 } 1476 trackRangeToDelete.FlushAndStopTracking(); 1477 if (NS_WARN_IF(!rangeToDelete.IsPositionedAndValidInComposedDoc())) { 1478 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1479 } 1480 } 1481 1482 // Then, normalize white-spaces after the end boundary. 1483 if (rangeToDelete.EndRef().IsInTextNode() && 1484 rangeToDelete.EndRef().IsMiddleOfContainer()) { 1485 Result<EditorDOMPoint, nsresult> pointToSplitOrError = 1486 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt( 1487 aHTMLEditor, rangeToDelete.EndRef().AsInText(), 1488 {NormalizeOption::HandleOnlyFollowingWhiteSpaces}); 1489 if (MOZ_UNLIKELY(pointToSplitOrError.isErr())) { 1490 NS_WARNING( 1491 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt(" 1492 ") failed"); 1493 return pointToSplitOrError.propagateErr(); 1494 } 1495 EditorDOMPoint pointToSplit = pointToSplitOrError.unwrap(); 1496 if (pointToSplit.IsSet() && pointToSplit != rangeToDelete.EndRef()) { 1497 MOZ_ASSERT(rangeToDelete.StartRef().EqualsOrIsBefore(pointToSplit)); 1498 rangeToDelete.SetEnd(std::move(pointToSplit)); 1499 } 1500 } else { 1501 AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(), 1502 &rangeToDelete); 1503 Result<EditorDOMPoint, nsresult> atFirstVisibleThingOrError = 1504 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter( 1505 aHTMLEditor, rangeToDelete.EndRef(), {}); 1506 if (MOZ_UNLIKELY(atFirstVisibleThingOrError.isErr())) { 1507 NS_WARNING( 1508 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() failed"); 1509 return atFirstVisibleThingOrError.propagateErr(); 1510 } 1511 trackRangeToDelete.FlushAndStopTracking(); 1512 if (NS_WARN_IF(!rangeToDelete.IsPositionedAndValidInComposedDoc())) { 1513 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1514 } 1515 } 1516 1517 // If cleaning up the white-spaces around the end boundary made the range 1518 // collapsed, the range was in invisible white-spaces. So, in the case, we 1519 // don't need to do nothing. 1520 if (MOZ_UNLIKELY(rangeToDelete.Collapsed())) { 1521 return rangeToDelete; 1522 } 1523 1524 // Next, delete the invisible white-spaces around the start boundary. 1525 { 1526 AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(), 1527 &rangeToDelete); 1528 Result<EditorDOMPoint, nsresult> 1529 deleteInvisibleTrailingWhiteSpaceResultOrError = 1530 WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces( 1531 aHTMLEditor, rangeToDelete.StartRef()); 1532 if (MOZ_UNLIKELY(deleteInvisibleTrailingWhiteSpaceResultOrError.isErr())) { 1533 NS_WARNING( 1534 "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces() failed"); 1535 return deleteInvisibleTrailingWhiteSpaceResultOrError.propagateErr(); 1536 } 1537 trackRangeToDelete.FlushAndStopTracking(); 1538 if (NS_WARN_IF(!rangeToDelete.IsPositionedAndValidInComposedDoc())) { 1539 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1540 } 1541 } 1542 1543 // Finally, normalize white-spaces before the start boundary only when 1544 // the start boundary is middle of a `Text` node. This is compatible with 1545 // the other browsers. 1546 if (rangeToDelete.StartRef().IsInTextNode() && 1547 rangeToDelete.StartRef().IsMiddleOfContainer()) { 1548 AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(), 1549 &rangeToDelete); 1550 Result<EditorDOMPoint, nsresult> afterLastVisibleThingOrError = 1551 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt( 1552 aHTMLEditor, rangeToDelete.StartRef().AsInText(), 1553 {NormalizeOption::HandleOnlyPrecedingWhiteSpaces}); 1554 if (MOZ_UNLIKELY(afterLastVisibleThingOrError.isErr())) { 1555 NS_WARNING( 1556 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt() " 1557 "failed"); 1558 return afterLastVisibleThingOrError.propagateErr(); 1559 } 1560 trackRangeToDelete.FlushAndStopTracking(); 1561 EditorDOMPoint pointToSplit = afterLastVisibleThingOrError.unwrap(); 1562 if (pointToSplit.IsSet() && pointToSplit != rangeToDelete.StartRef()) { 1563 MOZ_ASSERT(pointToSplit.EqualsOrIsBefore(rangeToDelete.EndRef())); 1564 rangeToDelete.SetStart(std::move(pointToSplit)); 1565 } 1566 } 1567 return rangeToDelete; 1568 } 1569 1570 Result<EditorDOMRange, nsresult> 1571 WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToDeleteCharacters( 1572 HTMLEditor& aHTMLEditor, Text& aTextNode, uint32_t aOffset, 1573 uint32_t aLength) { 1574 MOZ_ASSERT(aOffset <= aTextNode.TextDataLength()); 1575 MOZ_ASSERT(aOffset + aLength <= aTextNode.TextDataLength()); 1576 1577 const HTMLEditor::ReplaceWhiteSpacesData normalizedWhiteSpacesData = 1578 aHTMLEditor.GetSurroundingNormalizedStringToDelete(aTextNode, aOffset, 1579 aLength); 1580 EditorDOMRange rangeToDelete(EditorDOMPoint(&aTextNode, aOffset), 1581 EditorDOMPoint(&aTextNode, aOffset + aLength)); 1582 if (!normalizedWhiteSpacesData.ReplaceLength()) { 1583 return rangeToDelete; 1584 } 1585 // mNewOffsetAfterReplace is set to aOffset after applying replacing the 1586 // range. 1587 MOZ_ASSERT(normalizedWhiteSpacesData.mNewOffsetAfterReplace != UINT32_MAX); 1588 MOZ_ASSERT(normalizedWhiteSpacesData.mNewOffsetAfterReplace >= 1589 normalizedWhiteSpacesData.mReplaceStartOffset); 1590 MOZ_ASSERT(normalizedWhiteSpacesData.mNewOffsetAfterReplace <= 1591 normalizedWhiteSpacesData.mReplaceEndOffset); 1592 #ifdef DEBUG 1593 { 1594 const HTMLEditor::ReplaceWhiteSpacesData 1595 normalizedPrecedingWhiteSpacesData = 1596 normalizedWhiteSpacesData.PreviousDataOfNewOffset(aOffset); 1597 const HTMLEditor::ReplaceWhiteSpacesData 1598 normalizedFollowingWhiteSpacesData = 1599 normalizedWhiteSpacesData.NextDataOfNewOffset(aOffset + aLength); 1600 MOZ_ASSERT(normalizedPrecedingWhiteSpacesData.ReplaceLength() + aLength + 1601 normalizedFollowingWhiteSpacesData.ReplaceLength() == 1602 normalizedWhiteSpacesData.ReplaceLength()); 1603 MOZ_ASSERT( 1604 normalizedPrecedingWhiteSpacesData.mNormalizedString.Length() + 1605 normalizedFollowingWhiteSpacesData.mNormalizedString.Length() == 1606 normalizedWhiteSpacesData.mNormalizedString.Length()); 1607 } 1608 #endif 1609 const HTMLEditor::ReplaceWhiteSpacesData normalizedPrecedingWhiteSpacesData = 1610 normalizedWhiteSpacesData.PreviousDataOfNewOffset(aOffset) 1611 .GetMinimizedData(aTextNode); 1612 const HTMLEditor::ReplaceWhiteSpacesData normalizedFollowingWhiteSpacesData = 1613 normalizedWhiteSpacesData.NextDataOfNewOffset(aOffset + aLength) 1614 .GetMinimizedData(aTextNode); 1615 if (normalizedFollowingWhiteSpacesData.ReplaceLength()) { 1616 AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(), 1617 &rangeToDelete); 1618 Result<InsertTextResult, nsresult> 1619 replaceFollowingWhiteSpacesResultOrError = 1620 aHTMLEditor.ReplaceTextWithTransaction( 1621 aTextNode, normalizedFollowingWhiteSpacesData); 1622 if (MOZ_UNLIKELY(replaceFollowingWhiteSpacesResultOrError.isErr())) { 1623 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed"); 1624 return replaceFollowingWhiteSpacesResultOrError.propagateErr(); 1625 } 1626 trackRangeToDelete.FlushAndStopTracking(); 1627 if (NS_WARN_IF(!rangeToDelete.IsPositioned())) { 1628 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1629 } 1630 } 1631 if (normalizedPrecedingWhiteSpacesData.ReplaceLength()) { 1632 AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(), 1633 &rangeToDelete); 1634 Result<InsertTextResult, nsresult> 1635 replacePrecedingWhiteSpacesResultOrError = 1636 aHTMLEditor.ReplaceTextWithTransaction( 1637 aTextNode, normalizedPrecedingWhiteSpacesData); 1638 if (MOZ_UNLIKELY(replacePrecedingWhiteSpacesResultOrError.isErr())) { 1639 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed"); 1640 return replacePrecedingWhiteSpacesResultOrError.propagateErr(); 1641 } 1642 trackRangeToDelete.FlushAndStopTracking(); 1643 if (NS_WARN_IF(!rangeToDelete.IsPositioned())) { 1644 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1645 } 1646 } 1647 return std::move(rangeToDelete); 1648 } 1649 1650 // static 1651 Result<CreateLineBreakResult, nsresult> 1652 WhiteSpaceVisibilityKeeper::InsertLineBreak( 1653 LineBreakType aLineBreakType, HTMLEditor& aHTMLEditor, 1654 const EditorDOMPoint& aPointToInsert) { 1655 if (MOZ_UNLIKELY(NS_WARN_IF(!aPointToInsert.IsSet()))) { 1656 return Err(NS_ERROR_INVALID_ARG); 1657 } 1658 1659 EditorDOMPoint pointToInsert(aPointToInsert); 1660 // Chrome does not normalize preceding white-spaces at least when it ends 1661 // with an NBSP. 1662 Result<EditorDOMPoint, nsresult> 1663 normalizeSurroundingWhiteSpacesResultOrError = 1664 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt( 1665 aHTMLEditor, aPointToInsert, 1666 {NormalizeOption::StopIfPrecedingWhiteSpacesEndsWithNBP}); 1667 if (MOZ_UNLIKELY(normalizeSurroundingWhiteSpacesResultOrError.isErr())) { 1668 NS_WARNING( 1669 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt() failed"); 1670 return normalizeSurroundingWhiteSpacesResultOrError.propagateErr(); 1671 } 1672 pointToInsert = normalizeSurroundingWhiteSpacesResultOrError.unwrap(); 1673 if (NS_WARN_IF(!pointToInsert.IsSet())) { 1674 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1675 } 1676 1677 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 1678 aHTMLEditor.InsertLineBreak(WithTransaction::Yes, aLineBreakType, 1679 pointToInsert); 1680 NS_WARNING_ASSERTION(insertBRElementResultOrError.isOk(), 1681 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 1682 "aLineBreakType, eNone) failed"); 1683 return insertBRElementResultOrError; 1684 } 1685 1686 nsresult WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter( 1687 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint) { 1688 MOZ_ASSERT(aPoint.IsInContentNode()); 1689 1690 const RefPtr<Element> colsetBlockElement = 1691 HTMLEditUtils::GetInclusiveAncestorElement( 1692 *aPoint.ContainerAs<nsIContent>(), 1693 HTMLEditUtils::ClosestEditableBlockElement, 1694 BlockInlineCheck::UseComputedDisplayStyle); 1695 EditorDOMPoint atFirstInvisibleWhiteSpace; 1696 AutoTArray<OwningNonNull<nsIContent>, 32> unnecessaryContents; 1697 for (nsIContent* nextContent = 1698 HTMLEditUtils::GetNextLeafContentOrNextBlockElement( 1699 aPoint, 1700 {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock, 1701 HTMLEditUtils::LeafNodeType::TreatCommentAsLeafNode}, 1702 BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement); 1703 nextContent; 1704 nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( 1705 EditorRawDOMPoint::After(*nextContent), 1706 {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock, 1707 HTMLEditUtils::LeafNodeType::TreatCommentAsLeafNode}, 1708 BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement)) { 1709 if (!HTMLEditUtils::IsSimplyEditableNode(*nextContent)) { 1710 // XXX Assume non-editable nodes are visible. 1711 break; 1712 } 1713 const RefPtr<Text> followingTextNode = Text::FromNode(nextContent); 1714 if (!followingTextNode && 1715 HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*nextContent)) { 1716 break; 1717 } 1718 if (!followingTextNode || !followingTextNode->TextDataLength()) { 1719 // If it's an empty inline element like `<b></b>` or an empty `Text`, 1720 // delete it. 1721 nsIContent* emptyInlineContent = 1722 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 1723 *nextContent, BlockInlineCheck::UseComputedDisplayStyle); 1724 if (!emptyInlineContent) { 1725 emptyInlineContent = nextContent; 1726 } 1727 unnecessaryContents.AppendElement(*emptyInlineContent); 1728 continue; 1729 } 1730 const EditorRawDOMPointInText atFirstChar(followingTextNode, 0u); 1731 if (!atFirstChar.IsCharCollapsibleASCIISpace()) { 1732 break; 1733 } 1734 // If the preceding Text is collapsed and invisible, we should delete it 1735 // and keep deleting preceding invisible white-spaces. 1736 if (!HTMLEditUtils::IsVisibleTextNode(*followingTextNode)) { 1737 nsIContent* emptyInlineContent = 1738 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 1739 *followingTextNode, BlockInlineCheck::UseComputedDisplayStyle); 1740 if (!emptyInlineContent) { 1741 emptyInlineContent = followingTextNode; 1742 } 1743 unnecessaryContents.AppendElement(*emptyInlineContent); 1744 continue; 1745 } 1746 Result<EditorDOMPoint, nsresult> startOfTextOrError = 1747 WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces( 1748 aHTMLEditor, EditorDOMPoint(followingTextNode, 0u)); 1749 if (MOZ_UNLIKELY(startOfTextOrError.isErr())) { 1750 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed"); 1751 return startOfTextOrError.unwrapErr(); 1752 } 1753 break; 1754 } 1755 1756 for (const auto& contentToDelete : unnecessaryContents) { 1757 if (MOZ_UNLIKELY(!contentToDelete->IsInComposedDoc())) { 1758 continue; 1759 } 1760 nsresult rv = 1761 aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(contentToDelete)); 1762 if (NS_FAILED(rv)) { 1763 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 1764 return rv; 1765 } 1766 } 1767 return NS_OK; 1768 } 1769 1770 nsresult WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore( 1771 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint) { 1772 MOZ_ASSERT(aPoint.IsInContentNode()); 1773 1774 const RefPtr<Element> colsetBlockElement = 1775 HTMLEditUtils::GetInclusiveAncestorElement( 1776 *aPoint.ContainerAs<nsIContent>(), 1777 HTMLEditUtils::ClosestEditableBlockElement, 1778 BlockInlineCheck::UseComputedDisplayStyle); 1779 EditorDOMPoint atFirstInvisibleWhiteSpace; 1780 AutoTArray<OwningNonNull<nsIContent>, 32> unnecessaryContents; 1781 for (nsIContent* previousContent = 1782 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( 1783 aPoint, 1784 {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock, 1785 HTMLEditUtils::LeafNodeType::TreatCommentAsLeafNode}, 1786 BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement); 1787 previousContent; 1788 previousContent = 1789 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( 1790 EditorRawDOMPoint(previousContent), 1791 {HTMLEditUtils::LeafNodeType::LeafNodeOrChildBlock, 1792 HTMLEditUtils::LeafNodeType::TreatCommentAsLeafNode}, 1793 BlockInlineCheck::UseComputedDisplayStyle, colsetBlockElement)) { 1794 if (!HTMLEditUtils::IsSimplyEditableNode(*previousContent)) { 1795 // XXX Assume non-editable nodes are visible. 1796 break; 1797 } 1798 const RefPtr<Text> precedingTextNode = Text::FromNode(previousContent); 1799 if (!precedingTextNode && 1800 HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousContent)) { 1801 break; 1802 } 1803 if (!precedingTextNode || !precedingTextNode->TextDataLength()) { 1804 // If it's an empty inline element like `<b></b>` or an empty `Text`, 1805 // delete it. 1806 nsIContent* emptyInlineContent = 1807 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 1808 *previousContent, BlockInlineCheck::UseComputedDisplayStyle); 1809 if (!emptyInlineContent) { 1810 emptyInlineContent = previousContent; 1811 } 1812 unnecessaryContents.AppendElement(*emptyInlineContent); 1813 continue; 1814 } 1815 const auto atLastChar = 1816 EditorRawDOMPointInText::AtLastContentOf(*precedingTextNode); 1817 if (!atLastChar.IsCharCollapsibleASCIISpace()) { 1818 break; 1819 } 1820 // If the preceding Text is collapsed and invisible, we should delete it 1821 // and keep deleting preceding invisible white-spaces. 1822 if (!HTMLEditUtils::IsVisibleTextNode(*precedingTextNode)) { 1823 nsIContent* emptyInlineContent = 1824 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 1825 *precedingTextNode, BlockInlineCheck::UseComputedDisplayStyle); 1826 if (!emptyInlineContent) { 1827 emptyInlineContent = precedingTextNode; 1828 } 1829 unnecessaryContents.AppendElement(*emptyInlineContent); 1830 continue; 1831 } 1832 Result<EditorDOMPoint, nsresult> endOfTextOrResult = 1833 WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces( 1834 aHTMLEditor, EditorDOMPoint::AtEndOf(*precedingTextNode)); 1835 if (MOZ_UNLIKELY(endOfTextOrResult.isErr())) { 1836 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed"); 1837 return endOfTextOrResult.unwrapErr(); 1838 } 1839 break; 1840 } 1841 1842 for (const auto& contentToDelete : Reversed(unnecessaryContents)) { 1843 if (MOZ_UNLIKELY(!contentToDelete->IsInComposedDoc())) { 1844 continue; 1845 } 1846 nsresult rv = 1847 aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(contentToDelete)); 1848 if (NS_FAILED(rv)) { 1849 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 1850 return rv; 1851 } 1852 } 1853 return NS_OK; 1854 } 1855 1856 Result<EditorDOMPoint, nsresult> 1857 WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces( 1858 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint) { 1859 if (EditorUtils::IsWhiteSpacePreformatted( 1860 *aPoint.ContainerAs<nsIContent>())) { 1861 return EditorDOMPoint(); 1862 } 1863 if (aPoint.IsInTextNode() && 1864 // If there is a previous char and it's not a collapsible ASCII 1865 // white-space, the point is not in the leading white-spaces. 1866 (!aPoint.IsStartOfContainer() && !aPoint.IsPreviousCharASCIISpace()) && 1867 // If it does not points a collapsible ASCII white-space, the point is not 1868 // in the trailing white-spaces. 1869 (!aPoint.IsEndOfContainer() && !aPoint.IsCharCollapsibleASCIISpace())) { 1870 return EditorDOMPoint(); 1871 } 1872 const Element* const maybeNonEditableClosestBlockElement = 1873 HTMLEditUtils::GetInclusiveAncestorElement( 1874 *aPoint.ContainerAs<nsIContent>(), HTMLEditUtils::ClosestBlockElement, 1875 BlockInlineCheck::UseComputedDisplayStyle); 1876 if (MOZ_UNLIKELY(!maybeNonEditableClosestBlockElement)) { 1877 return EditorDOMPoint(); // aPoint is not in a block. 1878 } 1879 const TextFragmentData textFragmentDataForLeadingWhiteSpaces( 1880 {WSRunScanner::Option::OnlyEditableNodes}, 1881 aPoint.IsStartOfContainer() && 1882 (aPoint.GetContainer() == maybeNonEditableClosestBlockElement || 1883 aPoint.GetContainer()->IsEditingHost()) 1884 ? aPoint 1885 : aPoint.PreviousPointOrParentPoint<EditorDOMPoint>(), 1886 maybeNonEditableClosestBlockElement); 1887 if (NS_WARN_IF(!textFragmentDataForLeadingWhiteSpaces.IsInitialized())) { 1888 return Err(NS_ERROR_FAILURE); 1889 } 1890 1891 { 1892 const EditorDOMRange& leadingWhiteSpaceRange = 1893 textFragmentDataForLeadingWhiteSpaces 1894 .InvisibleLeadingWhiteSpaceRangeRef(); 1895 if (leadingWhiteSpaceRange.IsPositioned() && 1896 !leadingWhiteSpaceRange.Collapsed()) { 1897 EditorDOMPoint endOfLeadingWhiteSpaces(leadingWhiteSpaceRange.EndRef()); 1898 AutoTrackDOMPoint trackEndOfLeadingWhiteSpaces( 1899 aHTMLEditor.RangeUpdaterRef(), &endOfLeadingWhiteSpaces); 1900 Result<CaretPoint, nsresult> caretPointOrError = 1901 aHTMLEditor.DeleteTextAndTextNodesWithTransaction( 1902 leadingWhiteSpaceRange.StartRef(), 1903 leadingWhiteSpaceRange.EndRef(), 1904 HTMLEditor::TreatEmptyTextNodes:: 1905 KeepIfContainerOfRangeBoundaries); 1906 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 1907 NS_WARNING( 1908 "HTMLEditor::DeleteTextAndTextNodesWithTransaction(" 1909 "TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries) failed"); 1910 return caretPointOrError.propagateErr(); 1911 } 1912 caretPointOrError.unwrap().IgnoreCaretPointSuggestion(); 1913 // If the leading white-spaces were split into multiple text node, we need 1914 // only the last `Text` node. 1915 if (!leadingWhiteSpaceRange.InSameContainer() && 1916 leadingWhiteSpaceRange.StartRef().IsInTextNode() && 1917 leadingWhiteSpaceRange.StartRef() 1918 .ContainerAs<Text>() 1919 ->IsInComposedDoc() && 1920 leadingWhiteSpaceRange.EndRef().IsInTextNode() && 1921 leadingWhiteSpaceRange.EndRef() 1922 .ContainerAs<Text>() 1923 ->IsInComposedDoc() && 1924 !leadingWhiteSpaceRange.StartRef() 1925 .ContainerAs<Text>() 1926 ->TextDataLength()) { 1927 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive( 1928 *leadingWhiteSpaceRange.StartRef().ContainerAs<Text>())); 1929 if (NS_FAILED(rv)) { 1930 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed"); 1931 return Err(rv); 1932 } 1933 } 1934 trackEndOfLeadingWhiteSpaces.FlushAndStopTracking(); 1935 if (NS_WARN_IF(!endOfLeadingWhiteSpaces.IsSetAndValidInComposedDoc())) { 1936 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1937 } 1938 return endOfLeadingWhiteSpaces; 1939 } 1940 } 1941 1942 const TextFragmentData textFragmentData = 1943 textFragmentDataForLeadingWhiteSpaces.ScanStartRef() == aPoint 1944 ? textFragmentDataForLeadingWhiteSpaces 1945 : TextFragmentData({WSRunScanner::Option::OnlyEditableNodes}, aPoint, 1946 maybeNonEditableClosestBlockElement); 1947 const EditorDOMRange& trailingWhiteSpaceRange = 1948 textFragmentData.InvisibleTrailingWhiteSpaceRangeRef(); 1949 if (trailingWhiteSpaceRange.IsPositioned() && 1950 !trailingWhiteSpaceRange.Collapsed()) { 1951 EditorDOMPoint startOfTrailingWhiteSpaces( 1952 trailingWhiteSpaceRange.StartRef()); 1953 AutoTrackDOMPoint trackStartOfTrailingWhiteSpaces( 1954 aHTMLEditor.RangeUpdaterRef(), &startOfTrailingWhiteSpaces); 1955 Result<CaretPoint, nsresult> caretPointOrError = 1956 aHTMLEditor.DeleteTextAndTextNodesWithTransaction( 1957 trailingWhiteSpaceRange.StartRef(), 1958 trailingWhiteSpaceRange.EndRef(), 1959 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries); 1960 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 1961 NS_WARNING( 1962 "HTMLEditor::DeleteTextAndTextNodesWithTransaction(" 1963 "TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries) failed"); 1964 return caretPointOrError.propagateErr(); 1965 } 1966 caretPointOrError.unwrap().IgnoreCaretPointSuggestion(); 1967 // If the leading white-spaces were split into multiple text node, we need 1968 // only the last `Text` node. 1969 if (!trailingWhiteSpaceRange.InSameContainer() && 1970 trailingWhiteSpaceRange.StartRef().IsInTextNode() && 1971 trailingWhiteSpaceRange.StartRef() 1972 .ContainerAs<Text>() 1973 ->IsInComposedDoc() && 1974 trailingWhiteSpaceRange.EndRef().IsInTextNode() && 1975 trailingWhiteSpaceRange.EndRef() 1976 .ContainerAs<Text>() 1977 ->IsInComposedDoc() && 1978 !trailingWhiteSpaceRange.EndRef() 1979 .ContainerAs<Text>() 1980 ->TextDataLength()) { 1981 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction( 1982 MOZ_KnownLive(*trailingWhiteSpaceRange.EndRef().ContainerAs<Text>())); 1983 if (NS_FAILED(rv)) { 1984 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed"); 1985 return Err(rv); 1986 } 1987 } 1988 trackStartOfTrailingWhiteSpaces.FlushAndStopTracking(); 1989 if (NS_WARN_IF(!startOfTrailingWhiteSpaces.IsSetAndValidInComposedDoc())) { 1990 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1991 } 1992 return startOfTrailingWhiteSpaces; 1993 } 1994 1995 const auto atCollapsibleASCIISpace = 1996 [&]() MOZ_NEVER_INLINE_DEBUG -> EditorDOMPointInText { 1997 const auto point = 1998 textFragmentData.GetInclusiveNextCharPoint<EditorDOMPointInText>( 1999 textFragmentData.ScanStartRef(), IgnoreNonEditableNodes::Yes); 2000 if (point.IsSet() && 2001 // XXX Perhaps, we should ignore empty `Text` nodes and keep scanning. 2002 !point.IsEndOfContainer() && point.IsCharCollapsibleASCIISpace()) { 2003 return point; 2004 } 2005 const auto prevPoint = 2006 textFragmentData.GetPreviousCharPoint<EditorDOMPointInText>( 2007 textFragmentData.ScanStartRef(), IgnoreNonEditableNodes::Yes); 2008 return prevPoint.IsSet() && 2009 // XXX Perhaps, we should ignore empty `Text` nodes and keep 2010 // scanning. 2011 !prevPoint.IsEndOfContainer() && 2012 prevPoint.IsCharCollapsibleASCIISpace() 2013 ? prevPoint 2014 : EditorDOMPointInText(); 2015 }(); 2016 if (!atCollapsibleASCIISpace.IsSet()) { 2017 return EditorDOMPoint(); 2018 } 2019 const auto firstCollapsibleASCIISpacePoint = 2020 textFragmentData 2021 .GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointInText>( 2022 atCollapsibleASCIISpace, nsIEditor::eNone, 2023 IgnoreNonEditableNodes::No); 2024 const auto endOfCollapsibleASCIISpacePoint = 2025 textFragmentData 2026 .GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>( 2027 atCollapsibleASCIISpace, nsIEditor::eNone, 2028 IgnoreNonEditableNodes::No); 2029 if (firstCollapsibleASCIISpacePoint.NextPoint() == 2030 endOfCollapsibleASCIISpacePoint) { 2031 // Only one white-space, so that nothing to do. 2032 return EditorDOMPoint(); 2033 } 2034 // Okay, there are some collapsed white-spaces. We should delete them with 2035 // keeping first one. 2036 Result<CaretPoint, nsresult> deleteTextResultOrError = 2037 aHTMLEditor.DeleteTextAndTextNodesWithTransaction( 2038 firstCollapsibleASCIISpacePoint.NextPoint(), 2039 endOfCollapsibleASCIISpacePoint, 2040 HTMLEditor::TreatEmptyTextNodes::Remove); 2041 if (MOZ_UNLIKELY(deleteTextResultOrError.isErr())) { 2042 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed"); 2043 return deleteTextResultOrError.propagateErr(); 2044 } 2045 return deleteTextResultOrError.unwrap().UnwrapCaretPoint(); 2046 } 2047 2048 // static 2049 Result<InsertTextResult, nsresult> 2050 WhiteSpaceVisibilityKeeper::InsertTextOrInsertOrUpdateCompositionString( 2051 HTMLEditor& aHTMLEditor, const nsAString& aStringToInsert, 2052 const EditorDOMRange& aRangeToBeReplaced, InsertTextTo aInsertTextTo, 2053 InsertTextFor aPurpose) { 2054 MOZ_ASSERT(aRangeToBeReplaced.StartRef().IsInContentNode()); 2055 MOZ_ASSERT_IF(!EditorBase::InsertingTextForExtantComposition(aPurpose), 2056 aRangeToBeReplaced.Collapsed()); 2057 if (aStringToInsert.IsEmpty()) { 2058 MOZ_ASSERT(aRangeToBeReplaced.Collapsed()); 2059 return InsertTextResult(); 2060 } 2061 2062 if (NS_WARN_IF(!aRangeToBeReplaced.StartRef().IsInContentNode())) { 2063 return Err(NS_ERROR_FAILURE); // Cannot insert text 2064 } 2065 2066 EditorDOMPoint pointToInsert = aHTMLEditor.ComputePointToInsertText( 2067 aRangeToBeReplaced.StartRef(), aInsertTextTo); 2068 MOZ_ASSERT(pointToInsert.IsInContentNode()); 2069 const bool isWhiteSpaceCollapsible = !EditorUtils::IsWhiteSpacePreformatted( 2070 *aRangeToBeReplaced.StartRef().ContainerAs<nsIContent>()); 2071 2072 // First, delete invisible leading white-spaces and trailing white-spaces if 2073 // they are there around the replacing range boundaries. However, don't do 2074 // that if we're updating existing composition string to avoid the composition 2075 // transaction is broken by the text change around composition string. 2076 if (!EditorBase::InsertingTextForExtantComposition(aPurpose) && 2077 isWhiteSpaceCollapsible && pointToInsert.IsInContentNode()) { 2078 AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(), 2079 &pointToInsert); 2080 Result<EditorDOMPoint, nsresult> 2081 deletePointOfInvisibleWhiteSpacesAtStartOrError = 2082 WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces( 2083 aHTMLEditor, pointToInsert); 2084 if (MOZ_UNLIKELY(deletePointOfInvisibleWhiteSpacesAtStartOrError.isErr())) { 2085 NS_WARNING( 2086 "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces() failed"); 2087 return deletePointOfInvisibleWhiteSpacesAtStartOrError.propagateErr(); 2088 } 2089 trackPointToInsert.FlushAndStopTracking(); 2090 const EditorDOMPoint deletePointOfInvisibleWhiteSpacesAtStart = 2091 deletePointOfInvisibleWhiteSpacesAtStartOrError.unwrap(); 2092 if (NS_WARN_IF(deletePointOfInvisibleWhiteSpacesAtStart.IsSet() && 2093 !pointToInsert.IsSetAndValidInComposedDoc())) { 2094 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2095 } 2096 // If we're starting composition, we won't normalizing surrounding 2097 // white-spaces until end of the composition. Additionally, at that time, 2098 // we need to assume all white-spaces of surrounding white-spaces are 2099 // visible because canceling composition may cause previous white-space 2100 // invisible temporarily. Therefore, we should normalize surrounding 2101 // white-spaces to delete invisible white-spaces contained in the sequence. 2102 // E.g., `NBSP SP SP NBSP`, in this case, one of the SP is invisible. 2103 if (EditorBase::InsertingTextForStartingComposition(aPurpose) && 2104 pointToInsert.IsInTextNode()) { 2105 const auto whiteSpaceOffset = [&]() -> Maybe<uint32_t> { 2106 if (!pointToInsert.IsEndOfContainer() && 2107 pointToInsert.IsCharCollapsibleASCIISpaceOrNBSP()) { 2108 return Some(pointToInsert.Offset()); 2109 } 2110 if (!pointToInsert.IsStartOfContainer() && 2111 pointToInsert.IsPreviousCharCollapsibleASCIISpaceOrNBSP()) { 2112 return Some(pointToInsert.Offset() - 1u); 2113 } 2114 return Nothing(); 2115 }(); 2116 if (whiteSpaceOffset.isSome()) { 2117 Maybe<AutoTrackDOMPoint> trackPointToInsert; 2118 if (pointToInsert.Offset() != *whiteSpaceOffset) { 2119 trackPointToInsert.emplace(aHTMLEditor.RangeUpdaterRef(), 2120 &pointToInsert); 2121 } 2122 Result<EditorDOMPoint, nsresult> pointToInsertOrError = 2123 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt( 2124 aHTMLEditor, 2125 EditorDOMPointInText(pointToInsert.ContainerAs<Text>(), 2126 *whiteSpaceOffset)); 2127 if (MOZ_UNLIKELY(pointToInsertOrError.isErr())) { 2128 NS_WARNING( 2129 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAt() failed"); 2130 return pointToInsertOrError.propagateErr(); 2131 } 2132 if (trackPointToInsert.isSome()) { 2133 trackPointToInsert.reset(); 2134 } else { 2135 pointToInsert = pointToInsertOrError.unwrap(); 2136 } 2137 if (NS_WARN_IF(!pointToInsert.IsInContentNodeAndValidInComposedDoc())) { 2138 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2139 } 2140 } 2141 } 2142 } 2143 2144 if (NS_WARN_IF(!pointToInsert.IsInContentNode())) { 2145 return Err(NS_ERROR_FAILURE); 2146 } 2147 2148 const HTMLEditor::NormalizedStringToInsertText insertTextData = 2149 [&]() MOZ_NEVER_INLINE_DEBUG { 2150 if (!isWhiteSpaceCollapsible) { 2151 return HTMLEditor::NormalizedStringToInsertText(aStringToInsert, 2152 pointToInsert); 2153 } 2154 if (pointToInsert.IsInTextNode() && 2155 !EditorBase::InsertingTextForComposition(aPurpose)) { 2156 // If normalizing the surrounding white-spaces in the `Text`, we 2157 // should minimize the replacing range to avoid to unnecessary 2158 // replacement. 2159 return aHTMLEditor 2160 .NormalizeWhiteSpacesToInsertText( 2161 pointToInsert, aStringToInsert, 2162 HTMLEditor::NormalizeSurroundingWhiteSpaces::Yes) 2163 .GetMinimizedData(*pointToInsert.ContainerAs<Text>()); 2164 } 2165 return aHTMLEditor.NormalizeWhiteSpacesToInsertText( 2166 pointToInsert, aStringToInsert, 2167 // If we're handling composition string, we should not replace 2168 // surrounding white-spaces to avoid to make 2169 // CompositionTransaction confused. 2170 EditorBase::InsertingTextForComposition(aPurpose) 2171 ? HTMLEditor::NormalizeSurroundingWhiteSpaces::No 2172 : HTMLEditor::NormalizeSurroundingWhiteSpaces::Yes); 2173 }(); 2174 2175 MOZ_ASSERT_IF(insertTextData.ReplaceLength(), pointToInsert.IsInTextNode()); 2176 Result<InsertTextResult, nsresult> insertOrReplaceTextResultOrError = 2177 aHTMLEditor.InsertOrReplaceTextWithTransaction(pointToInsert, 2178 insertTextData); 2179 if (MOZ_UNLIKELY(insertOrReplaceTextResultOrError.isErr())) { 2180 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed"); 2181 return insertOrReplaceTextResultOrError; 2182 } 2183 // If the composition is committed, we should normalize surrounding 2184 // white-spaces of the commit string. 2185 if (!EditorBase::InsertingTextForCommittingComposition(aPurpose)) { 2186 return insertOrReplaceTextResultOrError; 2187 } 2188 InsertTextResult insertOrReplaceTextResult = 2189 insertOrReplaceTextResultOrError.unwrap(); 2190 const EditorDOMPointInText endOfCommitString = 2191 insertOrReplaceTextResult.EndOfInsertedTextRef().GetAsInText(); 2192 if (!endOfCommitString.IsSet() || endOfCommitString.IsContainerEmpty()) { 2193 return std::move(insertOrReplaceTextResult); 2194 } 2195 if (NS_WARN_IF(endOfCommitString.Offset() < 2196 insertTextData.mNormalizedString.Length())) { 2197 insertOrReplaceTextResult.IgnoreCaretPointSuggestion(); 2198 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2199 } 2200 const EditorDOMPointInText startOfCommitString( 2201 endOfCommitString.ContainerAs<Text>(), 2202 endOfCommitString.Offset() - insertTextData.mNormalizedString.Length()); 2203 MOZ_ASSERT(insertOrReplaceTextResult.EndOfInsertedTextRef() == 2204 insertOrReplaceTextResult.CaretPointRef()); 2205 EditorDOMPoint pointToPutCaret = insertOrReplaceTextResult.UnwrapCaretPoint(); 2206 // First, normalize the trailing white-spaces if there is. Note that its 2207 // sequence may start from before the commit string. In such case, the 2208 // another call of NormalizeWhiteSpacesAt() won't update the DOM. 2209 if (endOfCommitString.IsMiddleOfContainer()) { 2210 nsresult rv = WhiteSpaceVisibilityKeeper:: 2211 NormalizeVisibleWhiteSpacesWithoutDeletingInvisibleWhiteSpaces( 2212 aHTMLEditor, endOfCommitString.PreviousPoint()); 2213 if (NS_FAILED(rv)) { 2214 NS_WARNING( 2215 "WhiteSpaceVisibilityKeeper::" 2216 "NormalizeVisibleWhiteSpacesWithoutDeletingInvisibleWhiteSpaces() " 2217 "failed"); 2218 return Err(rv); 2219 } 2220 if (NS_WARN_IF(!pointToPutCaret.IsSetAndValidInComposedDoc())) { 2221 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2222 } 2223 } 2224 // Finally, normalize the leading white-spaces if there is and not a part of 2225 // the trailing white-spaces. 2226 if (!startOfCommitString.IsStartOfContainer()) { 2227 nsresult rv = WhiteSpaceVisibilityKeeper:: 2228 NormalizeVisibleWhiteSpacesWithoutDeletingInvisibleWhiteSpaces( 2229 aHTMLEditor, startOfCommitString.PreviousPoint()); 2230 if (NS_FAILED(rv)) { 2231 NS_WARNING( 2232 "WhiteSpaceVisibilityKeeper::" 2233 "NormalizeVisibleWhiteSpacesWithoutDeletingInvisibleWhiteSpaces() " 2234 "failed"); 2235 return Err(rv); 2236 } 2237 if (NS_WARN_IF(!pointToPutCaret.IsSetAndValidInComposedDoc())) { 2238 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2239 } 2240 } 2241 EditorDOMPoint endOfCommitStringAfterNormalized = pointToPutCaret; 2242 return InsertTextResult(std::move(endOfCommitStringAfterNormalized), 2243 CaretPoint(std::move(pointToPutCaret))); 2244 } 2245 2246 // static 2247 nsresult WhiteSpaceVisibilityKeeper:: 2248 NormalizeVisibleWhiteSpacesWithoutDeletingInvisibleWhiteSpaces( 2249 HTMLEditor& aHTMLEditor, const EditorDOMPointInText& aPoint) { 2250 MOZ_ASSERT(aPoint.IsSet()); 2251 MOZ_ASSERT(!aPoint.IsEndOfContainer()); 2252 2253 if (EditorUtils::IsWhiteSpacePreformatted(*aPoint.ContainerAs<Text>())) { 2254 return NS_OK; 2255 } 2256 Text& textNode = *aPoint.ContainerAs<Text>(); 2257 const bool isNewLinePreformatted = 2258 EditorUtils::IsNewLinePreformatted(textNode); 2259 const auto IsCollapsibleChar = [&](char16_t aChar) { 2260 return aChar == HTMLEditUtils::kNewLine ? !isNewLinePreformatted 2261 : nsCRT::IsAsciiSpace(aChar); 2262 }; 2263 const auto IsCollapsibleCharOrNBSP = [&](char16_t aChar) { 2264 return aChar == HTMLEditUtils::kNBSP || IsCollapsibleChar(aChar); 2265 }; 2266 const auto whiteSpaceOffset = [&]() -> Maybe<uint32_t> { 2267 if (IsCollapsibleCharOrNBSP(aPoint.Char())) { 2268 return Some(aPoint.Offset()); 2269 } 2270 if (!aPoint.IsAtLastContent() && 2271 IsCollapsibleCharOrNBSP(aPoint.NextChar())) { 2272 return Some(aPoint.Offset() + 1u); 2273 } 2274 return Nothing(); 2275 }(); 2276 if (whiteSpaceOffset.isNothing()) { 2277 return NS_OK; 2278 } 2279 CharacterDataBuffer::WhitespaceOptions whitespaceOptions{ 2280 CharacterDataBuffer::WhitespaceOption::FormFeedIsSignificant, 2281 CharacterDataBuffer::WhitespaceOption::TreatNBSPAsCollapsible}; 2282 if (isNewLinePreformatted) { 2283 whitespaceOptions += 2284 CharacterDataBuffer::WhitespaceOption::NewLineIsSignificant; 2285 } 2286 const uint32_t firstOffset = [&]() { 2287 if (!*whiteSpaceOffset) { 2288 return 0u; 2289 } 2290 const uint32_t offset = textNode.DataBuffer().RFindNonWhitespaceChar( 2291 whitespaceOptions, *whiteSpaceOffset - 1); 2292 return offset == CharacterDataBuffer::kNotFound ? 0u : offset + 1u; 2293 }(); 2294 const uint32_t endOffset = [&]() { 2295 const uint32_t offset = textNode.DataBuffer().FindNonWhitespaceChar( 2296 whitespaceOptions, *whiteSpaceOffset + 1); 2297 return offset == CharacterDataBuffer::kNotFound ? textNode.TextDataLength() 2298 : offset; 2299 }(); 2300 MOZ_DIAGNOSTIC_ASSERT(firstOffset <= endOffset); 2301 nsAutoString normalizedString; 2302 const char16_t precedingChar = 2303 !firstOffset ? static_cast<char16_t>(0) 2304 : textNode.DataBuffer().CharAt(firstOffset - 1u); 2305 const char16_t followingChar = endOffset == textNode.TextDataLength() 2306 ? static_cast<char16_t>(0) 2307 : textNode.DataBuffer().CharAt(endOffset); 2308 HTMLEditor::GenerateWhiteSpaceSequence( 2309 normalizedString, endOffset - firstOffset, 2310 !firstOffset ? HTMLEditor::CharPointData::InSameTextNode( 2311 HTMLEditor::CharPointType::TextEnd) 2312 : HTMLEditor::CharPointData::InSameTextNode( 2313 precedingChar == HTMLEditUtils::kNewLine 2314 ? HTMLEditor::CharPointType::PreformattedLineBreak 2315 : HTMLEditor::CharPointType::VisibleChar), 2316 endOffset == textNode.TextDataLength() 2317 ? HTMLEditor::CharPointData::InSameTextNode( 2318 HTMLEditor::CharPointType::TextEnd) 2319 : HTMLEditor::CharPointData::InSameTextNode( 2320 followingChar == HTMLEditUtils::kNewLine 2321 ? HTMLEditor::CharPointType::PreformattedLineBreak 2322 : HTMLEditor::CharPointType::VisibleChar)); 2323 MOZ_ASSERT(normalizedString.Length() == endOffset - firstOffset); 2324 const OwningNonNull<Text> text(textNode); 2325 Result<InsertTextResult, nsresult> normalizeWhiteSpaceSequenceResultOrError = 2326 aHTMLEditor.ReplaceTextWithTransaction( 2327 text, firstOffset, endOffset - firstOffset, normalizedString); 2328 if (MOZ_UNLIKELY(normalizeWhiteSpaceSequenceResultOrError.isErr())) { 2329 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed"); 2330 return normalizeWhiteSpaceSequenceResultOrError.unwrapErr(); 2331 } 2332 normalizeWhiteSpaceSequenceResultOrError.unwrap() 2333 .IgnoreCaretPointSuggestion(); 2334 return NS_OK; 2335 } 2336 2337 // static 2338 Result<CaretPoint, nsresult> 2339 WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt( 2340 HTMLEditor& aHTMLEditor, nsIContent& aContentToDelete, 2341 const EditorDOMPoint& aCaretPoint, const Element& aEditingHost) { 2342 EditorDOMPoint atContent(&aContentToDelete); 2343 if (!atContent.IsSet()) { 2344 NS_WARNING("Deleting content node was an orphan node"); 2345 return Err(NS_ERROR_FAILURE); 2346 } 2347 if (!HTMLEditUtils::IsRemovableNode(aContentToDelete)) { 2348 NS_WARNING("Deleting content node wasn't removable"); 2349 return Err(NS_ERROR_FAILURE); 2350 } 2351 EditorDOMPoint pointToPutCaret(aCaretPoint); 2352 Maybe<AutoTrackDOMPoint> trackPointToPutCaret; 2353 if (aCaretPoint.IsSet()) { 2354 trackPointToPutCaret.emplace(aHTMLEditor.RangeUpdaterRef(), 2355 &pointToPutCaret); 2356 } 2357 // If we're removing a block, it may be surrounded by invisible 2358 // white-spaces. We should remove them to avoid to make them accidentally 2359 // visible. 2360 if (HTMLEditUtils::IsBlockElement( 2361 aContentToDelete, BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 2362 AutoTrackDOMPoint trackAtContent(aHTMLEditor.RangeUpdaterRef(), &atContent); 2363 { 2364 nsresult rv = 2365 WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore( 2366 aHTMLEditor, EditorDOMPoint(aContentToDelete.AsElement())); 2367 if (NS_FAILED(rv)) { 2368 NS_WARNING( 2369 "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesBefore()" 2370 " failed"); 2371 return Err(rv); 2372 } 2373 if (NS_WARN_IF(!aContentToDelete.IsInComposedDoc())) { 2374 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2375 } 2376 rv = WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter( 2377 aHTMLEditor, EditorDOMPoint::After(*aContentToDelete.AsElement())); 2378 if (NS_FAILED(rv)) { 2379 NS_WARNING( 2380 "WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpacesAfter() " 2381 "failed"); 2382 return Err(rv); 2383 } 2384 if (NS_WARN_IF(!aContentToDelete.IsInComposedDoc())) { 2385 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2386 } 2387 if (trackPointToPutCaret.isSome()) { 2388 trackPointToPutCaret->Flush(StopTracking::No); 2389 } 2390 } 2391 if (pointToPutCaret.IsInContentNode()) { 2392 // Additionally, we may put caret into the preceding block (this is the 2393 // case when caret was in an empty block and type `Backspace`, or when 2394 // caret is at end of the preceding block and type `Delete`). In such 2395 // case, we need to normalize the white-space of the preceding `Text` of 2396 // the deleting empty block for the compatibility with the other 2397 // browsers. 2398 if (pointToPutCaret.IsBefore(EditorRawDOMPoint(&aContentToDelete))) { 2399 WSScanResult nextThingOfCaretPoint = 2400 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 2401 {}, pointToPutCaret); 2402 Maybe<EditorLineBreak> lineBreak; 2403 if (nextThingOfCaretPoint.ReachedLineBreak()) { 2404 lineBreak.emplace( 2405 nextThingOfCaretPoint.CreateEditorLineBreak<EditorLineBreak>()); 2406 nextThingOfCaretPoint = 2407 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 2408 {}, lineBreak->After<EditorRawDOMPoint>()); 2409 } 2410 if (nextThingOfCaretPoint.ReachedBlockBoundary()) { 2411 const EditorDOMPoint atBlockBoundary = 2412 nextThingOfCaretPoint.ReachedCurrentBlockBoundary() 2413 ? EditorDOMPoint::AtEndOf(*nextThingOfCaretPoint.ElementPtr()) 2414 : EditorDOMPoint(nextThingOfCaretPoint.ElementPtr()); 2415 Result<EditorDOMPoint, nsresult> afterLastVisibleThingOrError = 2416 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore( 2417 aHTMLEditor, atBlockBoundary, {}); 2418 if (MOZ_UNLIKELY(afterLastVisibleThingOrError.isErr())) { 2419 NS_WARNING( 2420 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore() " 2421 "failed"); 2422 return afterLastVisibleThingOrError.propagateErr(); 2423 } 2424 if (NS_WARN_IF(!aContentToDelete.IsInComposedDoc())) { 2425 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2426 } 2427 // If the previous content ends with an invisible line break, let's 2428 // delete it. 2429 if (lineBreak.isSome() && lineBreak->IsInComposedDoc()) { 2430 const WSScanResult prevThing = 2431 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 2432 {}, lineBreak->To<EditorRawDOMPoint>(), &aEditingHost); 2433 if (!prevThing.ReachedLineBoundary()) { 2434 Result<EditorDOMPoint, nsresult> pointOrError = 2435 aHTMLEditor.DeleteLineBreakWithTransaction( 2436 lineBreak.ref(), nsIEditor::eStrip, aEditingHost); 2437 if (MOZ_UNLIKELY(pointOrError.isErr())) { 2438 NS_WARNING( 2439 "HTMLEditor::DeleteLineBreakWithTransaction() failed"); 2440 return pointOrError.propagateErr(); 2441 } 2442 trackPointToPutCaret->Flush(StopTracking::No); 2443 } 2444 } 2445 } 2446 } 2447 // Similarly, we may put caret into the following block (this is the 2448 // case when caret was in an empty block and type `Delete`, or when 2449 // caret is at start of the following block and type `Backspace`). In 2450 // such case, we need to normalize the white-space of the following 2451 // `Text` of the deleting empty block for the compatibility with the 2452 // other browsers. 2453 else if (EditorRawDOMPoint::After(aContentToDelete) 2454 .EqualsOrIsBefore(pointToPutCaret)) { 2455 const WSScanResult previousThingOfCaretPoint = 2456 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 2457 {}, pointToPutCaret); 2458 if (previousThingOfCaretPoint.ReachedBlockBoundary()) { 2459 const EditorDOMPoint atBlockBoundary = 2460 previousThingOfCaretPoint.ReachedCurrentBlockBoundary() 2461 ? EditorDOMPoint(previousThingOfCaretPoint.ElementPtr(), 0u) 2462 : EditorDOMPoint(previousThingOfCaretPoint.ElementPtr()); 2463 Result<EditorDOMPoint, nsresult> atFirstVisibleThingOrError = 2464 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter( 2465 aHTMLEditor, atBlockBoundary, {}); 2466 if (MOZ_UNLIKELY(atFirstVisibleThingOrError.isErr())) { 2467 NS_WARNING( 2468 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() " 2469 "failed"); 2470 return atFirstVisibleThingOrError.propagateErr(); 2471 } 2472 if (NS_WARN_IF(!aContentToDelete.IsInComposedDoc())) { 2473 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2474 } 2475 } 2476 } 2477 } 2478 trackAtContent.Flush(StopTracking::Yes); 2479 if (NS_WARN_IF(!atContent.IsInContentNodeAndValidInComposedDoc())) { 2480 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2481 } 2482 } 2483 // If we're deleting inline content which is not followed by visible 2484 // content, i.e., the preceding text will become the last Text node, we 2485 // should normalize the preceding white-spaces for compatibility with the 2486 // other browsers. 2487 else { 2488 const WSScanResult nextThing = 2489 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 2490 {}, EditorRawDOMPoint::After(aContentToDelete)); 2491 if (nextThing.ReachedLineBoundary()) { 2492 AutoTrackDOMPoint trackAtContent(aHTMLEditor.RangeUpdaterRef(), 2493 &atContent); 2494 Result<EditorDOMPoint, nsresult> afterLastVisibleThingOrError = 2495 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore(aHTMLEditor, 2496 atContent, {}); 2497 if (MOZ_UNLIKELY(afterLastVisibleThingOrError.isErr())) { 2498 NS_WARNING( 2499 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore() " 2500 "failed"); 2501 return afterLastVisibleThingOrError.propagateErr(); 2502 } 2503 trackAtContent.Flush(StopTracking::Yes); 2504 if (NS_WARN_IF(!atContent.IsInContentNodeAndValidInComposedDoc())) { 2505 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2506 } 2507 } 2508 } 2509 2510 // Finally, we should normalize the following white-spaces for compatibility 2511 // with the other browsers. 2512 { 2513 AutoTrackDOMPoint trackAtContent(aHTMLEditor.RangeUpdaterRef(), &atContent); 2514 Result<EditorDOMPoint, nsresult> atFirstVisibleThingOrError = 2515 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter( 2516 aHTMLEditor, atContent.NextPoint(), {}); 2517 if (MOZ_UNLIKELY(atFirstVisibleThingOrError.isErr())) { 2518 NS_WARNING( 2519 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore() failed"); 2520 return atFirstVisibleThingOrError.propagateErr(); 2521 } 2522 trackAtContent.Flush(StopTracking::Yes); 2523 if (NS_WARN_IF(!atContent.IsInContentNodeAndValidInComposedDoc())) { 2524 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2525 } 2526 } 2527 2528 nsCOMPtr<nsIContent> previousEditableSibling = 2529 HTMLEditUtils::GetPreviousSibling( 2530 aContentToDelete, {WalkTreeOption::IgnoreNonEditableNode}); 2531 // Delete the node, and join like nodes if appropriate 2532 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aContentToDelete); 2533 if (NS_FAILED(rv)) { 2534 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 2535 return Err(rv); 2536 } 2537 2538 if (trackPointToPutCaret.isSome()) { 2539 trackPointToPutCaret->Flush(StopTracking::Yes); 2540 if (NS_WARN_IF(!pointToPutCaret.IsInContentNode())) { 2541 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2542 } 2543 } 2544 2545 // Are they both text nodes? If so, join them! 2546 // XXX This may cause odd behavior if there is non-editable nodes 2547 // around the atomic content. 2548 if (!aCaretPoint.IsInTextNode() || !previousEditableSibling || 2549 !previousEditableSibling->IsText()) { 2550 return CaretPoint(std::move(pointToPutCaret)); 2551 } 2552 2553 nsIContent* nextEditableSibling = HTMLEditUtils::GetNextSibling( 2554 *previousEditableSibling, {WalkTreeOption::IgnoreNonEditableNode}); 2555 if (aCaretPoint.GetContainer() != nextEditableSibling) { 2556 return CaretPoint(std::move(pointToPutCaret)); 2557 } 2558 2559 Result<JoinNodesResult, nsresult> joinTextNodesResultOrError = 2560 aHTMLEditor.JoinTextNodesWithNormalizeWhiteSpaces( 2561 MOZ_KnownLive(*previousEditableSibling->AsText()), 2562 MOZ_KnownLive(*aCaretPoint.ContainerAs<Text>())); 2563 if (MOZ_UNLIKELY(joinTextNodesResultOrError.isErr())) { 2564 NS_WARNING("HTMLEditor::JoinTextNodesWithNormalizeWhiteSpaces() failed"); 2565 return joinTextNodesResultOrError.propagateErr(); 2566 } 2567 return CaretPoint( 2568 joinTextNodesResultOrError.unwrap().AtJoinedPoint<EditorDOMPoint>()); 2569 } 2570 2571 // static 2572 nsresult WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes( 2573 HTMLEditor& aHTMLEditor, const EditorDOMRangeInTexts& aRangeToReplace, 2574 const nsAString& aReplaceString) { 2575 MOZ_ASSERT(aRangeToReplace.IsPositioned()); 2576 MOZ_ASSERT(aRangeToReplace.StartRef().IsSetAndValid()); 2577 MOZ_ASSERT(aRangeToReplace.EndRef().IsSetAndValid()); 2578 MOZ_ASSERT(aRangeToReplace.StartRef().IsBefore(aRangeToReplace.EndRef())); 2579 2580 { 2581 Result<InsertTextResult, nsresult> caretPointOrError = 2582 aHTMLEditor.ReplaceTextWithTransaction( 2583 MOZ_KnownLive(*aRangeToReplace.StartRef().ContainerAs<Text>()), 2584 aRangeToReplace.StartRef().Offset(), 2585 aRangeToReplace.InSameContainer() 2586 ? aRangeToReplace.EndRef().Offset() - 2587 aRangeToReplace.StartRef().Offset() 2588 : aRangeToReplace.StartRef().ContainerAs<Text>()->TextLength() - 2589 aRangeToReplace.StartRef().Offset(), 2590 aReplaceString); 2591 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 2592 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed"); 2593 return caretPointOrError.unwrapErr(); 2594 } 2595 // Ignore caret suggestion because there was 2596 // AutoTransactionsConserveSelection. 2597 caretPointOrError.unwrap().IgnoreCaretPointSuggestion(); 2598 } 2599 2600 if (aRangeToReplace.InSameContainer()) { 2601 return NS_OK; 2602 } 2603 2604 Result<CaretPoint, nsresult> caretPointOrError = 2605 aHTMLEditor.DeleteTextAndTextNodesWithTransaction( 2606 EditorDOMPointInText::AtEndOf( 2607 *aRangeToReplace.StartRef().ContainerAs<Text>()), 2608 aRangeToReplace.EndRef(), 2609 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries); 2610 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 2611 NS_WARNING("HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed"); 2612 return caretPointOrError.unwrapErr(); 2613 } 2614 // Ignore caret suggestion because there was 2615 // AutoTransactionsConserveSelection. 2616 caretPointOrError.unwrap().IgnoreCaretPointSuggestion(); 2617 return NS_OK; 2618 } 2619 2620 // static 2621 Result<CaretPoint, nsresult> 2622 WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces( 2623 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint) { 2624 MOZ_ASSERT(aPoint.IsSet()); 2625 const TextFragmentData textFragmentData( 2626 {WSRunScanner::Option::OnlyEditableNodes}, aPoint); 2627 if (NS_WARN_IF(!textFragmentData.IsInitialized())) { 2628 return Err(NS_ERROR_FAILURE); 2629 } 2630 const EditorDOMRange& leadingWhiteSpaceRange = 2631 textFragmentData.InvisibleLeadingWhiteSpaceRangeRef(); 2632 // XXX Getting trailing white-space range now must be wrong because 2633 // mutation event listener may invalidate it. 2634 const EditorDOMRange& trailingWhiteSpaceRange = 2635 textFragmentData.InvisibleTrailingWhiteSpaceRangeRef(); 2636 EditorDOMPoint pointToPutCaret; 2637 DebugOnly<bool> leadingWhiteSpacesDeleted = false; 2638 if (leadingWhiteSpaceRange.IsPositioned() && 2639 !leadingWhiteSpaceRange.Collapsed()) { 2640 Result<CaretPoint, nsresult> caretPointOrError = 2641 aHTMLEditor.DeleteTextAndTextNodesWithTransaction( 2642 leadingWhiteSpaceRange.StartRef(), leadingWhiteSpaceRange.EndRef(), 2643 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries); 2644 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 2645 NS_WARNING("HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed"); 2646 return caretPointOrError; 2647 } 2648 caretPointOrError.unwrap().MoveCaretPointTo( 2649 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 2650 leadingWhiteSpacesDeleted = true; 2651 } 2652 if (trailingWhiteSpaceRange.IsPositioned() && 2653 !trailingWhiteSpaceRange.Collapsed() && 2654 leadingWhiteSpaceRange != trailingWhiteSpaceRange) { 2655 NS_ASSERTION(!leadingWhiteSpacesDeleted, 2656 "We're trying to remove trailing white-spaces with maybe " 2657 "outdated range"); 2658 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(), 2659 &pointToPutCaret); 2660 Result<CaretPoint, nsresult> caretPointOrError = 2661 aHTMLEditor.DeleteTextAndTextNodesWithTransaction( 2662 trailingWhiteSpaceRange.StartRef(), 2663 trailingWhiteSpaceRange.EndRef(), 2664 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries); 2665 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 2666 NS_WARNING("HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed"); 2667 return caretPointOrError.propagateErr(); 2668 } 2669 trackPointToPutCaret.FlushAndStopTracking(); 2670 caretPointOrError.unwrap().MoveCaretPointTo( 2671 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 2672 } 2673 return CaretPoint(std::move(pointToPutCaret)); 2674 } 2675 2676 } // namespace mozilla