HTMLEditorDeleteHandler.cpp (347349B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=2 sw=2 et tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "HTMLEditor.h" 8 #include "HTMLEditorNestedClasses.h" 9 10 #include <utility> 11 12 #include "AutoClonedRangeArray.h" 13 #include "CSSEditUtils.h" 14 #include "EditAction.h" 15 #include "EditorDOMAPIWrapper.h" 16 #include "EditorDOMPoint.h" 17 #include "EditorLineBreak.h" 18 #include "EditorUtils.h" 19 #include "HTMLEditHelpers.h" 20 #include "HTMLEditorInlines.h" 21 #include "HTMLEditUtils.h" 22 #include "WhiteSpaceVisibilityKeeper.h" 23 #include "WSRunScanner.h" 24 25 #include "ErrorList.h" 26 #include "mozilla/Assertions.h" 27 #include "mozilla/ContentIterator.h" 28 #include "mozilla/Logging.h" 29 #include "mozilla/Maybe.h" 30 #include "mozilla/OwningNonNull.h" 31 #include "mozilla/SelectionState.h" 32 #include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_* 33 #include "mozilla/dom/AncestorIterator.h" 34 #include "mozilla/dom/Element.h" 35 #include "mozilla/dom/ElementInlines.h" // for Element::IsContentEditablePlainTextOnly 36 #include "mozilla/dom/HTMLBRElement.h" 37 #include "mozilla/dom/Selection.h" 38 #include "nsAtom.h" 39 #include "nsComputedDOMStyle.h" // for nsComputedDOMStyle 40 #include "nsContentUtils.h" 41 #include "nsDebug.h" 42 #include "nsError.h" 43 #include "nsFrameSelection.h" 44 #include "nsGkAtoms.h" 45 #include "nsIContent.h" 46 #include "nsINode.h" 47 #include "nsRange.h" 48 #include "nsString.h" 49 #include "nsStringFwd.h" 50 #include "nsStyleConsts.h" // for StyleWhiteSpace 51 #include "nsTArray.h" 52 53 // NOTE: This file was split from: 54 // https://searchfox.org/mozilla-central/rev/c409dd9235c133ab41eba635f906aa16e050c197/editor/libeditor/HTMLEditSubActionHandler.cpp 55 56 namespace mozilla { 57 58 using namespace dom; 59 using EditablePointOption = HTMLEditUtils::EditablePointOption; 60 using EditablePointOptions = HTMLEditUtils::EditablePointOptions; 61 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; 62 using InvisibleWhiteSpaces = HTMLEditUtils::InvisibleWhiteSpaces; 63 using LeafNodeType = HTMLEditUtils::LeafNodeType; 64 using ScanLineBreak = HTMLEditUtils::ScanLineBreak; 65 using TableBoundary = HTMLEditUtils::TableBoundary; 66 using WalkTreeOption = HTMLEditUtils::WalkTreeOption; 67 68 static LazyLogModule gOneLineMoverLog("AutoMoveOneLineHandler"); 69 70 template Result<CaretPoint, nsresult> 71 HTMLEditor::DeleteTextAndTextNodesWithTransaction( 72 const EditorDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint, 73 TreatEmptyTextNodes aTreatEmptyTextNodes); 74 template Result<CaretPoint, nsresult> 75 HTMLEditor::DeleteTextAndTextNodesWithTransaction( 76 const EditorDOMPointInText& aStartPoint, 77 const EditorDOMPointInText& aEndPoint, 78 TreatEmptyTextNodes aTreatEmptyTextNodes); 79 80 bool HTMLEditor::AutoDeleteRangesHandler:: 81 CanFallbackToDeleteRangeWithTransaction( 82 const nsRange& aRangeToDelete) const { 83 return !IsHandlingRecursively() && 84 (!aRangeToDelete.Collapsed() || 85 EditorBase::HowToHandleCollapsedRangeFor( 86 mOriginalDirectionAndAmount) != 87 EditorBase::HowToHandleCollapsedRange::Ignore); 88 } 89 90 bool HTMLEditor::AutoDeleteRangesHandler:: 91 CanFallbackToDeleteRangesWithTransaction( 92 const AutoClonedSelectionRangeArray& aRangesToDelete) const { 93 return !IsHandlingRecursively() && !aRangesToDelete.Ranges().IsEmpty() && 94 (!aRangesToDelete.IsCollapsed() || 95 EditorBase::HowToHandleCollapsedRangeFor( 96 mOriginalDirectionAndAmount) != 97 EditorBase::HowToHandleCollapsedRange::Ignore); 98 } 99 100 Result<CaretPoint, nsresult> 101 HTMLEditor::AutoDeleteRangesHandler::FallbackToDeleteRangesWithTransaction( 102 HTMLEditor& aHTMLEditor, AutoClonedSelectionRangeArray& aRangesToDelete, 103 const Element& aEditingHost) const { 104 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 105 MOZ_ASSERT(CanFallbackToDeleteRangesWithTransaction(aRangesToDelete)); 106 107 const auto stripWrappers = [&]() -> nsIEditor::EStripWrappers { 108 if (mOriginalStripWrappers == nsIEditor::eStrip && 109 aEditingHost.IsContentEditablePlainTextOnly()) { 110 return nsIEditor::eNoStrip; 111 } 112 return mOriginalStripWrappers; 113 }(); 114 115 { 116 AutoTrackDOMRange firstRangeTracker(aHTMLEditor.RangeUpdaterRef(), 117 &aRangesToDelete.FirstRangeRef()); 118 for (OwningNonNull<nsRange>& range : Reversed(aRangesToDelete.Ranges())) { 119 if (MOZ_UNLIKELY(!range->IsPositioned() || range->Collapsed())) { 120 continue; 121 } 122 Maybe<AutoTrackDOMRange> trackRange; 123 if (range != aRangesToDelete.FirstRangeRef()) { 124 trackRange.emplace(aHTMLEditor.RangeUpdaterRef(), &range); 125 } 126 Result<EditorDOMRange, nsresult> rangeToDeleteOrError = 127 WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToJoin( 128 aHTMLEditor, EditorDOMRange(range)); 129 if (MOZ_UNLIKELY(rangeToDeleteOrError.isErr())) { 130 NS_WARNING( 131 "WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToJoin(" 132 ") failed"); 133 return rangeToDeleteOrError.propagateErr(); 134 } 135 trackRange.reset(); 136 EditorDOMRange rangeToDelete = rangeToDeleteOrError.unwrap(); 137 if (MOZ_LIKELY(rangeToDelete.IsPositionedAndValidInComposedDoc())) { 138 nsresult rv = 139 range->SetStartAndEnd(rangeToDelete.StartRef().ToRawRangeBoundary(), 140 rangeToDelete.EndRef().ToRawRangeBoundary()); 141 if (NS_FAILED(rv)) { 142 NS_WARNING("nsRange::SetStartAndEnd() failed"); 143 return Err(rv); 144 } 145 } 146 } 147 } 148 aRangesToDelete.RemoveCollapsedRanges(); 149 if (MOZ_UNLIKELY(aRangesToDelete.IsCollapsed())) { 150 return CaretPoint( 151 EditorDOMPoint(aRangesToDelete.FirstRangeRef()->StartRef())); 152 } 153 154 Result<CaretPoint, nsresult> caretPointOrError = 155 aHTMLEditor.DeleteRangesWithTransaction(mOriginalDirectionAndAmount, 156 stripWrappers, aRangesToDelete); 157 NS_WARNING_ASSERTION(caretPointOrError.isOk(), 158 "HTMLEditor::DeleteRangesWithTransaction() failed"); 159 return caretPointOrError; 160 } 161 162 nsresult 163 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction( 164 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 165 AutoClonedSelectionRangeArray& aRangesToDelete, 166 const Element& aEditingHost) const { 167 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); 168 const EditorBase::HowToHandleCollapsedRange howToHandleCollapsedRange = 169 EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount); 170 if (NS_WARN_IF(aRangesToDelete.IsCollapsed() && 171 howToHandleCollapsedRange == 172 EditorBase::HowToHandleCollapsedRange::Ignore)) { 173 return NS_ERROR_FAILURE; 174 } 175 176 const auto stripWrappers = [&]() -> nsIEditor::EStripWrappers { 177 if (mOriginalStripWrappers == nsIEditor::eStrip && 178 aEditingHost.IsContentEditablePlainTextOnly()) { 179 return nsIEditor::eNoStrip; 180 } 181 return mOriginalStripWrappers; 182 }(); 183 184 aRangesToDelete.ExtendRangeToContainSurroundingInvisibleWhiteSpaces( 185 stripWrappers); 186 if (MOZ_UNLIKELY(aRangesToDelete.IsCollapsed() && 187 howToHandleCollapsedRange == 188 EditorBase::HowToHandleCollapsedRange::Ignore)) { 189 return NS_OK; 190 } 191 192 for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { 193 if (range->Collapsed()) { 194 continue; 195 } 196 nsresult rv = ComputeRangeToDeleteRangeWithTransaction( 197 aHTMLEditor, aDirectionAndAmount, range, aEditingHost); 198 if (NS_FAILED(rv)) { 199 NS_WARNING( 200 "AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction() " 201 "failed"); 202 return rv; 203 } 204 } 205 return NS_OK; 206 } 207 208 Result<EditActionResult, nsresult> 209 HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::Run( 210 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 211 nsIEditor::EStripWrappers aStripWrappers, const EditorDOMPoint& aCaretPoint, 212 nsRange& aRangeToDelete, const Element& aEditingHost) { 213 switch (mMode) { 214 case Mode::JoinCurrentBlock: { 215 Result<EditActionResult, nsresult> result = 216 HandleDeleteAtCurrentBlockBoundary(aHTMLEditor, aDirectionAndAmount, 217 aCaretPoint, aEditingHost); 218 NS_WARNING_ASSERTION(result.isOk(), 219 "AutoBlockElementsJoiner::" 220 "HandleDeleteAtCurrentBlockBoundary() failed"); 221 return result; 222 } 223 case Mode::JoinOtherBlock: { 224 Result<EditActionResult, nsresult> result = 225 HandleDeleteAtOtherBlockBoundary(aHTMLEditor, aDirectionAndAmount, 226 aStripWrappers, aCaretPoint, 227 aRangeToDelete, aEditingHost); 228 NS_WARNING_ASSERTION( 229 result.isOk(), 230 "AutoBlockElementsJoiner::HandleDeleteAtOtherBlockBoundary() failed"); 231 return result; 232 } 233 case Mode::DeleteBRElement: 234 case Mode::DeletePrecedingBRElementOfBlock: 235 case Mode::DeletePrecedingPreformattedLineBreak: { 236 Result<EditActionResult, nsresult> result = HandleDeleteLineBreak( 237 aHTMLEditor, aDirectionAndAmount, aCaretPoint, aEditingHost); 238 NS_WARNING_ASSERTION( 239 result.isOk(), 240 "AutoBlockElementsJoiner::HandleDeleteLineBreak() failed"); 241 return result; 242 } 243 case Mode::JoinBlocksInSameParent: 244 case Mode::DeleteContentInRange: 245 case Mode::DeleteNonCollapsedRange: 246 case Mode::DeletePrecedingLinesAndContentInRange: 247 MOZ_ASSERT_UNREACHABLE("This mode should be handled in the other Run()"); 248 return Err(NS_ERROR_UNEXPECTED); 249 case Mode::NotInitialized: 250 return EditActionResult::IgnoredResult(); 251 } 252 return Err(NS_ERROR_NOT_INITIALIZED); 253 } 254 255 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: 256 ComputeRangeToDelete(const HTMLEditor& aHTMLEditor, 257 nsIEditor::EDirection aDirectionAndAmount, 258 const EditorDOMPoint& aCaretPoint, 259 nsRange& aRangeToDelete, 260 const Element& aEditingHost) const { 261 switch (mMode) { 262 case Mode::JoinCurrentBlock: { 263 nsresult rv = ComputeRangeToDeleteAtCurrentBlockBoundary( 264 aHTMLEditor, aCaretPoint, aRangeToDelete, aEditingHost); 265 NS_WARNING_ASSERTION( 266 NS_SUCCEEDED(rv), 267 "AutoBlockElementsJoiner::ComputeRangeToDeleteAtCurrentBlockBoundary(" 268 ") failed"); 269 return rv; 270 } 271 case Mode::JoinOtherBlock: { 272 nsresult rv = ComputeRangeToDeleteAtOtherBlockBoundary( 273 aHTMLEditor, aDirectionAndAmount, aCaretPoint, aRangeToDelete, 274 aEditingHost); 275 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 276 "AutoBlockElementsJoiner::" 277 "ComputeRangeToDeleteAtOtherBlockBoundary() failed"); 278 return rv; 279 } 280 case Mode::DeleteBRElement: 281 case Mode::DeletePrecedingBRElementOfBlock: 282 case Mode::DeletePrecedingPreformattedLineBreak: { 283 nsresult rv = ComputeRangeToDeleteLineBreak( 284 aHTMLEditor, aRangeToDelete, aEditingHost, 285 ComputeRangeFor::GetTargetRanges); 286 NS_WARNING_ASSERTION( 287 NS_SUCCEEDED(rv), 288 "AutoBlockElementsJoiner::ComputeRangeToDeleteLineBreak() failed"); 289 return rv; 290 } 291 case Mode::JoinBlocksInSameParent: 292 case Mode::DeleteContentInRange: 293 case Mode::DeleteNonCollapsedRange: 294 case Mode::DeletePrecedingLinesAndContentInRange: 295 MOZ_ASSERT_UNREACHABLE( 296 "This mode should be handled in the other ComputeRangesToDelete()"); 297 return NS_ERROR_UNEXPECTED; 298 case Mode::NotInitialized: 299 return NS_OK; 300 } 301 return NS_ERROR_NOT_IMPLEMENTED; 302 } 303 304 Result<EditActionResult, nsresult> 305 HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::Run( 306 HTMLEditor& aHTMLEditor, const LimitersAndCaretData& aLimitersAndCaretData, 307 nsIEditor::EDirection aDirectionAndAmount, 308 nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete, 309 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, 310 const Element& aEditingHost) { 311 switch (mMode) { 312 case Mode::JoinCurrentBlock: 313 case Mode::JoinOtherBlock: 314 case Mode::DeleteBRElement: 315 case Mode::DeletePrecedingBRElementOfBlock: 316 case Mode::DeletePrecedingPreformattedLineBreak: 317 MOZ_ASSERT_UNREACHABLE("This mode should be handled in the other Run()"); 318 return Err(NS_ERROR_UNEXPECTED); 319 case Mode::JoinBlocksInSameParent: { 320 Result<EditActionResult, nsresult> result = JoinBlockElementsInSameParent( 321 aHTMLEditor, aLimitersAndCaretData, aDirectionAndAmount, 322 aStripWrappers, aRangeToDelete, aSelectionWasCollapsed, aEditingHost); 323 NS_WARNING_ASSERTION( 324 result.isOk(), 325 "AutoBlockElementsJoiner::JoinBlockElementsInSameParent() failed"); 326 return result; 327 } 328 case Mode::DeleteContentInRange: { 329 Result<EditActionResult, nsresult> result = DeleteContentInRange( 330 aHTMLEditor, aLimitersAndCaretData, aDirectionAndAmount, 331 aStripWrappers, aRangeToDelete, aEditingHost); 332 NS_WARNING_ASSERTION( 333 result.isOk(), 334 "AutoBlockElementsJoiner::DeleteContentInRange() failed"); 335 return result; 336 } 337 case Mode::DeleteNonCollapsedRange: 338 case Mode::DeletePrecedingLinesAndContentInRange: { 339 Result<EditActionResult, nsresult> result = HandleDeleteNonCollapsedRange( 340 aHTMLEditor, aDirectionAndAmount, aStripWrappers, aRangeToDelete, 341 aSelectionWasCollapsed, aEditingHost); 342 NS_WARNING_ASSERTION( 343 result.isOk(), 344 "AutoBlockElementsJoiner::HandleDeleteNonCollapsedRange() failed"); 345 return result; 346 } 347 case Mode::NotInitialized: 348 MOZ_ASSERT_UNREACHABLE("Call Run() after calling a preparation method"); 349 return EditActionResult::IgnoredResult(); 350 } 351 return Err(NS_ERROR_NOT_INITIALIZED); 352 } 353 354 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: 355 ComputeRangeToDelete( 356 const HTMLEditor& aHTMLEditor, 357 const AutoClonedSelectionRangeArray& aRangesToDelete, 358 nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete, 359 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, 360 const Element& aEditingHost) const { 361 switch (mMode) { 362 case Mode::JoinCurrentBlock: 363 case Mode::JoinOtherBlock: 364 case Mode::DeleteBRElement: 365 case Mode::DeletePrecedingBRElementOfBlock: 366 case Mode::DeletePrecedingPreformattedLineBreak: 367 MOZ_ASSERT_UNREACHABLE( 368 "This mode should be handled in the other ComputeRangesToDelete()"); 369 return NS_ERROR_UNEXPECTED; 370 case Mode::JoinBlocksInSameParent: { 371 nsresult rv = ComputeRangeToJoinBlockElementsInSameParent( 372 aHTMLEditor, aDirectionAndAmount, aRangeToDelete, aEditingHost); 373 NS_WARNING_ASSERTION( 374 NS_SUCCEEDED(rv), 375 "AutoBlockElementsJoiner::" 376 "ComputeRangesToJoinBlockElementsInSameParent() failed"); 377 return rv; 378 } 379 case Mode::DeleteContentInRange: { 380 nsresult rv = ComputeRangeToDeleteContentInRange( 381 aHTMLEditor, aDirectionAndAmount, aRangeToDelete, aEditingHost); 382 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 383 "AutoBlockElementsJoiner::" 384 "ComputeRangesToDeleteContentInRanges() failed"); 385 return rv; 386 } 387 case Mode::DeleteNonCollapsedRange: 388 case Mode::DeletePrecedingLinesAndContentInRange: { 389 nsresult rv = ComputeRangeToDeleteNonCollapsedRange( 390 aHTMLEditor, aDirectionAndAmount, aRangeToDelete, 391 aSelectionWasCollapsed, aEditingHost); 392 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 393 "AutoBlockElementsJoiner::" 394 "ComputeRangesToDeleteNonCollapsedRanges() failed"); 395 return rv; 396 } 397 case Mode::NotInitialized: 398 MOZ_ASSERT_UNREACHABLE( 399 "Call ComputeRangesToDelete() after calling a preparation method"); 400 return NS_ERROR_NOT_INITIALIZED; 401 } 402 return NS_ERROR_NOT_INITIALIZED; 403 } 404 405 nsresult HTMLEditor::ComputeTargetRanges( 406 nsIEditor::EDirection aDirectionAndAmount, 407 AutoClonedSelectionRangeArray& aRangesToDelete) const { 408 MOZ_ASSERT(IsEditActionDataAvailable()); 409 410 Element* editingHost = ComputeEditingHost(); 411 if (!editingHost) { 412 aRangesToDelete.RemoveAllRanges(); 413 return NS_ERROR_EDITOR_NO_EDITABLE_RANGE; 414 } 415 416 // First check for table selection mode. If so, hand off to table editor. 417 SelectedTableCellScanner scanner(aRangesToDelete); 418 if (scanner.IsInTableCellSelectionMode()) { 419 // If it's in table cell selection mode, we'll delete all childen in 420 // the all selected table cell elements, 421 if (scanner.ElementsRef().Length() == aRangesToDelete.Ranges().Length()) { 422 return NS_OK; 423 } 424 // but will ignore all ranges which does not select a table cell. 425 size_t removedRanges = 0; 426 for (size_t i = 1; i < scanner.ElementsRef().Length(); i++) { 427 if (HTMLEditUtils::GetTableCellElementIfOnlyOneSelected( 428 aRangesToDelete.Ranges()[i - removedRanges]) != 429 scanner.ElementsRef()[i]) { 430 // XXX Need to manage anchor-focus range too! 431 aRangesToDelete.Ranges().RemoveElementAt(i - removedRanges); 432 removedRanges++; 433 } 434 } 435 return NS_OK; 436 } 437 438 aRangesToDelete.EnsureOnlyEditableRanges(*editingHost); 439 if (aRangesToDelete.Ranges().IsEmpty()) { 440 NS_WARNING( 441 "There is no range which we can delete entire of or around the caret"); 442 return NS_ERROR_EDITOR_NO_EDITABLE_RANGE; 443 } 444 // Delete each range if completely in a replaced element or a void element 445 // because collapsing the range outside may cause the surrounding content 446 // which is outside the selection range will be deleted. 447 if (aRangesToDelete.AdjustRangesNotInReplacedNorVoidElements( 448 AutoClonedRangeArray::RangeInReplacedOrVoidElement::Delete, 449 *editingHost) && 450 !aRangesToDelete.Ranges().Length()) { 451 return NS_ERROR_EDITOR_NO_EDITABLE_RANGE; 452 } 453 AutoDeleteRangesHandler deleteHandler; 454 // Should we delete target ranges which cannot delete actually? 455 nsresult rv = deleteHandler.ComputeRangesToDelete( 456 *this, aDirectionAndAmount, aRangesToDelete, *editingHost); 457 NS_WARNING_ASSERTION( 458 NS_SUCCEEDED(rv), 459 "AutoDeleteRangesHandler::ComputeRangesToDelete() failed"); 460 return rv; 461 } 462 463 Result<EditActionResult, nsresult> HTMLEditor::HandleDeleteSelection( 464 nsIEditor::EDirection aDirectionAndAmount, 465 nsIEditor::EStripWrappers aStripWrappers) { 466 MOZ_ASSERT(IsEditActionDataAvailable()); 467 MOZ_ASSERT(aStripWrappers == nsIEditor::eStrip || 468 aStripWrappers == nsIEditor::eNoStrip); 469 470 if (MOZ_UNLIKELY(!SelectionRef().RangeCount())) { 471 return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE); 472 } 473 474 const RefPtr<Element> editingHost = ComputeEditingHost(); 475 if (MOZ_UNLIKELY(!editingHost)) { 476 return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE); 477 } 478 479 // Remember that we did a selection deletion. Used by 480 // CreateStyleForInsertText() 481 TopLevelEditSubActionDataRef().mDidDeleteSelection = true; 482 483 if (MOZ_UNLIKELY(IsEmpty())) { 484 return EditActionResult::CanceledResult(); 485 } 486 487 // First check for table selection mode. If so, hand off to table editor. 488 if (HTMLEditUtils::IsInTableCellSelectionMode(SelectionRef())) { 489 nsresult rv = DeleteTableCellContentsWithTransaction(); 490 if (NS_WARN_IF(Destroyed())) { 491 return Err(NS_ERROR_EDITOR_DESTROYED); 492 } 493 if (NS_FAILED(rv)) { 494 NS_WARNING("HTMLEditor::DeleteTableCellContentsWithTransaction() failed"); 495 return Err(rv); 496 } 497 return EditActionResult::HandledResult(); 498 } 499 500 AutoClonedSelectionRangeArray rangesToDelete(SelectionRef()); 501 rangesToDelete.EnsureOnlyEditableRanges(*editingHost); 502 // AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() need to use 503 // NodeIsInLimiters() to extend the range for deletion. But if focus event 504 // doesn't receive yet, ancestor hasn't been set yet. So we need to set 505 // ancestor limiter to editing host, <body> or something else in such case. 506 if (!rangesToDelete.GetAncestorLimiter()) { 507 rangesToDelete.SetAncestorLimiter(FindSelectionRoot(*editingHost)); 508 } 509 if (MOZ_UNLIKELY(rangesToDelete.Ranges().IsEmpty())) { 510 NS_WARNING( 511 "There is no range which we can delete entire the ranges or around the " 512 "caret"); 513 return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE); 514 } 515 // Delete each range if completely in a replaced element or a void element 516 // because collapsing the range outside may cause the surrounding content 517 // which is outside the selection range will be deleted. 518 if (rangesToDelete.AdjustRangesNotInReplacedNorVoidElements( 519 AutoClonedRangeArray::RangeInReplacedOrVoidElement::Delete, 520 *editingHost) && 521 rangesToDelete.Ranges().IsEmpty()) { 522 // Collapse Selection to the first editable range to avoid the toplevel edit 523 // subaction handler to be confused at non-selection ranges. 524 if (GetTopLevelEditSubAction() != EditSubAction::eDeleteSelectedContent) { 525 AutoClonedSelectionRangeArray editableSelectionRanges(SelectionRef()); 526 editableSelectionRanges.EnsureOnlyEditableRanges(*editingHost); 527 if (!editableSelectionRanges.GetAncestorLimiter()) { 528 editableSelectionRanges.SetAncestorLimiter( 529 FindSelectionRoot(*editingHost)); 530 } 531 editableSelectionRanges.AdjustRangesNotInReplacedNorVoidElements( 532 AutoClonedRangeArray::RangeInReplacedOrVoidElement::Collapse, 533 *editingHost); 534 if (NS_WARN_IF(editableSelectionRanges.Ranges().IsEmpty())) { 535 return Err(NS_ERROR_FAILURE); 536 } 537 nsresult rv = editableSelectionRanges.Collapse( 538 editableSelectionRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>()); 539 if (NS_WARN_IF(Destroyed())) { 540 return Err(NS_ERROR_EDITOR_DESTROYED); 541 } 542 if (NS_FAILED(rv)) { 543 return Err(rv); 544 } 545 } 546 return Err(NS_ERROR_EDITOR_NO_DELETABLE_RANGE); 547 } 548 AutoDeleteRangesHandler deleteHandler; 549 Result<EditActionResult, nsresult> result = deleteHandler.Run( 550 *this, aDirectionAndAmount, aStripWrappers, rangesToDelete, *editingHost); 551 if (MOZ_UNLIKELY(result.isErr()) || result.inspect().Canceled()) { 552 NS_WARNING_ASSERTION(result.isOk(), 553 "AutoDeleteRangesHandler::Run() failed"); 554 return result; 555 } 556 return EditActionResult::HandledResult(); 557 } 558 559 Result<EditorDOMPoint, nsresult> HTMLEditor::DeleteLineBreakWithTransaction( 560 const EditorLineBreak& aLineBreak, 561 nsIEditor::EStripWrappers aDeleteEmptyInlines, 562 const Element& aEditingHost) { 563 MOZ_ASSERT(aLineBreak.IsInComposedDoc()); 564 MOZ_ASSERT_IF(aLineBreak.IsPreformattedLineBreak(), 565 aLineBreak.CharAtOffsetIsLineBreak()); 566 567 if (aLineBreak.IsHTMLBRElement() || 568 aLineBreak.TextIsOnlyPreformattedLineBreak()) { 569 const OwningNonNull<nsIContent> nodeToDelete = [&]() -> nsIContent& { 570 if (aDeleteEmptyInlines == nsIEditor::eNoStrip) { 571 return aLineBreak.ContentRef(); 572 } 573 Element* const newEmptyInlineElement = 574 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 575 aLineBreak.ContentRef(), 576 BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost); 577 return newEmptyInlineElement ? *newEmptyInlineElement 578 : aLineBreak.ContentRef(); 579 }(); 580 const nsCOMPtr<nsINode> parentNode = nodeToDelete->GetParentNode(); 581 if (NS_WARN_IF(!parentNode)) { 582 return Err(NS_ERROR_FAILURE); 583 } 584 const nsCOMPtr<nsIContent> nextSibling = nodeToDelete->GetNextSibling(); 585 nsresult rv = DeleteNodeWithTransaction(nodeToDelete); 586 if (NS_FAILED(rv)) { 587 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 588 return Err(rv); 589 } 590 if (NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parentNode) || 591 NS_WARN_IF(!parentNode->IsInComposedDoc())) { 592 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 593 } 594 return nextSibling ? EditorDOMPoint(nextSibling) 595 : EditorDOMPoint::AtEndOf(*parentNode); 596 } 597 598 const OwningNonNull<Text> textNode(aLineBreak.TextRef()); 599 Result<CaretPoint, nsresult> caretPointOrError = 600 DeleteTextWithTransaction(textNode, aLineBreak.Offset(), 1u); 601 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 602 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed"); 603 return caretPointOrError.propagateErr(); 604 } 605 if (NS_WARN_IF(!caretPointOrError.inspect().HasCaretPointSuggestion())) { 606 return Err(NS_ERROR_FAILURE); 607 } 608 return caretPointOrError.unwrap().UnwrapCaretPoint(); 609 } 610 611 Result<CaretPoint, nsresult> HTMLEditor::DeleteRangesWithTransaction( 612 nsIEditor::EDirection aDirectionAndAmount, 613 nsIEditor::EStripWrappers aStripWrappers, 614 AutoClonedRangeArray& aRangesToDelete) { 615 const RefPtr<Element> editingHost = 616 ComputeEditingHost(LimitInBodyElement::No); 617 if (NS_WARN_IF(!editingHost)) { 618 return Err(NS_ERROR_UNEXPECTED); 619 } 620 621 aRangesToDelete.ExtendRangeToContainSurroundingInvisibleWhiteSpaces( 622 aStripWrappers); 623 if (MOZ_UNLIKELY(aRangesToDelete.IsCollapsed())) { 624 return CaretPoint(EditorDOMPoint(aRangesToDelete.FocusRef())); 625 } 626 627 Result<CaretPoint, nsresult> result = EditorBase::DeleteRangesWithTransaction( 628 aDirectionAndAmount, aStripWrappers, aRangesToDelete); 629 if (MOZ_UNLIKELY(result.isErr())) { 630 return result; 631 } 632 633 const bool isDeleteSelection = 634 GetTopLevelEditSubAction() == EditSubAction::eDeleteSelectedContent; 635 EditorDOMPoint pointToPutCaret = result.unwrap().UnwrapCaretPoint(); 636 MOZ_ASSERT_IF(pointToPutCaret.IsSet(), HTMLEditUtils::IsSimplyEditableNode( 637 *pointToPutCaret.GetContainer())); 638 { 639 AutoTrackDOMPoint trackCaretPoint(RangeUpdaterRef(), &pointToPutCaret); 640 for (const auto& range : aRangesToDelete.Ranges()) { 641 // Refer the start boundary of the range because it should be end of the 642 // preceding content, but the end boundary may be in an ancestor when an 643 // ancestor element of end boundary has already been deleted. 644 if (MOZ_UNLIKELY(!range->IsPositioned() || 645 !range->GetStartContainer()->IsContent())) { 646 continue; 647 } 648 EditorDOMPoint pointToInsertLineBreak(range->StartRef()); 649 // Don't remove empty inline elements in the plaintext-only mode because 650 // nobody can restore the style again. 651 if (aStripWrappers == nsIEditor::eStrip) { 652 const OwningNonNull<nsIContent> maybeEmptyContent = 653 *pointToInsertLineBreak.ContainerAs<nsIContent>(); 654 if (MOZ_UNLIKELY( 655 !HTMLEditUtils::IsRemovableFromParentNode(maybeEmptyContent))) { 656 continue; 657 } 658 // If the `Text` becomes invisible but has collapsible white-spaces, we 659 // shouldn't delete it because the deletion deletes only before or after 660 // the white-space to keep the white-space visible. 661 if (!maybeEmptyContent->IsText() || 662 !maybeEmptyContent->AsText()->TextDataLength()) { 663 Result<CaretPoint, nsresult> caretPointOrError = 664 DeleteEmptyInclusiveAncestorInlineElements(maybeEmptyContent, 665 *editingHost); 666 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 667 NS_WARNING( 668 "HTMLEditor::DeleteEmptyInclusiveAncestorInlineElements() " 669 "failed"); 670 return caretPointOrError.propagateErr(); 671 } 672 if (NS_WARN_IF(!range->IsPositioned() || 673 !range->GetStartContainer()->IsContent())) { 674 continue; 675 } 676 MOZ_ASSERT_IF( 677 caretPointOrError.inspect().HasCaretPointSuggestion(), 678 HTMLEditUtils::IsSimplyEditableNode( 679 *caretPointOrError.inspect().CaretPointRef().GetContainer())); 680 caretPointOrError.unwrap().MoveCaretPointTo( 681 pointToInsertLineBreak, {SuggestCaret::OnlyIfHasSuggestion}); 682 if (NS_WARN_IF( 683 !pointToInsertLineBreak.IsSetAndValidInComposedDoc())) { 684 continue; 685 } 686 } 687 } 688 689 if ((IsMailEditor() || IsPlaintextMailComposer()) && 690 MOZ_LIKELY(pointToInsertLineBreak.IsInContentNode())) { 691 AutoTrackDOMPoint trackPointToInsertLineBreak(RangeUpdaterRef(), 692 &pointToInsertLineBreak); 693 nsresult rv = DeleteMostAncestorMailCiteElementIfEmpty( 694 MOZ_KnownLive(*pointToInsertLineBreak.ContainerAs<nsIContent>())); 695 if (NS_FAILED(rv)) { 696 NS_WARNING( 697 "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed"); 698 return Err(rv); 699 } 700 trackPointToInsertLineBreak.FlushAndStopTracking(); 701 if (NS_WARN_IF(!pointToInsertLineBreak.IsSetAndValidInComposedDoc())) { 702 continue; 703 } 704 MOZ_ASSERT(HTMLEditUtils::IsSimplyEditableNode( 705 *pointToInsertLineBreak.GetContainer())); 706 } 707 708 if (isDeleteSelection) { 709 { 710 AutoTrackDOMPoint trackPointToInsertLineBreak( 711 RangeUpdaterRef(), &pointToInsertLineBreak); 712 nsresult rv = 713 EnsureNoFollowingUnnecessaryLineBreak(pointToInsertLineBreak); 714 if (NS_FAILED(rv)) { 715 NS_WARNING( 716 "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed"); 717 return Err(rv); 718 } 719 trackPointToInsertLineBreak.FlushAndStopTracking(); 720 if (NS_WARN_IF(!pointToInsertLineBreak 721 .IsInContentNodeAndValidInComposedDoc())) { 722 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 723 } 724 } 725 Result<CreateLineBreakResult, nsresult> insertPaddingBRElementOrError = 726 InsertPaddingBRElementIfNeeded( 727 pointToInsertLineBreak, 728 editingHost->IsContentEditablePlainTextOnly() 729 ? nsIEditor::eNoStrip 730 : nsIEditor::eStrip, 731 *editingHost); 732 if (MOZ_UNLIKELY(insertPaddingBRElementOrError.isErr())) { 733 NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed"); 734 return insertPaddingBRElementOrError.propagateErr(); 735 } 736 insertPaddingBRElementOrError.unwrap().IgnoreCaretPointSuggestion(); 737 } 738 } 739 } 740 return CaretPoint(std::move(pointToPutCaret)); 741 } 742 743 nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete( 744 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 745 AutoClonedSelectionRangeArray& aRangesToDelete, 746 const Element& aEditingHost) { 747 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 748 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); 749 750 mOriginalDirectionAndAmount = aDirectionAndAmount; 751 mOriginalStripWrappers = nsIEditor::eNoStrip; 752 753 if (aHTMLEditor.mPaddingBRElementForEmptyEditor) { 754 nsresult rv = aRangesToDelete.Collapse( 755 EditorRawDOMPoint(aHTMLEditor.mPaddingBRElementForEmptyEditor)); 756 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 757 "AutoClonedRangeArray::Collapse() failed"); 758 return rv; 759 } 760 761 SelectionWasCollapsed selectionWasCollapsed = aRangesToDelete.IsCollapsed() 762 ? SelectionWasCollapsed::Yes 763 : SelectionWasCollapsed::No; 764 if (selectionWasCollapsed == SelectionWasCollapsed::Yes) { 765 const auto startPoint = 766 aRangesToDelete.GetFirstRangeStartPoint<EditorDOMPoint>(); 767 if (NS_WARN_IF(!startPoint.IsSet())) { 768 return NS_ERROR_FAILURE; 769 } 770 if (startPoint.IsInContentNode()) { 771 AutoEmptyBlockAncestorDeleter deleter; 772 if (deleter.ScanEmptyBlockInclusiveAncestor( 773 aHTMLEditor, *startPoint.ContainerAs<nsIContent>())) { 774 nsresult rv = deleter.ComputeTargetRanges( 775 aHTMLEditor, aDirectionAndAmount, aEditingHost, aRangesToDelete); 776 NS_WARNING_ASSERTION( 777 NS_SUCCEEDED(rv), 778 "AutoEmptyBlockAncestorDeleter::ComputeTargetRanges() failed"); 779 return rv; 780 } 781 } 782 783 // We shouldn't update caret bidi level right now, but we need to check 784 // whether the deletion will be canceled or not. 785 AutoCaretBidiLevelManager bidiLevelManager(aHTMLEditor, aDirectionAndAmount, 786 startPoint); 787 if (bidiLevelManager.Failed()) { 788 NS_WARNING( 789 "EditorBase::AutoCaretBidiLevelManager failed to initialize itself"); 790 return NS_ERROR_FAILURE; 791 } 792 if (bidiLevelManager.Canceled()) { 793 return NS_SUCCESS_DOM_NO_OPERATION; 794 } 795 796 Result<nsIEditor::EDirection, nsresult> extendResult = 797 aRangesToDelete.ExtendAnchorFocusRangeFor(aHTMLEditor, 798 aDirectionAndAmount); 799 if (extendResult.isErr()) { 800 NS_WARNING( 801 "AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() failed"); 802 return extendResult.unwrapErr(); 803 } 804 805 // For compatibility with other browsers, we should set target ranges 806 // to start from and/or end after an atomic content rather than start 807 // from preceding text node end nor end at following text node start. 808 Result<bool, nsresult> shrunkenResult = 809 aRangesToDelete.ShrinkRangesIfStartFromOrEndAfterAtomicContent( 810 aHTMLEditor, aDirectionAndAmount, 811 AutoClonedRangeArray::IfSelectingOnlyOneAtomicContent::Collapse); 812 if (shrunkenResult.isErr()) { 813 NS_WARNING( 814 "AutoClonedRangeArray::" 815 "ShrinkRangesIfStartFromOrEndAfterAtomicContent() " 816 "failed"); 817 return shrunkenResult.unwrapErr(); 818 } 819 820 if (!shrunkenResult.inspect() || !aRangesToDelete.IsCollapsed()) { 821 aDirectionAndAmount = extendResult.unwrap(); 822 } 823 824 if (aDirectionAndAmount == nsIEditor::eNone) { 825 MOZ_ASSERT(aRangesToDelete.Ranges().Length() == 1); 826 if (!CanFallbackToDeleteRangesWithTransaction(aRangesToDelete)) { 827 // XXX In this case, do we need to modify the range again? 828 return NS_SUCCESS_DOM_NO_OPERATION; 829 } 830 nsresult rv = FallbackToComputeRangesToDeleteRangesWithTransaction( 831 aHTMLEditor, aRangesToDelete, aEditingHost); 832 NS_WARNING_ASSERTION( 833 NS_SUCCEEDED(rv), 834 "AutoDeleteRangesHandler::" 835 "FallbackToComputeRangesToDeleteRangesWithTransaction() failed"); 836 return rv; 837 } 838 839 if (aRangesToDelete.IsCollapsed()) { 840 const auto caretPoint = 841 aRangesToDelete.GetFirstRangeStartPoint<EditorDOMPoint>(); 842 if (MOZ_UNLIKELY(NS_WARN_IF(!caretPoint.IsInContentNode()))) { 843 return NS_ERROR_FAILURE; 844 } 845 if (!EditorUtils::IsEditableContent(*caretPoint.ContainerAs<nsIContent>(), 846 EditorType::HTML)) { 847 return NS_SUCCESS_DOM_NO_OPERATION; 848 } 849 const WSRunScanner wsRunScannerAtCaret( 850 {WSRunScanner::Option::OnlyEditableNodes}, caretPoint); 851 const WSScanResult scanFromCaretPointResult = 852 aDirectionAndAmount == nsIEditor::eNext 853 ? wsRunScannerAtCaret 854 .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(caretPoint) 855 : wsRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom( 856 caretPoint); 857 if (scanFromCaretPointResult.Failed()) { 858 NS_WARNING( 859 "WSRunScanner::Scan(Next|Previous)VisibleNodeOrBlockBoundaryFrom() " 860 "failed"); 861 return NS_ERROR_FAILURE; 862 } 863 MOZ_ASSERT(scanFromCaretPointResult.GetContent()); 864 865 if (scanFromCaretPointResult.ReachedBRElement()) { 866 if (scanFromCaretPointResult.BRElementPtr() == &aEditingHost) { 867 return NS_OK; 868 } 869 if (!scanFromCaretPointResult.IsContentEditable()) { 870 return NS_SUCCESS_DOM_NO_OPERATION; 871 } 872 if (scanFromCaretPointResult.ReachedInvisibleBRElement()) { 873 EditorDOMPoint newCaretPosition = 874 aDirectionAndAmount == nsIEditor::eNext 875 ? scanFromCaretPointResult 876 .PointAfterReachedContent<EditorDOMPoint>() 877 : scanFromCaretPointResult 878 .PointAtReachedContent<EditorDOMPoint>(); 879 if (NS_WARN_IF(!newCaretPosition.IsSet())) { 880 return NS_ERROR_FAILURE; 881 } 882 AutoHideSelectionChanges blockSelectionListeners( 883 aHTMLEditor.SelectionRef()); 884 nsresult rv = aHTMLEditor.CollapseSelectionTo(newCaretPosition); 885 if (MOZ_UNLIKELY(NS_FAILED(rv))) { 886 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 887 return NS_ERROR_FAILURE; 888 } 889 if (NS_WARN_IF(!aHTMLEditor.SelectionRef().RangeCount())) { 890 return NS_ERROR_UNEXPECTED; 891 } 892 aRangesToDelete.Initialize(aHTMLEditor.SelectionRef()); 893 AutoDeleteRangesHandler anotherHandler(this); 894 rv = anotherHandler.ComputeRangesToDelete( 895 aHTMLEditor, aDirectionAndAmount, aRangesToDelete, aEditingHost); 896 NS_WARNING_ASSERTION( 897 NS_SUCCEEDED(rv), 898 "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() " 899 "failed"); 900 901 rv = aHTMLEditor.CollapseSelectionTo(caretPoint); 902 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 903 NS_WARNING( 904 "EditorBase::CollapseSelectionTo() caused destroying the " 905 "editor"); 906 return NS_ERROR_EDITOR_DESTROYED; 907 } 908 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 909 "EditorBase::CollapseSelectionTo() failed to " 910 "restore original selection, but ignored"); 911 912 MOZ_ASSERT(aRangesToDelete.Ranges().Length() == 1); 913 // If the range is collapsed, there is no content which should 914 // be removed together. In this case, only the invisible `<br>` 915 // element should be selected. 916 if (aRangesToDelete.IsCollapsed()) { 917 nsresult rv = aRangesToDelete.SelectNode( 918 *scanFromCaretPointResult.BRElementPtr()); 919 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 920 "AutoClonedRangeArray::SelectNode() failed"); 921 return rv; 922 } 923 924 // Otherwise, extend the range to contain the invisible `<br>` 925 // element. 926 if (scanFromCaretPointResult 927 .PointAtReachedContent<EditorRawDOMPoint>() 928 .IsBefore( 929 aRangesToDelete 930 .GetFirstRangeStartPoint<EditorRawDOMPoint>())) { 931 nsresult rv = aRangesToDelete.FirstRangeRef()->SetStartAndEnd( 932 EditorRawDOMPoint(scanFromCaretPointResult.BRElementPtr()) 933 .ToRawRangeBoundary(), 934 aRangesToDelete.FirstRangeRef()->EndRef()); 935 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 936 "nsRange::SetStartAndEnd() failed"); 937 return rv; 938 } 939 if (aRangesToDelete.GetFirstRangeEndPoint<EditorRawDOMPoint>() 940 .IsBefore( 941 scanFromCaretPointResult 942 .PointAfterReachedContent<EditorRawDOMPoint>())) { 943 nsresult rv = aRangesToDelete.FirstRangeRef()->SetStartAndEnd( 944 aRangesToDelete.FirstRangeRef()->StartRef(), 945 scanFromCaretPointResult 946 .PointAfterReachedContent<EditorRawDOMPoint>() 947 .ToRawRangeBoundary()); 948 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 949 "nsRange::SetStartAndEnd() failed"); 950 return rv; 951 } 952 NS_WARNING("Was the invisible `<br>` element selected?"); 953 return NS_OK; 954 } 955 } 956 957 nsresult rv = ComputeRangesToDeleteAroundCollapsedRanges( 958 aHTMLEditor, aDirectionAndAmount, aRangesToDelete, 959 wsRunScannerAtCaret, scanFromCaretPointResult, aEditingHost); 960 NS_WARNING_ASSERTION( 961 NS_SUCCEEDED(rv), 962 "AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges(" 963 ") failed"); 964 return rv; 965 } 966 } 967 968 nsresult rv = ComputeRangesToDeleteNonCollapsedRanges( 969 aHTMLEditor, aDirectionAndAmount, aRangesToDelete, selectionWasCollapsed, 970 aEditingHost); 971 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 972 "AutoDeleteRangesHandler::" 973 "ComputeRangesToDeleteNonCollapsedRanges() failed"); 974 return rv; 975 } 976 977 Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler::Run( 978 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 979 nsIEditor::EStripWrappers aStripWrappers, 980 AutoClonedSelectionRangeArray& aRangesToDelete, 981 const Element& aEditingHost) { 982 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 983 MOZ_ASSERT(aStripWrappers == nsIEditor::eStrip || 984 aStripWrappers == nsIEditor::eNoStrip); 985 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); 986 987 mOriginalDirectionAndAmount = aDirectionAndAmount; 988 mOriginalStripWrappers = aStripWrappers; 989 990 if (MOZ_UNLIKELY(aHTMLEditor.IsEmpty())) { 991 return EditActionResult::CanceledResult(); 992 } 993 994 // selectionWasCollapsed is used later to determine whether we should join 995 // blocks in HandleDeleteNonCollapsedRanges(). We don't really care about 996 // collapsed because it will be modified by 997 // AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() later. 998 // AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner should 999 // happen if the original selection is collapsed and the cursor is at the end 1000 // of a block element, in which case 1001 // AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() would always 1002 // make the selection not collapsed. 1003 SelectionWasCollapsed selectionWasCollapsed = aRangesToDelete.IsCollapsed() 1004 ? SelectionWasCollapsed::Yes 1005 : SelectionWasCollapsed::No; 1006 1007 if (selectionWasCollapsed == SelectionWasCollapsed::Yes) { 1008 const auto startPoint = 1009 aRangesToDelete.GetFirstRangeStartPoint<EditorDOMPoint>(); 1010 if (NS_WARN_IF(!startPoint.IsSet())) { 1011 return Err(NS_ERROR_FAILURE); 1012 } 1013 1014 // If we are inside an empty block, delete it. 1015 if (startPoint.IsInContentNode()) { 1016 #ifdef DEBUG 1017 nsMutationGuard debugMutation; 1018 #endif // #ifdef DEBUG 1019 AutoEmptyBlockAncestorDeleter deleter; 1020 if (deleter.ScanEmptyBlockInclusiveAncestor( 1021 aHTMLEditor, *startPoint.ContainerAs<nsIContent>())) { 1022 Result<DeleteRangeResult, nsresult> deleteResultOrError = 1023 deleter.Run(aHTMLEditor, aDirectionAndAmount, aEditingHost); 1024 if (MOZ_UNLIKELY(deleteResultOrError.isErr())) { 1025 NS_WARNING("AutoEmptyBlockAncestorDeleter::Run() failed"); 1026 return deleteResultOrError.propagateErr(); 1027 } 1028 DeleteRangeResult deleteResult = deleteResultOrError.unwrap(); 1029 if (deleteResult.Handled()) { 1030 nsresult rv = deleteResult.SuggestCaretPointTo( 1031 aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion}); 1032 if (NS_FAILED(rv)) { 1033 NS_WARNING("CaretPoint::SuggestCaretPoint() failed"); 1034 return Err(rv); 1035 } 1036 return EditActionResult::HandledResult(); 1037 } 1038 } 1039 MOZ_ASSERT(!debugMutation.Mutated(0), 1040 "AutoEmptyBlockAncestorDeleter shouldn't modify the DOM tree " 1041 "if it returns not handled nor error"); 1042 } 1043 1044 // Test for distance between caret and text that will be deleted. 1045 AutoCaretBidiLevelManager bidiLevelManager(aHTMLEditor, aDirectionAndAmount, 1046 startPoint); 1047 if (MOZ_UNLIKELY(bidiLevelManager.Failed())) { 1048 NS_WARNING( 1049 "EditorBase::AutoCaretBidiLevelManager failed to initialize itself"); 1050 return Err(NS_ERROR_FAILURE); 1051 } 1052 bidiLevelManager.MaybeUpdateCaretBidiLevel(aHTMLEditor); 1053 if (bidiLevelManager.Canceled()) { 1054 return EditActionResult::CanceledResult(); 1055 } 1056 1057 // Calling `ExtendAnchorFocusRangeFor()` and 1058 // `ShrinkRangesIfStartFromOrEndAfterAtomicContent()` may move caret to 1059 // the container of deleting atomic content. However, it may be different 1060 // from the original caret's container. The original caret container may 1061 // be important to put caret after deletion so that let's cache the 1062 // original position. 1063 Maybe<EditorDOMPoint> caretPoint; 1064 if (aRangesToDelete.IsCollapsed() && !aRangesToDelete.Ranges().IsEmpty()) { 1065 caretPoint = 1066 Some(aRangesToDelete.GetFirstRangeStartPoint<EditorDOMPoint>()); 1067 if (NS_WARN_IF(!caretPoint.ref().IsInContentNode())) { 1068 return Err(NS_ERROR_FAILURE); 1069 } 1070 } 1071 1072 Result<nsIEditor::EDirection, nsresult> extendResult = 1073 aRangesToDelete.ExtendAnchorFocusRangeFor(aHTMLEditor, 1074 aDirectionAndAmount); 1075 if (MOZ_UNLIKELY(extendResult.isErr())) { 1076 NS_WARNING( 1077 "AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() failed"); 1078 return extendResult.propagateErr(); 1079 } 1080 if (caretPoint.isSome() && 1081 MOZ_UNLIKELY(!caretPoint.ref().IsSetAndValid())) { 1082 NS_WARNING("The caret position became invalid"); 1083 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1084 } 1085 1086 // If there is only one range and it selects an atomic content, we should 1087 // delete it with collapsed range path for making consistent behavior 1088 // between both cases, the content is selected case and caret is at it or 1089 // after it case. 1090 Result<bool, nsresult> shrunkenResult = 1091 aRangesToDelete.ShrinkRangesIfStartFromOrEndAfterAtomicContent( 1092 aHTMLEditor, aDirectionAndAmount, 1093 AutoClonedRangeArray::IfSelectingOnlyOneAtomicContent::Collapse); 1094 if (MOZ_UNLIKELY(shrunkenResult.isErr())) { 1095 NS_WARNING( 1096 "AutoClonedRangeArray::" 1097 "ShrinkRangesIfStartFromOrEndAfterAtomicContent() " 1098 "failed"); 1099 return shrunkenResult.propagateErr(); 1100 } 1101 1102 if (!shrunkenResult.inspect() || !aRangesToDelete.IsCollapsed()) { 1103 aDirectionAndAmount = extendResult.unwrap(); 1104 } 1105 1106 if (aDirectionAndAmount == nsIEditor::eNone) { 1107 MOZ_ASSERT(aRangesToDelete.Ranges().Length() == 1); 1108 if (!CanFallbackToDeleteRangesWithTransaction(aRangesToDelete)) { 1109 return EditActionResult::IgnoredResult(); 1110 } 1111 Result<CaretPoint, nsresult> caretPointOrError = 1112 FallbackToDeleteRangesWithTransaction(aHTMLEditor, aRangesToDelete, 1113 aEditingHost); 1114 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 1115 NS_WARNING( 1116 "AutoDeleteRangesHandler::FallbackToDeleteRangesWithTransaction() " 1117 "failed"); 1118 } 1119 nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo( 1120 aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, 1121 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 1122 SuggestCaret::AndIgnoreTrivialError}); 1123 if (NS_FAILED(rv)) { 1124 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 1125 return Err(rv); 1126 } 1127 NS_WARNING_ASSERTION( 1128 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 1129 "CaretPoint::SuggestCaretPointTo() failed, but ignored"); 1130 // Don't return "ignored" to avoid to fall it back to delete ranges 1131 // recursively. 1132 return EditActionResult::HandledResult(); 1133 } 1134 1135 if (aRangesToDelete.IsCollapsed()) { 1136 // Use the original caret position for handling the deletion around 1137 // collapsed range because the container may be different from the 1138 // new collapsed position's container. 1139 if (!EditorUtils::IsEditableContent( 1140 *caretPoint.ref().ContainerAs<nsIContent>(), EditorType::HTML)) { 1141 return EditActionResult::CanceledResult(); 1142 } 1143 const WSRunScanner wsRunScannerAtCaret( 1144 {WSRunScanner::Option::OnlyEditableNodes}, caretPoint.ref()); 1145 const WSScanResult scanFromCaretPointResult = 1146 aDirectionAndAmount == nsIEditor::eNext 1147 ? wsRunScannerAtCaret 1148 .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom( 1149 caretPoint.ref()) 1150 : wsRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom( 1151 caretPoint.ref()); 1152 if (MOZ_UNLIKELY(scanFromCaretPointResult.Failed())) { 1153 NS_WARNING( 1154 "WSRunScanner::Scan(Next|Previous)VisibleNodeOrBlockBoundaryFrom() " 1155 "failed"); 1156 return Err(NS_ERROR_FAILURE); 1157 } 1158 MOZ_ASSERT(scanFromCaretPointResult.GetContent()); 1159 1160 // Short circuit for invisible breaks. delete them and recurse. 1161 if (scanFromCaretPointResult.ReachedBRElement()) { 1162 if (scanFromCaretPointResult.BRElementPtr() == &aEditingHost) { 1163 return EditActionResult::HandledResult(); 1164 } 1165 if (!scanFromCaretPointResult.IsContentEditable()) { 1166 return EditActionResult::CanceledResult(); 1167 } 1168 if (scanFromCaretPointResult.ReachedInvisibleBRElement()) { 1169 // TODO: We should extend the range to delete again before/after 1170 // the caret point and use `HandleDeleteNonCollapsedRanges()` 1171 // instead after we would create delete range computation 1172 // method at switching to the new white-space normalizer. 1173 Result<CaretPoint, nsresult> caretPointOrError = 1174 WhiteSpaceVisibilityKeeper:: 1175 DeleteContentNodeAndJoinTextNodesAroundIt( 1176 aHTMLEditor, 1177 MOZ_KnownLive(*scanFromCaretPointResult.BRElementPtr()), 1178 caretPoint.ref(), aEditingHost); 1179 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 1180 NS_WARNING( 1181 "WhiteSpaceVisibilityKeeper::" 1182 "DeleteContentNodeAndJoinTextNodesAroundIt() failed"); 1183 return caretPointOrError.propagateErr(); 1184 } 1185 if (caretPointOrError.inspect().HasCaretPointSuggestion()) { 1186 caretPoint = Some(caretPointOrError.unwrap().UnwrapCaretPoint()); 1187 } 1188 if (NS_WARN_IF(!caretPoint->IsSetAndValid())) { 1189 return Err(NS_ERROR_FAILURE); 1190 } 1191 AutoClonedSelectionRangeArray rangesToDelete( 1192 caretPoint.ref(), aRangesToDelete.LimitersAndCaretDataRef()); 1193 if (NS_WARN_IF(rangesToDelete.Ranges().IsEmpty())) { 1194 return Err(NS_ERROR_FAILURE); 1195 } 1196 if (aHTMLEditor.MaybeNodeRemovalsObservedByDevTools()) { 1197 // Let's check whether there is new invisible `<br>` element 1198 // for avoiding infinite recursive calls. 1199 const WSRunScanner wsRunScannerAtCaret( 1200 {WSRunScanner::Option::OnlyEditableNodes}, caretPoint.ref()); 1201 const WSScanResult scanFromCaretPointResult = 1202 aDirectionAndAmount == nsIEditor::eNext 1203 ? wsRunScannerAtCaret 1204 .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom( 1205 caretPoint.ref()) 1206 : wsRunScannerAtCaret 1207 .ScanPreviousVisibleNodeOrBlockBoundaryFrom( 1208 caretPoint.ref()); 1209 if (MOZ_UNLIKELY(scanFromCaretPointResult.Failed())) { 1210 NS_WARNING( 1211 "WSRunScanner::Scan(Next|Previous)" 1212 "VisibleNodeOrBlockBoundaryFrom() failed"); 1213 return Err(NS_ERROR_FAILURE); 1214 } 1215 if (NS_WARN_IF( 1216 scanFromCaretPointResult.ReachedInvisibleBRElement())) { 1217 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1218 } 1219 } 1220 AutoDeleteRangesHandler anotherHandler(this); 1221 Result<EditActionResult, nsresult> result = 1222 anotherHandler.Run(aHTMLEditor, aDirectionAndAmount, 1223 aStripWrappers, rangesToDelete, aEditingHost); 1224 NS_WARNING_ASSERTION( 1225 result.isOk(), "Recursive AutoDeleteRangesHandler::Run() failed"); 1226 return result; 1227 } 1228 } 1229 1230 Result<EditActionResult, nsresult> result = 1231 HandleDeleteAroundCollapsedRanges( 1232 aHTMLEditor, aDirectionAndAmount, aStripWrappers, aRangesToDelete, 1233 wsRunScannerAtCaret, scanFromCaretPointResult, aEditingHost); 1234 NS_WARNING_ASSERTION(result.isOk(), 1235 "AutoDeleteRangesHandler::" 1236 "HandleDeleteAroundCollapsedRanges() failed"); 1237 return result; 1238 } 1239 } 1240 1241 Result<EditActionResult, nsresult> result = HandleDeleteNonCollapsedRanges( 1242 aHTMLEditor, aDirectionAndAmount, aStripWrappers, aRangesToDelete, 1243 selectionWasCollapsed, aEditingHost); 1244 NS_WARNING_ASSERTION( 1245 result.isOk(), 1246 "AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges() failed"); 1247 return result; 1248 } 1249 1250 nsresult 1251 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges( 1252 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1253 AutoClonedSelectionRangeArray& aRangesToDelete, 1254 const WSRunScanner& aWSRunScannerAtCaret, 1255 const WSScanResult& aScanFromCaretPointResult, 1256 const Element& aEditingHost) const { 1257 if (aScanFromCaretPointResult.InCollapsibleWhiteSpaces() || 1258 aScanFromCaretPointResult.InNonCollapsibleCharacters() || 1259 aScanFromCaretPointResult.ReachedPreformattedLineBreak()) { 1260 // This means that if aDirectionAndAmount == nsIEditor::eNext, collapse 1261 // selection at the found character. Otherwise, collapse selection after 1262 // the found character. 1263 nsresult rv = aRangesToDelete.Collapse( 1264 aScanFromCaretPointResult.Point_Deprecated<EditorRawDOMPoint>()); 1265 if (MOZ_UNLIKELY(NS_FAILED(rv))) { 1266 NS_WARNING("AutoClonedRangeArray::Collapse() failed"); 1267 return NS_ERROR_FAILURE; 1268 } 1269 rv = ComputeRangesToDeleteTextAroundCollapsedRanges(aDirectionAndAmount, 1270 aRangesToDelete); 1271 NS_WARNING_ASSERTION( 1272 NS_SUCCEEDED(rv), 1273 "AutoDeleteRangesHandler::" 1274 "ComputeRangesToDeleteTextAroundCollapsedRanges() failed"); 1275 return rv; 1276 } 1277 1278 if (aScanFromCaretPointResult.ReachedSpecialContent() || 1279 aScanFromCaretPointResult.ReachedBRElement() || 1280 aScanFromCaretPointResult.ReachedHRElement() || 1281 aScanFromCaretPointResult.ReachedNonEditableOtherBlockElement()) { 1282 if (aScanFromCaretPointResult.GetContent() == &aEditingHost) { 1283 return NS_OK; 1284 } 1285 nsIContent* atomicContent = GetAtomicContentToDelete( 1286 aDirectionAndAmount, aWSRunScannerAtCaret, aScanFromCaretPointResult); 1287 if (!HTMLEditUtils::IsRemovableNode(*atomicContent)) { 1288 NS_WARNING( 1289 "AutoDeleteRangesHandler::GetAtomicContentToDelete() cannot find " 1290 "removable atomic content"); 1291 return NS_ERROR_FAILURE; 1292 } 1293 nsresult rv = 1294 ComputeRangesToDeleteAtomicContent(*atomicContent, aRangesToDelete); 1295 NS_WARNING_ASSERTION( 1296 NS_SUCCEEDED(rv), 1297 "AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent() failed"); 1298 return rv; 1299 } 1300 1301 if (aScanFromCaretPointResult.ReachedOtherBlockElement()) { 1302 if (NS_WARN_IF(!aScanFromCaretPointResult.ContentIsElement())) { 1303 return NS_ERROR_FAILURE; 1304 } 1305 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); 1306 bool handled = false; 1307 for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { 1308 MOZ_ASSERT(range->IsPositioned()); 1309 AutoBlockElementsJoiner joiner(*this); 1310 if (!joiner.PrepareToDeleteAtOtherBlockBoundary( 1311 aHTMLEditor, aDirectionAndAmount, 1312 *aScanFromCaretPointResult.ElementPtr(), 1313 aWSRunScannerAtCaret.ScanStartRef(), aWSRunScannerAtCaret)) { 1314 continue; 1315 } 1316 handled = true; 1317 nsresult rv = joiner.ComputeRangeToDelete( 1318 aHTMLEditor, aDirectionAndAmount, aWSRunScannerAtCaret.ScanStartRef(), 1319 range, aEditingHost); 1320 if (NS_FAILED(rv)) { 1321 NS_WARNING( 1322 "AutoBlockElementsJoiner::ComputeRangeToDelete() failed (other " 1323 "block boundary)"); 1324 return rv; 1325 } 1326 } 1327 return handled ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION; 1328 } 1329 1330 if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary() || 1331 aScanFromCaretPointResult.ReachedInlineEditingHostBoundary()) { 1332 MOZ_ASSERT(aScanFromCaretPointResult.ContentIsElement()); 1333 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); 1334 bool handled = false; 1335 for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { 1336 AutoBlockElementsJoiner joiner(*this); 1337 if (!joiner.PrepareToDeleteAtCurrentBlockBoundary( 1338 aHTMLEditor, aDirectionAndAmount, 1339 *aScanFromCaretPointResult.ElementPtr(), 1340 aWSRunScannerAtCaret.ScanStartRef(), aEditingHost)) { 1341 continue; 1342 } 1343 handled = true; 1344 nsresult rv = joiner.ComputeRangeToDelete( 1345 aHTMLEditor, aDirectionAndAmount, aWSRunScannerAtCaret.ScanStartRef(), 1346 range, aEditingHost); 1347 if (NS_FAILED(rv)) { 1348 NS_WARNING( 1349 "AutoBlockElementsJoiner::ComputeRangeToDelete() failed (current " 1350 "block boundary)"); 1351 return rv; 1352 } 1353 } 1354 return handled ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION; 1355 } 1356 1357 return NS_OK; 1358 } 1359 1360 Result<EditActionResult, nsresult> 1361 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges( 1362 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1363 nsIEditor::EStripWrappers aStripWrappers, 1364 AutoClonedSelectionRangeArray& aRangesToDelete, 1365 const WSRunScanner& aWSRunScannerAtCaret, 1366 const WSScanResult& aScanFromCaretPointResult, 1367 const Element& aEditingHost) { 1368 MOZ_ASSERT(aHTMLEditor.IsTopLevelEditSubActionDataAvailable()); 1369 MOZ_ASSERT(aRangesToDelete.IsCollapsed()); 1370 MOZ_ASSERT(aDirectionAndAmount != nsIEditor::eNone); 1371 MOZ_ASSERT(aWSRunScannerAtCaret.ScanStartRef().IsInContentNode()); 1372 MOZ_ASSERT(EditorUtils::IsEditableContent( 1373 *aWSRunScannerAtCaret.ScanStartRef().ContainerAs<nsIContent>(), 1374 EditorType::HTML)); 1375 1376 if (aScanFromCaretPointResult.InCollapsibleWhiteSpaces() || 1377 aScanFromCaretPointResult.InNonCollapsibleCharacters() || 1378 aScanFromCaretPointResult.ReachedPreformattedLineBreak()) { 1379 // This means that if aDirectionAndAmount == nsIEditor::eNext, collapse 1380 // selection at the found character. Otherwise, collapse selection after 1381 // the found character. 1382 nsresult rv = aRangesToDelete.Collapse( 1383 aScanFromCaretPointResult.Point_Deprecated<EditorRawDOMPoint>()); 1384 if (NS_FAILED(rv)) { 1385 NS_WARNING("AutoClonedRangeArray::Collapse() failed"); 1386 return Err(NS_ERROR_FAILURE); 1387 } 1388 Result<CaretPoint, nsresult> caretPointOrError = 1389 HandleDeleteTextAroundCollapsedRanges(aHTMLEditor, aDirectionAndAmount, 1390 aRangesToDelete, aEditingHost); 1391 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 1392 NS_WARNING( 1393 "AutoDeleteRangesHandler::HandleDeleteTextAroundCollapsedRanges() " 1394 "failed"); 1395 return caretPointOrError.propagateErr(); 1396 } 1397 rv = caretPointOrError.unwrap().SuggestCaretPointTo( 1398 aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, 1399 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 1400 SuggestCaret::AndIgnoreTrivialError}); 1401 if (NS_FAILED(rv)) { 1402 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 1403 return Err(rv); 1404 } 1405 NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 1406 "CaretPoint::SuggestCaretPoint() failed, but ignored"); 1407 return EditActionResult::HandledResult(); 1408 } 1409 1410 if (aScanFromCaretPointResult.ReachedSpecialContent() || 1411 aScanFromCaretPointResult.ReachedBRElement() || 1412 aScanFromCaretPointResult.ReachedHRElement() || 1413 aScanFromCaretPointResult.ReachedNonEditableOtherBlockElement()) { 1414 if (aScanFromCaretPointResult.GetContent() == &aEditingHost) { 1415 return EditActionResult::HandledResult(); 1416 } 1417 nsCOMPtr<nsIContent> atomicContent = GetAtomicContentToDelete( 1418 aDirectionAndAmount, aWSRunScannerAtCaret, aScanFromCaretPointResult); 1419 if (MOZ_UNLIKELY(!HTMLEditUtils::IsRemovableNode(*atomicContent))) { 1420 NS_WARNING( 1421 "AutoDeleteRangesHandler::GetAtomicContentToDelete() cannot find " 1422 "removable atomic content"); 1423 return Err(NS_ERROR_FAILURE); 1424 } 1425 Result<CaretPoint, nsresult> caretPointOrError = HandleDeleteAtomicContent( 1426 aHTMLEditor, *atomicContent, aWSRunScannerAtCaret.ScanStartRef(), 1427 aWSRunScannerAtCaret, aEditingHost); 1428 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 1429 NS_WARNING("AutoDeleteRangesHandler::HandleDeleteAtomicContent() failed"); 1430 return caretPointOrError.propagateErr(); 1431 } 1432 nsresult rv = caretPointOrError.unwrap().SuggestCaretPointTo( 1433 aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion}); 1434 if (NS_FAILED(rv)) { 1435 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 1436 return Err(rv); 1437 } 1438 NS_WARNING_ASSERTION( 1439 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 1440 "CaretPoint::SuggestCaretPointTo() failed, but ignored"); 1441 return EditActionResult::HandledResult(); 1442 } 1443 1444 if (aScanFromCaretPointResult.ReachedOtherBlockElement()) { 1445 if (NS_WARN_IF(!aScanFromCaretPointResult.ContentIsElement())) { 1446 return Err(NS_ERROR_FAILURE); 1447 } 1448 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); 1449 bool allRangesNotHandled = true; 1450 auto ret = EditActionResult::IgnoredResult(); 1451 for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { 1452 AutoBlockElementsJoiner joiner(*this); 1453 if (!joiner.PrepareToDeleteAtOtherBlockBoundary( 1454 aHTMLEditor, aDirectionAndAmount, 1455 *aScanFromCaretPointResult.ElementPtr(), 1456 aWSRunScannerAtCaret.ScanStartRef(), aWSRunScannerAtCaret)) { 1457 continue; 1458 } 1459 allRangesNotHandled = false; 1460 Result<EditActionResult, nsresult> result = 1461 joiner.Run(aHTMLEditor, aDirectionAndAmount, aStripWrappers, 1462 aWSRunScannerAtCaret.ScanStartRef(), MOZ_KnownLive(range), 1463 aEditingHost); 1464 if (MOZ_UNLIKELY(result.isErr())) { 1465 NS_WARNING( 1466 "AutoBlockElementsJoiner::Run() failed (other block boundary)"); 1467 return result; 1468 } 1469 ret |= result.inspect(); 1470 } 1471 return allRangesNotHandled ? EditActionResult::CanceledResult() 1472 : std::move(ret); 1473 } 1474 1475 if (aScanFromCaretPointResult.ReachedCurrentBlockBoundary() || 1476 aScanFromCaretPointResult.ReachedInlineEditingHostBoundary()) { 1477 MOZ_ASSERT(aScanFromCaretPointResult.ContentIsElement()); 1478 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); 1479 bool allRangesNotHandled = true; 1480 auto ret = EditActionResult::IgnoredResult(); 1481 for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { 1482 AutoBlockElementsJoiner joiner(*this); 1483 if (!joiner.PrepareToDeleteAtCurrentBlockBoundary( 1484 aHTMLEditor, aDirectionAndAmount, 1485 *aScanFromCaretPointResult.ElementPtr(), 1486 aWSRunScannerAtCaret.ScanStartRef(), aEditingHost)) { 1487 continue; 1488 } 1489 allRangesNotHandled = false; 1490 Result<EditActionResult, nsresult> result = 1491 joiner.Run(aHTMLEditor, aDirectionAndAmount, aStripWrappers, 1492 aWSRunScannerAtCaret.ScanStartRef(), MOZ_KnownLive(range), 1493 aEditingHost); 1494 if (MOZ_UNLIKELY(result.isErr())) { 1495 NS_WARNING( 1496 "AutoBlockElementsJoiner::Run() failed (current block boundary)"); 1497 return result; 1498 } 1499 ret |= result.inspect(); 1500 } 1501 return allRangesNotHandled ? EditActionResult::CanceledResult() 1502 : std::move(ret); 1503 } 1504 1505 MOZ_ASSERT_UNREACHABLE("New type of reached content hasn't been handled yet"); 1506 return EditActionResult::IgnoredResult(); 1507 } 1508 1509 nsresult HTMLEditor::AutoDeleteRangesHandler:: 1510 ComputeRangesToDeleteTextAroundCollapsedRanges( 1511 nsIEditor::EDirection aDirectionAndAmount, 1512 AutoClonedSelectionRangeArray& aRangesToDelete) const { 1513 MOZ_ASSERT(aDirectionAndAmount == nsIEditor::eNext || 1514 aDirectionAndAmount == nsIEditor::ePrevious); 1515 1516 const auto caretPosition = 1517 aRangesToDelete.GetFirstRangeStartPoint<EditorDOMPoint>(); 1518 MOZ_ASSERT(caretPosition.IsSetAndValid()); 1519 if (MOZ_UNLIKELY(NS_WARN_IF(!caretPosition.IsInContentNode()))) { 1520 return NS_ERROR_FAILURE; 1521 } 1522 1523 EditorDOMRangeInTexts rangeToDelete; 1524 if (aDirectionAndAmount == nsIEditor::eNext) { 1525 Result<EditorDOMRangeInTexts, nsresult> result = 1526 WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom( 1527 {WSRunScanner::Option::OnlyEditableNodes}, caretPosition); 1528 if (result.isErr()) { 1529 NS_WARNING( 1530 "WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom() failed"); 1531 return result.unwrapErr(); 1532 } 1533 rangeToDelete = result.unwrap(); 1534 if (!rangeToDelete.IsPositioned()) { 1535 return NS_OK; // no range to delete, but consume it. 1536 } 1537 } else { 1538 Result<EditorDOMRangeInTexts, nsresult> result = 1539 WSRunScanner::GetRangeInTextNodesToBackspaceFrom( 1540 {WSRunScanner::Option::OnlyEditableNodes}, caretPosition); 1541 if (result.isErr()) { 1542 NS_WARNING("WSRunScanner::GetRangeInTextNodesToBackspaceFrom() failed"); 1543 return result.unwrapErr(); 1544 } 1545 rangeToDelete = result.unwrap(); 1546 if (!rangeToDelete.IsPositioned()) { 1547 return NS_OK; // no range to delete, but consume it. 1548 } 1549 } 1550 1551 // FIXME: If we'll delete unnecessary following <br>, we need to include it 1552 // into aRangesToDelete. 1553 1554 nsresult rv = aRangesToDelete.SetStartAndEnd(rangeToDelete.StartRef(), 1555 rangeToDelete.EndRef()); 1556 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1557 "AutoArrayRanges::SetStartAndEnd() failed"); 1558 return rv; 1559 } 1560 1561 Result<CaretPoint, nsresult> 1562 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteTextAroundCollapsedRanges( 1563 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1564 AutoClonedSelectionRangeArray& aRangesToDelete, 1565 const Element& aEditingHost) { 1566 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 1567 MOZ_ASSERT(aDirectionAndAmount == nsIEditor::eNext || 1568 aDirectionAndAmount == nsIEditor::ePrevious); 1569 1570 nsresult rv = ComputeRangesToDeleteTextAroundCollapsedRanges( 1571 aDirectionAndAmount, aRangesToDelete); 1572 if (NS_FAILED(rv)) { 1573 return Err(NS_ERROR_FAILURE); 1574 } 1575 if (MOZ_UNLIKELY(aRangesToDelete.IsCollapsed())) { 1576 return CaretPoint(EditorDOMPoint()); // no range to delete 1577 } 1578 1579 // FYI: rangeToDelete does not contain newly empty inline ancestors which 1580 // are removed by DeleteTextAndNormalizeSurroundingWhiteSpaces(). 1581 // So, if `getTargetRanges()` needs to include parent empty elements, 1582 // we need to extend the range with 1583 // HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement(). 1584 EditorRawDOMRange rangeToDelete(aRangesToDelete.FirstRangeRef()); 1585 if (MOZ_UNLIKELY(!rangeToDelete.IsInTextNodes())) { 1586 NS_WARNING("The extended range to delete character was not in text nodes"); 1587 return Err(NS_ERROR_FAILURE); 1588 } 1589 1590 // If deleting some characters makes the last line before a block boundary 1591 // empty, we need to put a line break. 1592 const bool becomesEmptyLine = [&]() { 1593 if (!rangeToDelete.StartRef().IsStartOfContainer() || 1594 !rangeToDelete.EndRef().IsEndOfContainer()) { 1595 return false; 1596 } 1597 const WSScanResult previousThing = 1598 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 1599 {}, rangeToDelete.StartRef()); 1600 if (!previousThing.ReachedLineBoundary() || 1601 previousThing.ReachedBlockBoundary()) { 1602 return false; 1603 } 1604 WSScanResult nextThing = 1605 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 1606 {}, rangeToDelete.EndRef()); 1607 if (nextThing.ReachedBRElement() || 1608 nextThing.ReachedPreformattedLineBreak()) { 1609 nextThing = WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 1610 {}, nextThing.PointAfterReachedContent<EditorRawDOMPoint>()); 1611 } 1612 return nextThing.ReachedBlockBoundary(); 1613 }(); 1614 1615 Result<CaretPoint, nsresult> caretPointOrError = 1616 aHTMLEditor.DeleteTextAndNormalizeSurroundingWhiteSpaces( 1617 rangeToDelete.StartRef().AsInText(), 1618 rangeToDelete.EndRef().AsInText(), 1619 !aEditingHost.IsContentEditablePlainTextOnly() 1620 ? TreatEmptyTextNodes::RemoveAllEmptyInlineAncestors 1621 : TreatEmptyTextNodes::Remove, 1622 aDirectionAndAmount == nsIEditor::eNext ? DeleteDirection::Forward 1623 : DeleteDirection::Backward, 1624 aEditingHost); 1625 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 1626 NS_WARNING( 1627 "HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces() failed"); 1628 return caretPointOrError; 1629 } 1630 if (!becomesEmptyLine) { 1631 return caretPointOrError; 1632 } 1633 const EditorDOMPoint pointToPutLineBreak = 1634 caretPointOrError.unwrap().UnwrapCaretPoint(); 1635 const Maybe<LineBreakType> lineBreakType = 1636 aHTMLEditor.GetPreferredLineBreakType( 1637 *pointToPutLineBreak.ContainerAs<nsIContent>(), aEditingHost); 1638 if (NS_WARN_IF(lineBreakType.isNothing())) { 1639 return Err(NS_ERROR_FAILURE); 1640 } 1641 Result<CreateLineBreakResult, nsresult> lineBreakOrError = 1642 aHTMLEditor.InsertLineBreak(WithTransaction::Yes, *lineBreakType, 1643 pointToPutLineBreak, nsIEditor::ePrevious); 1644 if (MOZ_UNLIKELY(lineBreakOrError.isErr())) { 1645 NS_WARNING( 1646 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 1647 "nsIEditor::ePrevious) failed"); 1648 return lineBreakOrError.propagateErr(); 1649 } 1650 return CaretPoint(lineBreakOrError.unwrap().UnwrapCaretPoint()); 1651 } 1652 1653 // static 1654 nsIContent* HTMLEditor::AutoDeleteRangesHandler::GetAtomicContentToDelete( 1655 nsIEditor::EDirection aDirectionAndAmount, 1656 const WSRunScanner& aWSRunScannerAtCaret, 1657 const WSScanResult& aScanFromCaretPointResult) { 1658 MOZ_ASSERT(aScanFromCaretPointResult.GetContent()); 1659 1660 if (!aScanFromCaretPointResult.ReachedSpecialContent()) { 1661 return aScanFromCaretPointResult.GetContent(); 1662 } 1663 1664 if (!aScanFromCaretPointResult.GetContent()->IsText() || 1665 HTMLEditUtils::IsRemovableNode(*aScanFromCaretPointResult.GetContent())) { 1666 return aScanFromCaretPointResult.GetContent(); 1667 } 1668 1669 // aScanFromCaretPointResult is non-removable text node. 1670 // Since we try removing atomic content, we look for removable node from 1671 // scanned point that is non-removable text. 1672 nsIContent* removableRoot = aScanFromCaretPointResult.GetContent(); 1673 while (removableRoot && !HTMLEditUtils::IsRemovableNode(*removableRoot)) { 1674 removableRoot = removableRoot->GetParent(); 1675 } 1676 1677 if (removableRoot) { 1678 return removableRoot; 1679 } 1680 1681 // Not found better content. This content may not be removable. 1682 return aScanFromCaretPointResult.GetContent(); 1683 } 1684 1685 nsresult 1686 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent( 1687 const nsIContent& aAtomicContent, 1688 AutoClonedSelectionRangeArray& aRangesToDelete) const { 1689 EditorDOMRange rangeToDelete = 1690 WSRunScanner::GetRangesForDeletingAtomicContent( 1691 {WSRunScanner::Option::OnlyEditableNodes}, aAtomicContent); 1692 if (!rangeToDelete.IsPositioned()) { 1693 NS_WARNING("WSRunScanner::GetRangeForDeleteAContentNode() failed"); 1694 return NS_ERROR_FAILURE; 1695 } 1696 1697 // FIXME: If we'll delete unnecessary following <br>, we need to include it 1698 // into aRangesToDelete. 1699 1700 nsresult rv = aRangesToDelete.SetStartAndEnd(rangeToDelete.StartRef(), 1701 rangeToDelete.EndRef()); 1702 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1703 "AutoClonedRangeArray::SetStartAndEnd() failed"); 1704 return rv; 1705 } 1706 1707 Result<CaretPoint, nsresult> 1708 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAtomicContent( 1709 HTMLEditor& aHTMLEditor, nsIContent& aAtomicContent, 1710 const EditorDOMPoint& aCaretPoint, const WSRunScanner& aWSRunScannerAtCaret, 1711 const Element& aEditingHost) { 1712 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 1713 MOZ_ASSERT(!HTMLEditUtils::IsInvisibleBRElement(aAtomicContent)); 1714 MOZ_ASSERT(!aAtomicContent.IsEditingHost()); 1715 1716 EditorDOMPoint pointToPutCaret = aCaretPoint; 1717 { 1718 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(), 1719 &pointToPutCaret); 1720 Result<CaretPoint, nsresult> caretPointOrError = 1721 WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt( 1722 aHTMLEditor, aAtomicContent, aCaretPoint, aEditingHost); 1723 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 1724 NS_WARNING( 1725 "WhiteSpaceVisibilityKeeper::" 1726 "DeleteContentNodeAndJoinTextNodesAroundIt() failed"); 1727 return caretPointOrError; 1728 } 1729 trackPointToPutCaret.FlushAndStopTracking(); 1730 caretPointOrError.unwrap().MoveCaretPointTo( 1731 pointToPutCaret, aHTMLEditor, 1732 {SuggestCaret::OnlyIfHasSuggestion, 1733 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 1734 if (NS_WARN_IF(!pointToPutCaret.IsSet())) { 1735 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1736 } 1737 } 1738 1739 if (MOZ_LIKELY(pointToPutCaret.IsInContentNode())) { 1740 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(), 1741 &pointToPutCaret); 1742 nsresult rv = 1743 aHTMLEditor.EnsureNoFollowingUnnecessaryLineBreak(pointToPutCaret); 1744 if (NS_FAILED(rv)) { 1745 NS_WARNING("HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed"); 1746 return Err(rv); 1747 } 1748 } 1749 if (NS_WARN_IF(!pointToPutCaret.IsSet())) { 1750 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1751 } 1752 1753 if ((aHTMLEditor.IsMailEditor() || aHTMLEditor.IsPlaintextMailComposer()) && 1754 MOZ_LIKELY(pointToPutCaret.IsInContentNode())) { 1755 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(), 1756 &pointToPutCaret); 1757 nsresult rv = aHTMLEditor.DeleteMostAncestorMailCiteElementIfEmpty( 1758 MOZ_KnownLive(*pointToPutCaret.ContainerAs<nsIContent>())); 1759 if (NS_FAILED(rv)) { 1760 NS_WARNING( 1761 "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed"); 1762 return Err(rv); 1763 } 1764 trackPointToPutCaret.FlushAndStopTracking(); 1765 if (NS_WARN_IF(!pointToPutCaret.IsSetAndValidInComposedDoc())) { 1766 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1767 } 1768 } 1769 1770 if (aHTMLEditor.GetTopLevelEditSubAction() == 1771 EditSubAction::eDeleteSelectedContent) { 1772 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(), 1773 &pointToPutCaret); 1774 Result<CreateLineBreakResult, nsresult> insertPaddingBRElementOrError = 1775 aHTMLEditor.InsertPaddingBRElementIfNeeded( 1776 pointToPutCaret, 1777 aEditingHost.IsContentEditablePlainTextOnly() ? nsIEditor::eNoStrip 1778 : nsIEditor::eStrip, 1779 aEditingHost); 1780 if (MOZ_UNLIKELY(insertPaddingBRElementOrError.isErr())) { 1781 NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed"); 1782 return insertPaddingBRElementOrError.propagateErr(); 1783 } 1784 trackPointToPutCaret.FlushAndStopTracking(); 1785 if (!pointToPutCaret.IsInTextNode()) { 1786 insertPaddingBRElementOrError.unwrap().MoveCaretPointTo( 1787 pointToPutCaret, aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion}); 1788 if (NS_WARN_IF(!pointToPutCaret.IsSet())) { 1789 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1790 } 1791 } else { 1792 insertPaddingBRElementOrError.unwrap().IgnoreCaretPointSuggestion(); 1793 if (NS_WARN_IF(!pointToPutCaret.IsSet())) { 1794 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1795 } 1796 } 1797 } 1798 return CaretPoint(std::move(pointToPutCaret)); 1799 } 1800 1801 // static 1802 Result<bool, nsresult> HTMLEditor::AutoDeleteRangesHandler:: 1803 ExtendRangeToContainAncestorInlineElementsAtStart( 1804 nsRange& aRangeToDelete, const Element& aEditingHost) { 1805 MOZ_ASSERT(aRangeToDelete.IsPositioned()); 1806 MOZ_ASSERT(aRangeToDelete.GetCommonAncestorContainer(IgnoreErrors())); 1807 MOZ_ASSERT(aRangeToDelete.GetCommonAncestorContainer(IgnoreErrors()) 1808 ->IsInclusiveDescendantOf(&aEditingHost)); 1809 1810 EditorRawDOMPoint startPoint(aRangeToDelete.StartRef()); 1811 if (startPoint.IsInTextNode()) { 1812 if (!startPoint.IsStartOfContainer()) { 1813 // FIXME: If before the point has only collapsible white-spaces and the 1814 // text node follows a block boundary, we should treat the range start 1815 // from start of the text node. 1816 return true; 1817 } 1818 startPoint.Set(startPoint.ContainerAs<Text>()); 1819 if (NS_WARN_IF(!startPoint.IsSet())) { 1820 return Err(NS_ERROR_FAILURE); 1821 } 1822 if (startPoint.GetContainer() == &aEditingHost) { 1823 return false; 1824 } 1825 } else if (startPoint.IsInDataNode()) { 1826 startPoint.Set(startPoint.ContainerAs<nsIContent>()); 1827 if (NS_WARN_IF(!startPoint.IsSet())) { 1828 return Err(NS_ERROR_FAILURE); 1829 } 1830 if (startPoint.GetContainer() == &aEditingHost) { 1831 return false; 1832 } 1833 } else if (startPoint.GetContainer() == &aEditingHost) { 1834 return false; 1835 } 1836 1837 // FYI: This method is designed for deleting inline elements which become 1838 // empty if aRangeToDelete which crosses a block boundary of right block 1839 // child. Therefore, you may need to improve this method if you want to use 1840 // this in the other cases. 1841 1842 nsINode* const commonAncestor = 1843 nsContentUtils::GetClosestCommonInclusiveAncestor( 1844 startPoint.GetContainer(), aRangeToDelete.GetEndContainer()); 1845 if (NS_WARN_IF(!commonAncestor)) { 1846 return Err(NS_ERROR_FAILURE); 1847 } 1848 MOZ_ASSERT(commonAncestor->IsInclusiveDescendantOf(&aEditingHost)); 1849 1850 EditorRawDOMPoint newStartPoint(startPoint); 1851 while (newStartPoint.GetContainer() != &aEditingHost && 1852 newStartPoint.GetContainer() != commonAncestor) { 1853 if (NS_WARN_IF(!newStartPoint.IsInContentNode())) { 1854 return Err(NS_ERROR_FAILURE); 1855 } 1856 if (!HTMLEditUtils::IsInlineContent( 1857 *newStartPoint.ContainerAs<nsIContent>(), 1858 BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 1859 break; 1860 } 1861 // The container is inline, check whether the point is first visible point 1862 // or not to consider whether climbing up the tree. 1863 bool foundVisiblePrevSibling = false; 1864 for (nsIContent* content = newStartPoint.GetPreviousSiblingOfChild(); 1865 content; content = content->GetPreviousSibling()) { 1866 if (Text* text = Text::FromNode(content)) { 1867 if (HTMLEditUtils::IsVisibleTextNode(*text)) { 1868 foundVisiblePrevSibling = true; 1869 break; 1870 } 1871 // The text node is invisible. 1872 } else if (content->IsComment()) { 1873 // Ignore the comment node. 1874 } else if (!HTMLEditUtils::IsInlineContent( 1875 *content, 1876 BlockInlineCheck::UseComputedDisplayOutsideStyle) || 1877 !HTMLEditUtils::IsEmptyNode( 1878 *content, 1879 {EmptyCheckOption::TreatSingleBRElementAsVisible})) { 1880 foundVisiblePrevSibling = true; 1881 break; 1882 } 1883 } 1884 if (foundVisiblePrevSibling) { 1885 break; 1886 } 1887 // the point can be treated as start of the parent inline now. 1888 newStartPoint.Set(newStartPoint.ContainerAs<nsIContent>()); 1889 if (NS_WARN_IF(!newStartPoint.IsSet())) { 1890 return Err(NS_ERROR_FAILURE); 1891 } 1892 } 1893 if (newStartPoint == startPoint) { 1894 return false; // Don't need to modify the range 1895 } 1896 IgnoredErrorResult error; 1897 aRangeToDelete.SetStart(newStartPoint.ToRawRangeBoundary(), error); 1898 if (MOZ_UNLIKELY(error.Failed())) { 1899 return Err(NS_ERROR_FAILURE); 1900 } 1901 return true; 1902 } 1903 1904 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: 1905 PrepareToDeleteAtOtherBlockBoundary( 1906 const HTMLEditor& aHTMLEditor, 1907 nsIEditor::EDirection aDirectionAndAmount, Element& aOtherBlockElement, 1908 const EditorDOMPoint& aCaretPoint, 1909 const WSRunScanner& aWSRunScannerAtCaret) { 1910 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 1911 MOZ_ASSERT(aCaretPoint.IsSetAndValid()); 1912 1913 mMode = Mode::JoinOtherBlock; 1914 1915 // Make sure it's not a table element. If so, cancel the operation 1916 // (translation: users cannot backspace or delete across table cells) 1917 if (HTMLEditUtils::IsAnyTableElementExceptColumnElement(aOtherBlockElement)) { 1918 return false; 1919 } 1920 1921 mOtherBlockElement = &aOtherBlockElement; 1922 // First find the adjacent node in the block 1923 mLeafContentInOtherBlock = 1924 ComputeLeafContentInOtherBlockElement(aDirectionAndAmount); 1925 if (aDirectionAndAmount == nsIEditor::ePrevious) { 1926 mLeftContent = mLeafContentInOtherBlock; 1927 mRightContent = aCaretPoint.GetContainerAs<nsIContent>(); 1928 } else { 1929 mLeftContent = aCaretPoint.GetContainerAs<nsIContent>(); 1930 mRightContent = mLeafContentInOtherBlock; 1931 } 1932 1933 // Next to a block. See if we are between the block and a `<br>`. 1934 // If so, we really want to delete the `<br>`. Else join content at 1935 // selection to the block. 1936 const WSScanResult scanFromCaretResult = 1937 aDirectionAndAmount == nsIEditor::eNext 1938 ? aWSRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom( 1939 aCaretPoint) 1940 : aWSRunScannerAtCaret 1941 .ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aCaretPoint); 1942 // If we found a `<br>` element, we need to delete it instead of joining the 1943 // contents. 1944 if (scanFromCaretResult.ReachedBRElement()) { 1945 mBRElement = scanFromCaretResult.BRElementPtr(); 1946 mMode = Mode::DeleteBRElement; 1947 return true; 1948 } 1949 1950 return mLeftContent && mRightContent; 1951 } 1952 nsIContent* HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: 1953 ComputeLeafContentInOtherBlockElement( 1954 nsIEditor::EDirection aDirectionAndAmount) const { 1955 MOZ_ASSERT(mOtherBlockElement); 1956 return aDirectionAndAmount == nsIEditor::ePrevious 1957 ? HTMLEditUtils::GetLastLeafContent( 1958 *mOtherBlockElement, {LeafNodeType::OnlyEditableLeafNode}, 1959 BlockInlineCheck::Unused, mOtherBlockElement) 1960 : HTMLEditUtils::GetFirstLeafContent( 1961 *mOtherBlockElement, {LeafNodeType::OnlyEditableLeafNode}, 1962 BlockInlineCheck::Unused, mOtherBlockElement); 1963 } 1964 1965 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: 1966 ComputeRangeToDeleteLineBreak(const HTMLEditor& aHTMLEditor, 1967 nsRange& aRangeToDelete, 1968 const Element& aEditingHost, 1969 ComputeRangeFor aComputeRangeFor) const { 1970 // FIXME: Scan invisible leading white-spaces after the <br>. 1971 MOZ_ASSERT_IF(mMode == Mode::DeleteBRElement, mBRElement); 1972 MOZ_ASSERT_IF(mMode == Mode::DeletePrecedingBRElementOfBlock, mBRElement); 1973 MOZ_ASSERT_IF(mMode == Mode::DeletePrecedingPreformattedLineBreak, 1974 mPreformattedLineBreak.IsSetAndValid()); 1975 MOZ_ASSERT_IF(mMode == Mode::DeletePrecedingPreformattedLineBreak, 1976 mPreformattedLineBreak.IsCharPreformattedNewLine()); 1977 MOZ_ASSERT_IF(aComputeRangeFor == ComputeRangeFor::GetTargetRanges, 1978 aRangeToDelete.IsPositioned()); 1979 1980 // If we're computing for beforeinput.getTargetRanges() and the inputType 1981 // is not a simple deletion like replacing selected content with new 1982 // content, the range should end at the original end boundary of the given 1983 // range. 1984 const bool preserveEndBoundary = 1985 (mMode == Mode::DeletePrecedingBRElementOfBlock || 1986 mMode == Mode::DeletePrecedingPreformattedLineBreak) && 1987 aComputeRangeFor == ComputeRangeFor::GetTargetRanges && 1988 !MayEditActionDeleteAroundCollapsedSelection(aHTMLEditor.GetEditAction()); 1989 1990 if (mMode != Mode::DeletePrecedingPreformattedLineBreak) { 1991 Element* const mostDistantInlineAncestor = 1992 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 1993 *mBRElement, BlockInlineCheck::UseComputedDisplayOutsideStyle, 1994 &aEditingHost); 1995 if (preserveEndBoundary) { 1996 // FIXME: If the range ends at end of an inline element, we may need to 1997 // extend the range. 1998 IgnoredErrorResult error; 1999 aRangeToDelete.SetStart(EditorRawDOMPoint(mostDistantInlineAncestor 2000 ? mostDistantInlineAncestor 2001 : mBRElement) 2002 .ToRawRangeBoundary(), 2003 error); 2004 NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SetStart() failed"); 2005 MOZ_ASSERT_IF(!error.Failed(), !aRangeToDelete.Collapsed()); 2006 return error.StealNSResult(); 2007 } 2008 IgnoredErrorResult error; 2009 aRangeToDelete.SelectNode( 2010 mostDistantInlineAncestor ? *mostDistantInlineAncestor : *mBRElement, 2011 error); 2012 NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SelectNode() failed"); 2013 return error.StealNSResult(); 2014 } 2015 2016 Element* const mostDistantInlineAncestor = 2017 mPreformattedLineBreak.ContainerAs<Text>()->TextDataLength() == 1 2018 ? HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 2019 *mPreformattedLineBreak.ContainerAs<Text>(), 2020 BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost) 2021 : nullptr; 2022 2023 if (!mostDistantInlineAncestor) { 2024 if (preserveEndBoundary) { 2025 // FIXME: If the range ends at end of an inline element, we may need to 2026 // extend the range. 2027 IgnoredErrorResult error; 2028 aRangeToDelete.SetStart(mPreformattedLineBreak.ToRawRangeBoundary(), 2029 error); 2030 MOZ_ASSERT_IF(!error.Failed(), !aRangeToDelete.Collapsed()); 2031 NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SetStart() failed"); 2032 return error.StealNSResult(); 2033 } 2034 nsresult rv = aRangeToDelete.SetStartAndEnd( 2035 mPreformattedLineBreak.ToRawRangeBoundary(), 2036 mPreformattedLineBreak.NextPoint().ToRawRangeBoundary()); 2037 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed"); 2038 return rv; 2039 } 2040 2041 if (preserveEndBoundary) { 2042 // FIXME: If the range ends at end of an inline element, we may need to 2043 // extend the range. 2044 IgnoredErrorResult error; 2045 aRangeToDelete.SetStart( 2046 EditorRawDOMPoint(mostDistantInlineAncestor).ToRawRangeBoundary(), 2047 error); 2048 MOZ_ASSERT_IF(!error.Failed(), !aRangeToDelete.Collapsed()); 2049 NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SetStart() failed"); 2050 return error.StealNSResult(); 2051 } 2052 2053 IgnoredErrorResult error; 2054 aRangeToDelete.SelectNode(*mostDistantInlineAncestor, error); 2055 NS_WARNING_ASSERTION(!error.Failed(), "nsRange::SelectNode() failed"); 2056 return error.StealNSResult(); 2057 } 2058 2059 Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: 2060 AutoBlockElementsJoiner::HandleDeleteLineBreak( 2061 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 2062 const EditorDOMPoint& aCaretPoint, const Element& aEditingHost) { 2063 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 2064 MOZ_ASSERT(mBRElement || mPreformattedLineBreak.IsSet()); 2065 2066 // If we're deleting selection (not replacing with new content), we should 2067 // put caret to end of preceding text node if there is. Then, users can type 2068 // text in it like the other browsers. 2069 EditorDOMPoint pointToPutCaret = [&]() { 2070 // but when we're deleting a preceding line break of current block, we 2071 // should keep the caret position in the current block. 2072 if (mMode == Mode::DeletePrecedingBRElementOfBlock || 2073 mMode == Mode::DeletePrecedingPreformattedLineBreak) { 2074 return aCaretPoint; 2075 } 2076 if (!MayEditActionDeleteAroundCollapsedSelection( 2077 aHTMLEditor.GetEditAction())) { 2078 return EditorDOMPoint(); 2079 } 2080 const WSRunScanner scanner({WSRunScanner::Option::OnlyEditableNodes}, 2081 EditorRawDOMPoint(mBRElement)); 2082 const WSScanResult maybePreviousText = 2083 scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom( 2084 EditorRawDOMPoint(mBRElement)); 2085 if (maybePreviousText.IsContentEditable() && 2086 maybePreviousText.InVisibleOrCollapsibleCharacters() && 2087 !HTMLEditor::GetLinkElement(maybePreviousText.TextPtr())) { 2088 return maybePreviousText.PointAfterReachedContent<EditorDOMPoint>(); 2089 } 2090 const WSScanResult maybeNextText = 2091 scanner.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom( 2092 EditorRawDOMPoint::After(*mBRElement)); 2093 if (maybeNextText.IsContentEditable() && 2094 maybeNextText.InVisibleOrCollapsibleCharacters()) { 2095 return maybeNextText.PointAtReachedContent<EditorDOMPoint>(); 2096 } 2097 return EditorDOMPoint(); 2098 }(); 2099 2100 RefPtr<nsRange> rangeToDelete = 2101 nsRange::Create(const_cast<Element*>(&aEditingHost)); 2102 MOZ_ASSERT(rangeToDelete); 2103 nsresult rv = 2104 ComputeRangeToDeleteLineBreak(aHTMLEditor, *rangeToDelete, aEditingHost, 2105 ComputeRangeFor::ToDeleteTheRange); 2106 if (NS_FAILED(rv)) { 2107 NS_WARNING( 2108 "AutoBlockElementsJoiner::ComputeRangeToDeleteLineBreak() failed"); 2109 return Err(rv); 2110 } 2111 Result<EditActionResult, nsresult> result = HandleDeleteNonCollapsedRange( 2112 aHTMLEditor, aDirectionAndAmount, nsIEditor::eNoStrip, *rangeToDelete, 2113 SelectionWasCollapsed::Yes, aEditingHost); 2114 if (MOZ_UNLIKELY(result.isErr())) { 2115 NS_WARNING( 2116 "AutoBlockElementsJoiner::HandleDeleteNonCollapsedRange() failed"); 2117 return result; 2118 } 2119 2120 if (mLeftContent && mRightContent && 2121 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent) != 2122 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent)) { 2123 return EditActionResult::HandledResult(); 2124 } 2125 2126 // Put selection at edge of block and we are done. 2127 if (NS_WARN_IF(mMode == Mode::DeleteBRElement && !mLeafContentInOtherBlock)) { 2128 // XXX This must be odd case. The other block can be empty. 2129 return Err(NS_ERROR_FAILURE); 2130 } 2131 2132 if ((mMode == Mode::DeletePrecedingBRElementOfBlock || 2133 mMode == Mode::DeletePrecedingPreformattedLineBreak) && 2134 pointToPutCaret.IsSetAndValidInComposedDoc()) { 2135 // If we're deleting only the preceding lines of a block, we should 2136 // normalize the white-spaces at start of the block for compatibility 2137 // with the other browsers. 2138 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(), 2139 &pointToPutCaret); 2140 Result<EditorDOMPoint, nsresult> atFirstVisibleThingOrError = 2141 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter( 2142 aHTMLEditor, pointToPutCaret, {}); 2143 if (MOZ_UNLIKELY(atFirstVisibleThingOrError.isErr())) { 2144 NS_WARNING( 2145 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() failed"); 2146 return atFirstVisibleThingOrError.propagateErr(); 2147 } 2148 trackPointToPutCaret.FlushAndStopTracking(); 2149 if (NS_WARN_IF(!pointToPutCaret.IsSetAndValidInComposedDoc())) { 2150 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2151 } 2152 } 2153 2154 if (pointToPutCaret.IsSet()) { 2155 nsresult rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret); 2156 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2157 return Err(NS_ERROR_EDITOR_DESTROYED); 2158 } 2159 if (mMode == Mode::DeleteBRElement && NS_SUCCEEDED(rv)) { 2160 // If we prefer to use style in the previous line, we should forget 2161 // previous styles since the caret position has all styles which we want 2162 // to use with new content. 2163 if (nsIEditor::DirectionIsBackspace(aDirectionAndAmount)) { 2164 aHTMLEditor.TopLevelEditSubActionDataRef() 2165 .mCachedPendingStyles->Clear(); 2166 } 2167 // And we don't want to keep extending a link at ex-end of the previous 2168 // paragraph. 2169 if (HTMLEditor::GetLinkElement(pointToPutCaret.GetContainer())) { 2170 aHTMLEditor.mPendingStylesToApplyToNewContent 2171 ->ClearLinkAndItsSpecifiedStyle(); 2172 } 2173 } else { 2174 NS_WARNING_ASSERTION( 2175 NS_SUCCEEDED(rv), 2176 "EditorBase::CollapseSelectionTo() failed, but ignored"); 2177 } 2178 return EditActionResult::HandledResult(); 2179 } 2180 2181 EditorRawDOMPoint newCaretPosition = 2182 HTMLEditUtils::GetGoodCaretPointFor<EditorRawDOMPoint>( 2183 *mLeafContentInOtherBlock, aDirectionAndAmount); 2184 if (MOZ_UNLIKELY(!newCaretPosition.IsInContentNode())) { 2185 NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed"); 2186 return Err(NS_ERROR_FAILURE); 2187 } 2188 // If we're deleting only a line break and move caret to left block, we 2189 // want to normalize the white-spaces at end of the left block for the 2190 // compatibility with the other browsers. 2191 WSScanResult nextThingOfCaretPoint = 2192 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 2193 {}, newCaretPosition); 2194 if (nextThingOfCaretPoint.ReachedBRElement() || 2195 nextThingOfCaretPoint.ReachedPreformattedLineBreak()) { 2196 nextThingOfCaretPoint = 2197 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 2198 {}, nextThingOfCaretPoint 2199 .PointAfterReachedContent<EditorRawDOMPoint>()); 2200 } 2201 if (nextThingOfCaretPoint.ReachedBlockBoundary()) { 2202 const EditorDOMPoint atBlockBoundary = 2203 nextThingOfCaretPoint.ReachedCurrentBlockBoundary() 2204 ? EditorDOMPoint::AtEndOf(*nextThingOfCaretPoint.ElementPtr()) 2205 : EditorDOMPoint(nextThingOfCaretPoint.ElementPtr()); 2206 Result<EditorDOMPoint, nsresult> afterLastVisibleThingOrError = 2207 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore( 2208 aHTMLEditor, atBlockBoundary, {}); 2209 if (MOZ_UNLIKELY(afterLastVisibleThingOrError.isErr())) { 2210 NS_WARNING( 2211 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore() " 2212 "failed"); 2213 return afterLastVisibleThingOrError.propagateErr(); 2214 } 2215 } 2216 rv = aHTMLEditor.CollapseSelectionTo(newCaretPosition); 2217 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2218 return Err(NS_ERROR_EDITOR_DESTROYED); 2219 } 2220 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2221 "EditorBase::CollapseSelectionTo() failed, but ignored"); 2222 return EditActionResult::HandledResult(); 2223 } 2224 2225 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: 2226 ComputeRangeToDeleteAtOtherBlockBoundary( 2227 const HTMLEditor& aHTMLEditor, 2228 nsIEditor::EDirection aDirectionAndAmount, 2229 const EditorDOMPoint& aCaretPoint, nsRange& aRangeToDelete, 2230 const Element& aEditingHost) const { 2231 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 2232 MOZ_ASSERT(aCaretPoint.IsSetAndValid()); 2233 MOZ_ASSERT(mLeftContent); 2234 MOZ_ASSERT(mRightContent); 2235 2236 if (HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent) != 2237 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent)) { 2238 if (!mDeleteRangesHandlerConst.CanFallbackToDeleteRangeWithTransaction( 2239 aRangeToDelete)) { 2240 nsresult rv = aRangeToDelete.CollapseTo(aCaretPoint.ToRawRangeBoundary()); 2241 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::CollapseTo() failed"); 2242 return rv; 2243 } 2244 nsresult rv = mDeleteRangesHandlerConst 2245 .FallbackToComputeRangeToDeleteRangeWithTransaction( 2246 aHTMLEditor, aRangeToDelete, aEditingHost); 2247 NS_WARNING_ASSERTION( 2248 NS_SUCCEEDED(rv), 2249 "AutoDeleteRangesHandler::" 2250 "FallbackToComputeRangeToDeleteRangeWithTransaction() failed"); 2251 return rv; 2252 } 2253 2254 AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent, 2255 *mRightContent); 2256 Result<bool, nsresult> canJoinThem = 2257 joiner.Prepare(aHTMLEditor, aEditingHost); 2258 if (canJoinThem.isErr()) { 2259 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed"); 2260 return canJoinThem.unwrapErr(); 2261 } 2262 if (canJoinThem.inspect() && joiner.CanJoinBlocks() && 2263 !joiner.ShouldDeleteLeafContentInstead()) { 2264 nsresult rv = 2265 joiner.ComputeRangeToDelete(aHTMLEditor, aCaretPoint, aRangeToDelete); 2266 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2267 "AutoInclusiveAncestorBlockElementsJoiner::" 2268 "ComputeRangeToDelete() failed"); 2269 return rv; 2270 } 2271 2272 // If AutoInclusiveAncestorBlockElementsJoiner didn't handle it and it's not 2273 // canceled, user may want to modify the start leaf node or the last leaf 2274 // node of the block. 2275 if (mLeafContentInOtherBlock == aCaretPoint.GetContainer()) { 2276 return NS_OK; 2277 } 2278 2279 AutoHideSelectionChanges hideSelectionChanges(aHTMLEditor.SelectionRef()); 2280 2281 // If it's ignored, it didn't modify the DOM tree. In this case, user must 2282 // want to delete nearest leaf node in the other block element. 2283 // TODO: We need to consider this before calling ComputeRangesToDelete() for 2284 // computing the deleting range. 2285 EditorRawDOMPoint newCaretPoint = 2286 aDirectionAndAmount == nsIEditor::ePrevious 2287 ? EditorRawDOMPoint::AtEndOf(*mLeafContentInOtherBlock) 2288 : EditorRawDOMPoint(mLeafContentInOtherBlock, 0); 2289 // If new caret position is same as current caret position, we can do 2290 // nothing anymore. 2291 if (aRangeToDelete.Collapsed() && 2292 aRangeToDelete.EndRef() == newCaretPoint.ToRawRangeBoundary()) { 2293 return NS_OK; 2294 } 2295 // TODO: Stop modifying the `Selection` for computing the target ranges. 2296 nsresult rv = aHTMLEditor.CollapseSelectionTo(newCaretPoint); 2297 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 2298 NS_WARNING( 2299 "EditorBase::CollapseSelectionTo() caused destroying the editor"); 2300 return NS_ERROR_EDITOR_DESTROYED; 2301 } 2302 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2303 "EditorBase::CollapseSelectionTo() failed"); 2304 if (NS_SUCCEEDED(rv)) { 2305 AutoClonedSelectionRangeArray rangeArray(aHTMLEditor.SelectionRef()); 2306 if (!rangeArray.GetAncestorLimiter()) { 2307 rangeArray.SetAncestorLimiter( 2308 aHTMLEditor.FindSelectionRoot(aEditingHost)); 2309 } 2310 AutoDeleteRangesHandler anotherHandler(mDeleteRangesHandlerConst); 2311 rv = anotherHandler.ComputeRangesToDelete(aHTMLEditor, aDirectionAndAmount, 2312 rangeArray, aEditingHost); 2313 if (NS_SUCCEEDED(rv)) { 2314 if (MOZ_LIKELY(!rangeArray.Ranges().IsEmpty())) { 2315 MOZ_ASSERT(rangeArray.Ranges().Length() == 1); 2316 aRangeToDelete.SetStartAndEnd(rangeArray.FirstRangeRef()->StartRef(), 2317 rangeArray.FirstRangeRef()->EndRef()); 2318 } else { 2319 NS_WARNING( 2320 "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() " 2321 "returned no range"); 2322 rv = NS_ERROR_FAILURE; 2323 } 2324 } else { 2325 NS_WARNING( 2326 "Recursive AutoDeleteRangesHandler::ComputeRangesToDelete() failed"); 2327 } 2328 } 2329 // Restore selection. 2330 nsresult rvCollapsingSelectionTo = 2331 aHTMLEditor.CollapseSelectionTo(aCaretPoint); 2332 if (MOZ_UNLIKELY(rvCollapsingSelectionTo == NS_ERROR_EDITOR_DESTROYED)) { 2333 NS_WARNING( 2334 "EditorBase::CollapseSelectionTo() caused destroying the editor"); 2335 return NS_ERROR_EDITOR_DESTROYED; 2336 } 2337 NS_WARNING_ASSERTION( 2338 NS_SUCCEEDED(rvCollapsingSelectionTo), 2339 "EditorBase::CollapseSelectionTo() failed to restore caret position"); 2340 return NS_SUCCEEDED(rv) && NS_SUCCEEDED(rvCollapsingSelectionTo) 2341 ? NS_OK 2342 : NS_ERROR_FAILURE; 2343 } 2344 2345 Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: 2346 AutoBlockElementsJoiner::HandleDeleteAtOtherBlockBoundary( 2347 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 2348 nsIEditor::EStripWrappers aStripWrappers, 2349 const EditorDOMPoint& aCaretPoint, nsRange& aRangeToDelete, 2350 const Element& aEditingHost) { 2351 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 2352 MOZ_ASSERT(aCaretPoint.IsSetAndValid()); 2353 MOZ_ASSERT(mDeleteRangesHandler); 2354 MOZ_ASSERT(mLeftContent); 2355 MOZ_ASSERT(mRightContent); 2356 2357 if (HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent) != 2358 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent)) { 2359 // If we have not deleted `<br>` element and are not called recursively, 2360 // we should call `DeleteRangesWithTransaction()` here. 2361 if (!mDeleteRangesHandler->CanFallbackToDeleteRangeWithTransaction( 2362 aRangeToDelete)) { 2363 return EditActionResult::IgnoredResult(); 2364 } 2365 Result<CaretPoint, nsresult> caretPointOrError = 2366 mDeleteRangesHandler->FallbackToDeleteRangeWithTransaction( 2367 aHTMLEditor, aRangeToDelete); 2368 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 2369 NS_WARNING( 2370 "AutoDeleteRangesHandler::FallbackToDeleteRangesWithTransaction() " 2371 "failed"); 2372 return caretPointOrError.propagateErr(); 2373 } 2374 nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo( 2375 aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, 2376 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 2377 SuggestCaret::AndIgnoreTrivialError}); 2378 if (NS_FAILED(rv)) { 2379 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 2380 return Err(rv); 2381 } 2382 NS_WARNING_ASSERTION( 2383 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 2384 "CaretPoint::SuggestCaretPointTo() failed, but ignored"); 2385 // Don't return "ignored" to avoid to fall it back to delete ranges 2386 // recursively. 2387 return EditActionResult::HandledResult(); 2388 } 2389 2390 // Else we are joining content to block 2391 AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent, 2392 *mRightContent); 2393 Result<bool, nsresult> canJoinThem = 2394 joiner.Prepare(aHTMLEditor, aEditingHost); 2395 if (MOZ_UNLIKELY(canJoinThem.isErr())) { 2396 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed"); 2397 return canJoinThem.propagateErr(); 2398 } 2399 2400 if (!canJoinThem.inspect() || !joiner.CanJoinBlocks()) { 2401 nsresult rv = aHTMLEditor.CollapseSelectionTo(aCaretPoint); 2402 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2403 return Err(NS_ERROR_EDITOR_DESTROYED); 2404 } 2405 NS_WARNING_ASSERTION( 2406 NS_SUCCEEDED(rv), 2407 "EditorBase::CollapseSelectionTo() failed, but ignored"); 2408 return !canJoinThem.inspect() ? EditActionResult::CanceledResult() 2409 : EditActionResult::IgnoredResult(); 2410 } 2411 2412 EditorDOMPoint pointToPutCaret(aCaretPoint); 2413 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(), 2414 &pointToPutCaret); 2415 Result<DeleteRangeResult, nsresult> moveFirstLineResult = 2416 joiner.Run(aHTMLEditor, aEditingHost); 2417 if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) { 2418 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed"); 2419 return moveFirstLineResult.propagateErr(); 2420 } 2421 DeleteRangeResult unwrappedMoveFirstLineResult = moveFirstLineResult.unwrap(); 2422 #ifdef DEBUG 2423 if (joiner.ShouldDeleteLeafContentInstead()) { 2424 NS_ASSERTION(unwrappedMoveFirstLineResult.Ignored(), 2425 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` " 2426 "returning ignored, but returned not ignored"); 2427 } else { 2428 NS_ASSERTION(!unwrappedMoveFirstLineResult.Ignored(), 2429 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` " 2430 "returning handled, but returned ignored"); 2431 } 2432 #endif // #ifdef DEBUG 2433 // Even if AutoInclusiveAncestorBlockElementsJoiner claims "ignored", 2434 // invisible white-spaces may have been normalized. So, 2435 // mLeafContentInOtherBlock could've been removed from the DOM. Therefore, we 2436 // need to update mLeafContentInOtherBlock. 2437 if (mLeafContentInOtherBlock && 2438 !mLeafContentInOtherBlock->IsInComposedDoc()) { 2439 mLeafContentInOtherBlock = 2440 ComputeLeafContentInOtherBlockElement(aDirectionAndAmount); 2441 } 2442 // If we're deleting selection (not replacing with new content) and 2443 // AutoInclusiveAncestorBlockElementsJoiner computed new caret position, 2444 // we should use it. Otherwise, we should keep the our traditional behavior. 2445 if (unwrappedMoveFirstLineResult.Handled() && 2446 unwrappedMoveFirstLineResult.HasCaretPointSuggestion() && 2447 MayEditActionDeleteAroundCollapsedSelection( 2448 aHTMLEditor.GetEditAction())) { 2449 EditorDOMPoint pointToPutCaret = 2450 unwrappedMoveFirstLineResult.UnwrapCaretPoint(); 2451 nsresult rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret); 2452 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2453 return Err(NS_ERROR_EDITOR_DESTROYED); 2454 } 2455 if (NS_FAILED(rv)) { 2456 NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored"); 2457 return EditActionResult::HandledResult(); 2458 } 2459 // If we prefer to use style in the previous line, we should forget 2460 // previous styles since the caret position has all styles which we want 2461 // to use with new content. 2462 if (nsIEditor::DirectionIsBackspace(aDirectionAndAmount)) { 2463 aHTMLEditor.TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear(); 2464 } 2465 // And we don't want to keep extending a link at ex-end of the previous 2466 // paragraph. 2467 if (HTMLEditor::GetLinkElement(pointToPutCaret.GetContainer())) { 2468 aHTMLEditor.mPendingStylesToApplyToNewContent 2469 ->ClearLinkAndItsSpecifiedStyle(); 2470 } 2471 return EditActionResult::HandledResult(); 2472 } 2473 trackPointToPutCaret.FlushAndStopTracking(); 2474 unwrappedMoveFirstLineResult.IgnoreCaretPointSuggestion(); 2475 2476 // If AutoInclusiveAncestorBlockElementsJoiner didn't handle it and it's not 2477 // canceled, user may want to modify the start leaf node or the last leaf 2478 // node of the block. 2479 if (unwrappedMoveFirstLineResult.Ignored() && mLeafContentInOtherBlock && 2480 mLeafContentInOtherBlock != aCaretPoint.GetContainer()) { 2481 // If it's ignored, it didn't modify the DOM tree. In this case, user 2482 // must want to delete nearest leaf node in the other block element. 2483 // TODO: We need to consider this before calling Run() for computing the 2484 // deleting range. 2485 EditorRawDOMPoint newCaretPoint = 2486 aDirectionAndAmount == nsIEditor::ePrevious 2487 ? EditorRawDOMPoint::AtEndOf(*mLeafContentInOtherBlock) 2488 : EditorRawDOMPoint(mLeafContentInOtherBlock, 0); 2489 // If new caret position is same as current caret position, we can do 2490 // nothing anymore. 2491 if (aRangeToDelete.Collapsed() && 2492 aRangeToDelete.EndRef() == newCaretPoint.ToRawRangeBoundary()) { 2493 return EditActionResult::CanceledResult(); 2494 } 2495 nsresult rv = aHTMLEditor.CollapseSelectionTo(newCaretPoint); 2496 if (NS_FAILED(rv)) { 2497 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 2498 return Err(rv); 2499 } 2500 AutoClonedSelectionRangeArray rangesToDelete(aHTMLEditor.SelectionRef()); 2501 if (!rangesToDelete.GetAncestorLimiter()) { 2502 rangesToDelete.SetAncestorLimiter( 2503 aHTMLEditor.FindSelectionRoot(aEditingHost)); 2504 } 2505 AutoDeleteRangesHandler anotherHandler(mDeleteRangesHandler); 2506 Result<EditActionResult, nsresult> fallbackResult = 2507 anotherHandler.Run(aHTMLEditor, aDirectionAndAmount, aStripWrappers, 2508 rangesToDelete, aEditingHost); 2509 if (MOZ_UNLIKELY(fallbackResult.isErr())) { 2510 NS_WARNING("Recursive AutoDeleteRangesHandler::Run() failed"); 2511 return fallbackResult; 2512 } 2513 return fallbackResult; 2514 } 2515 // Otherwise, we must have deleted the selection as user expected. 2516 nsresult rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret); 2517 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2518 return Err(NS_ERROR_EDITOR_DESTROYED); 2519 } 2520 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2521 "EditorBase::CollapseSelectionTo() failed, but ignored"); 2522 return unwrappedMoveFirstLineResult.Handled() 2523 ? EditActionResult::HandledResult() 2524 : EditActionResult::IgnoredResult(); 2525 } 2526 2527 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: 2528 PrepareToDeleteAtCurrentBlockBoundary( 2529 const HTMLEditor& aHTMLEditor, 2530 nsIEditor::EDirection aDirectionAndAmount, 2531 Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint, 2532 const Element& aEditingHost) { 2533 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 2534 2535 // At edge of our block. Look beside it and see if we can join to an 2536 // adjacent block 2537 mMode = Mode::JoinCurrentBlock; 2538 2539 // Don't break the basic structure of the HTML document. 2540 if (aCurrentBlockElement.IsAnyOfHTMLElements(nsGkAtoms::html, nsGkAtoms::head, 2541 nsGkAtoms::body)) { 2542 return false; 2543 } 2544 2545 // Make sure it's not a table element. If so, cancel the operation 2546 // (translation: users cannot backspace or delete across table cells) 2547 if (HTMLEditUtils::IsAnyTableElementExceptColumnElement( 2548 aCurrentBlockElement)) { 2549 return false; 2550 } 2551 2552 auto ScanJoinTarget = [&]() -> nsIContent* { 2553 nsIContent* targetContent = 2554 aDirectionAndAmount == nsIEditor::ePrevious 2555 ? HTMLEditUtils::GetPreviousContent( 2556 aCurrentBlockElement, {WalkTreeOption::IgnoreNonEditableNode}, 2557 BlockInlineCheck::Unused, &aEditingHost) 2558 : HTMLEditUtils::GetNextContent( 2559 aCurrentBlockElement, {WalkTreeOption::IgnoreNonEditableNode}, 2560 BlockInlineCheck::Unused, &aEditingHost); 2561 // If found content is an invisible text node, let's scan visible things. 2562 auto IsIgnorableDataNode = [](nsIContent* aContent) { 2563 return aContent && HTMLEditUtils::IsRemovableNode(*aContent) && 2564 ((aContent->IsText() && 2565 aContent->AsText()->TextIsOnlyWhitespace() && 2566 !HTMLEditUtils::IsVisibleTextNode(*aContent->AsText())) || 2567 (aContent->IsCharacterData() && !aContent->IsText())); 2568 }; 2569 if (!IsIgnorableDataNode(targetContent)) { 2570 return targetContent; 2571 } 2572 MOZ_ASSERT(mSkippedInvisibleContents.IsEmpty()); 2573 for (nsIContent* adjacentContent = 2574 aDirectionAndAmount == nsIEditor::ePrevious 2575 ? HTMLEditUtils::GetPreviousContent( 2576 *targetContent, {WalkTreeOption::StopAtBlockBoundary}, 2577 BlockInlineCheck::UseComputedDisplayOutsideStyle, 2578 &aEditingHost) 2579 : HTMLEditUtils::GetNextContent( 2580 *targetContent, {WalkTreeOption::StopAtBlockBoundary}, 2581 BlockInlineCheck::UseComputedDisplayOutsideStyle, 2582 &aEditingHost); 2583 adjacentContent; 2584 adjacentContent = 2585 aDirectionAndAmount == nsIEditor::ePrevious 2586 ? HTMLEditUtils::GetPreviousContent( 2587 *adjacentContent, {WalkTreeOption::StopAtBlockBoundary}, 2588 BlockInlineCheck::UseComputedDisplayOutsideStyle, 2589 &aEditingHost) 2590 : HTMLEditUtils::GetNextContent( 2591 *adjacentContent, {WalkTreeOption::StopAtBlockBoundary}, 2592 BlockInlineCheck::UseComputedDisplayOutsideStyle, 2593 &aEditingHost)) { 2594 // If non-editable element is found, we should not skip it to avoid 2595 // joining too far nodes. 2596 if (!HTMLEditUtils::IsSimplyEditableNode(*adjacentContent)) { 2597 break; 2598 } 2599 // If block element is found, we should join last leaf content in it. 2600 if (HTMLEditUtils::IsBlockElement( 2601 *adjacentContent, 2602 BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 2603 nsIContent* leafContent = 2604 aDirectionAndAmount == nsIEditor::ePrevious 2605 ? HTMLEditUtils::GetLastLeafContent( 2606 *adjacentContent, {LeafNodeType::OnlyEditableLeafNode}) 2607 : HTMLEditUtils::GetFirstLeafContent( 2608 *adjacentContent, {LeafNodeType::OnlyEditableLeafNode}); 2609 mSkippedInvisibleContents.AppendElement(*targetContent); 2610 return leafContent ? leafContent : adjacentContent; 2611 } 2612 // Only when the found node is an invisible text node or a non-text data 2613 // node, we should keep scanning. 2614 if (IsIgnorableDataNode(adjacentContent)) { 2615 mSkippedInvisibleContents.AppendElement(*targetContent); 2616 targetContent = adjacentContent; 2617 continue; 2618 } 2619 // Otherwise, we find a visible things. We should join with last found 2620 // invisible text node. 2621 break; 2622 } 2623 return targetContent; 2624 }; 2625 2626 if (aDirectionAndAmount == nsIEditor::ePrevious) { 2627 const WSScanResult prevVisibleThing = [&]() { 2628 // When Backspace at start of a block, we need to delete only a preceding 2629 // <br> element if there is. 2630 const Result<Element*, nsresult> 2631 inclusiveAncestorOfRightChildBlockOrError = AutoBlockElementsJoiner:: 2632 GetMostDistantBlockAncestorIfPointIsStartAtBlock(aCaretPoint, 2633 aEditingHost); 2634 if (NS_WARN_IF(inclusiveAncestorOfRightChildBlockOrError.isErr()) || 2635 !inclusiveAncestorOfRightChildBlockOrError.inspect()) { 2636 return WSScanResult::Error(); 2637 } 2638 const WSScanResult prevVisibleThingBeforeCurrentBlock = 2639 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 2640 {WSRunScanner::Option::OnlyEditableNodes}, 2641 EditorRawDOMPoint( 2642 inclusiveAncestorOfRightChildBlockOrError.inspect())); 2643 if (!prevVisibleThingBeforeCurrentBlock.ReachedBRElement() && 2644 !prevVisibleThingBeforeCurrentBlock.ReachedPreformattedLineBreak()) { 2645 return WSScanResult::Error(); 2646 } 2647 // There is a preceding line break, but it may be invisible. Then, users 2648 // want to delete its preceding content not only the line break. 2649 // Therefore, let's check whether the line break follows another line 2650 // break or a block boundary. In these cases, the line break causes an 2651 // empty line which users may want to delete. 2652 const auto atPrecedingLineBreak = 2653 prevVisibleThingBeforeCurrentBlock 2654 .PointAtReachedContent<EditorRawDOMPoint>(); 2655 MOZ_ASSERT(atPrecedingLineBreak.IsSet()); 2656 const WSScanResult prevVisibleThingBeforeLineBreak = 2657 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 2658 {WSRunScanner::Option::OnlyEditableNodes}, atPrecedingLineBreak); 2659 if (prevVisibleThingBeforeLineBreak.ReachedBRElement() || 2660 prevVisibleThingBeforeLineBreak.ReachedPreformattedLineBreak() || 2661 prevVisibleThingBeforeLineBreak.ReachedCurrentBlockBoundary()) { 2662 // Target the latter line break for things simpler. It's easier to 2663 // compute the target range. 2664 MOZ_ASSERT_IF( 2665 prevVisibleThingBeforeCurrentBlock.ReachedPreformattedLineBreak() && 2666 prevVisibleThingBeforeLineBreak.ReachedPreformattedLineBreak(), 2667 prevVisibleThingBeforeCurrentBlock 2668 .PointAtReachedContent<EditorRawDOMPoint>() != 2669 prevVisibleThingBeforeLineBreak 2670 .PointAtReachedContent<EditorRawDOMPoint>()); 2671 return prevVisibleThingBeforeCurrentBlock; 2672 } 2673 return WSScanResult::Error(); 2674 }(); 2675 2676 // If previous visible thing is a <br>, we should just delete it without 2677 // unwrapping the first line of the right child block. Note that the <br> 2678 // is always treated as invisible by HTMLEditUtils because it's immediately 2679 // preceding <br> of the block boundary. However, deleting it is fine 2680 // because the above checks whether it causes empty line or not. 2681 if (prevVisibleThing.ReachedBRElement()) { 2682 mMode = Mode::DeletePrecedingBRElementOfBlock; 2683 mBRElement = prevVisibleThing.BRElementPtr(); 2684 return true; 2685 } 2686 2687 // Same for a preformatted line break. 2688 if (prevVisibleThing.ReachedPreformattedLineBreak()) { 2689 mMode = Mode::DeletePrecedingPreformattedLineBreak; 2690 mPreformattedLineBreak = 2691 prevVisibleThing.PointAtReachedContent<EditorRawDOMPoint>() 2692 .AsInText(); 2693 return true; 2694 } 2695 2696 mLeftContent = ScanJoinTarget(); 2697 mRightContent = aCaretPoint.GetContainerAs<nsIContent>(); 2698 } else { 2699 mRightContent = ScanJoinTarget(); 2700 mLeftContent = aCaretPoint.GetContainerAs<nsIContent>(); 2701 } 2702 2703 // Nothing to join 2704 if (!mLeftContent || !mRightContent) { 2705 return false; 2706 } 2707 2708 // Don't cross table boundaries. 2709 return HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mLeftContent) == 2710 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*mRightContent); 2711 } 2712 2713 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: 2714 ComputeRangeToDeleteAtCurrentBlockBoundary( 2715 const HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCaretPoint, 2716 nsRange& aRangeToDelete, const Element& aEditingHost) const { 2717 MOZ_ASSERT(mLeftContent); 2718 MOZ_ASSERT(mRightContent); 2719 2720 AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent, 2721 *mRightContent); 2722 Result<bool, nsresult> canJoinThem = 2723 joiner.Prepare(aHTMLEditor, aEditingHost); 2724 if (canJoinThem.isErr()) { 2725 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed"); 2726 return canJoinThem.unwrapErr(); 2727 } 2728 if (canJoinThem.inspect()) { 2729 nsresult rv = 2730 joiner.ComputeRangeToDelete(aHTMLEditor, aCaretPoint, aRangeToDelete); 2731 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2732 "AutoInclusiveAncestorBlockElementsJoiner::" 2733 "ComputeRangesToDelete() failed"); 2734 return rv; 2735 } 2736 2737 // In this case, nothing will be deleted so that the affected range should 2738 // be collapsed. 2739 nsresult rv = aRangeToDelete.CollapseTo(aCaretPoint.ToRawRangeBoundary()); 2740 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::CollapseTo() failed"); 2741 return rv; 2742 } 2743 2744 Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: 2745 AutoBlockElementsJoiner::HandleDeleteAtCurrentBlockBoundary( 2746 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 2747 const EditorDOMPoint& aCaretPoint, const Element& aEditingHost) { 2748 MOZ_ASSERT(mLeftContent); 2749 MOZ_ASSERT(mRightContent); 2750 2751 AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent, 2752 *mRightContent); 2753 Result<bool, nsresult> canJoinThem = 2754 joiner.Prepare(aHTMLEditor, aEditingHost); 2755 if (MOZ_UNLIKELY(canJoinThem.isErr())) { 2756 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed"); 2757 return Err(canJoinThem.unwrapErr()); 2758 } 2759 2760 if (!canJoinThem.inspect() || !joiner.CanJoinBlocks()) { 2761 nsresult rv = aHTMLEditor.CollapseSelectionTo(aCaretPoint); 2762 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2763 return Err(NS_ERROR_EDITOR_DESTROYED); 2764 } 2765 NS_WARNING_ASSERTION( 2766 NS_SUCCEEDED(rv), 2767 "EditorBase::CollapseSelectionTo() failed, but ignored"); 2768 return !canJoinThem.inspect() ? EditActionResult::CanceledResult() 2769 : EditActionResult::HandledResult(); 2770 } 2771 2772 EditorDOMPoint pointToPutCaret(aCaretPoint); 2773 AutoTrackDOMPoint tracker(aHTMLEditor.RangeUpdaterRef(), &pointToPutCaret); 2774 Result<DeleteRangeResult, nsresult> moveFirstLineResult = 2775 joiner.Run(aHTMLEditor, aEditingHost); 2776 if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) { 2777 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed"); 2778 return moveFirstLineResult.propagateErr(); 2779 } 2780 DeleteRangeResult unwrappedMoveFirstLineResult = moveFirstLineResult.unwrap(); 2781 MOZ_ASSERT_IF( 2782 unwrappedMoveFirstLineResult.HasCaretPointSuggestion(), 2783 HTMLEditUtils::IsSimplyEditableNode( 2784 *unwrappedMoveFirstLineResult.CaretPointRef().GetContainer())); 2785 #ifdef DEBUG 2786 if (joiner.ShouldDeleteLeafContentInstead()) { 2787 NS_ASSERTION(unwrappedMoveFirstLineResult.Ignored(), 2788 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` " 2789 "returning ignored, but returned not ignored"); 2790 } else { 2791 NS_ASSERTION(!unwrappedMoveFirstLineResult.Ignored(), 2792 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` " 2793 "returning handled, but returned ignored"); 2794 } 2795 #endif // #ifdef DEBUG 2796 2797 // Cleaning up invisible nodes which are skipped at scanning mLeftContent or 2798 // mRightContent. 2799 { 2800 AutoTrackDOMDeleteRangeResult trackMoveFirstLineResult( 2801 aHTMLEditor.RangeUpdaterRef(), &unwrappedMoveFirstLineResult); 2802 for (const OwningNonNull<nsIContent>& content : mSkippedInvisibleContents) { 2803 nsresult rv = 2804 aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(content)); 2805 if (NS_FAILED(rv)) { 2806 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 2807 return Err(rv); 2808 } 2809 } 2810 mSkippedInvisibleContents.Clear(); 2811 trackMoveFirstLineResult.FlushAndStopTracking(); 2812 if (unwrappedMoveFirstLineResult.HasCaretPointSuggestion() && 2813 NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode( 2814 *unwrappedMoveFirstLineResult.CaretPointRef().GetContainer()))) { 2815 unwrappedMoveFirstLineResult.ForgetCaretPointSuggestion(); 2816 } 2817 } 2818 2819 // If we're deleting selection (not replacing with new content) and 2820 // AutoInclusiveAncestorBlockElementsJoiner computed new caret position, we 2821 // should use it. Otherwise, we should keep the our traditional behavior. 2822 if (unwrappedMoveFirstLineResult.Handled() && 2823 unwrappedMoveFirstLineResult.HasCaretPointSuggestion() && 2824 MayEditActionDeleteAroundCollapsedSelection( 2825 aHTMLEditor.GetEditAction())) { 2826 EditorDOMPoint pointToPutCaret = 2827 unwrappedMoveFirstLineResult.UnwrapCaretPoint(); 2828 // Don't remove empty inline elements in the plaintext-only mode because 2829 // nobody can restore the style again. 2830 if (pointToPutCaret.IsInContentNodeAndValidInComposedDoc() && 2831 !aEditingHost.IsContentEditablePlainTextOnly() && 2832 MOZ_LIKELY(HTMLEditUtils::IsRemovableFromParentNode( 2833 *pointToPutCaret.ContainerAs<nsIContent>()))) { 2834 AutoTrackDOMPoint trackCaretPoint(aHTMLEditor.RangeUpdaterRef(), 2835 &pointToPutCaret); 2836 Result<CaretPoint, nsresult> caretPointOrError = 2837 aHTMLEditor.DeleteEmptyInclusiveAncestorInlineElements( 2838 MOZ_KnownLive(*pointToPutCaret.ContainerAs<nsIContent>()), 2839 aEditingHost); 2840 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 2841 NS_WARNING( 2842 "HTMLEditor::DeleteEmptyInclusiveAncestorInlineElements() failed"); 2843 return caretPointOrError.propagateErr(); 2844 } 2845 trackCaretPoint.FlushAndStopTracking(); 2846 caretPointOrError.unwrap().MoveCaretPointTo( 2847 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 2848 } 2849 if ((aHTMLEditor.IsMailEditor() || aHTMLEditor.IsPlaintextMailComposer()) && 2850 MOZ_LIKELY(pointToPutCaret.IsInContentNode())) { 2851 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(), 2852 &pointToPutCaret); 2853 nsresult rv = aHTMLEditor.DeleteMostAncestorMailCiteElementIfEmpty( 2854 MOZ_KnownLive(*pointToPutCaret.ContainerAs<nsIContent>())); 2855 if (NS_FAILED(rv)) { 2856 NS_WARNING( 2857 "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed"); 2858 return Err(rv); 2859 } 2860 trackPointToPutCaret.FlushAndStopTracking(); 2861 if (NS_WARN_IF(!pointToPutCaret.IsSetAndValidInComposedDoc())) { 2862 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2863 } 2864 } 2865 if (aHTMLEditor.GetTopLevelEditSubAction() == 2866 EditSubAction::eDeleteSelectedContent && 2867 pointToPutCaret.IsSetAndValidInComposedDoc()) { 2868 AutoTrackDOMPoint trackCaretPoint(aHTMLEditor.RangeUpdaterRef(), 2869 &pointToPutCaret); 2870 Result<CreateLineBreakResult, nsresult> insertPaddingBRElementOrError = 2871 aHTMLEditor.InsertPaddingBRElementIfNeeded( 2872 pointToPutCaret, 2873 aEditingHost.IsContentEditablePlainTextOnly() 2874 ? nsIEditor::eNoStrip 2875 : nsIEditor::eStrip, 2876 aEditingHost); 2877 if (MOZ_UNLIKELY(insertPaddingBRElementOrError.isErr())) { 2878 NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed"); 2879 return insertPaddingBRElementOrError.propagateErr(); 2880 } 2881 insertPaddingBRElementOrError.unwrap().MoveCaretPointTo( 2882 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 2883 } 2884 nsresult rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret); 2885 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2886 return Err(NS_ERROR_EDITOR_DESTROYED); 2887 } 2888 if (NS_FAILED(rv)) { 2889 NS_WARNING("EditorBase::CollapseSelectionTo() failed, but ignored"); 2890 return EditActionResult::HandledResult(); 2891 } 2892 // If we prefer to use style in the previous line, we should forget 2893 // previous styles since the caret position has all styles which we want 2894 // to use with new content. 2895 if (nsIEditor::DirectionIsBackspace(aDirectionAndAmount)) { 2896 aHTMLEditor.TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear(); 2897 } 2898 // And we don't want to keep extending a link at ex-end of the previous 2899 // paragraph. 2900 if (HTMLEditor::GetLinkElement(pointToPutCaret.GetContainer())) { 2901 aHTMLEditor.mPendingStylesToApplyToNewContent 2902 ->ClearLinkAndItsSpecifiedStyle(); 2903 } 2904 return EditActionResult::HandledResult(); 2905 } 2906 unwrappedMoveFirstLineResult.IgnoreCaretPointSuggestion(); 2907 tracker.FlushAndStopTracking(); 2908 nsresult rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret); 2909 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2910 return Err(NS_ERROR_EDITOR_DESTROYED); 2911 } 2912 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2913 "EditorBase::CollapseSelectionTo() failed, but ignored"); 2914 // This should claim that trying to join the block means that 2915 // this handles the action because the caller shouldn't do anything 2916 // anymore in this case. 2917 return EditActionResult::HandledResult(); 2918 } 2919 2920 nsresult 2921 HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteNonCollapsedRanges( 2922 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 2923 AutoClonedSelectionRangeArray& aRangesToDelete, 2924 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, 2925 const Element& aEditingHost) const { 2926 MOZ_ASSERT(!aRangesToDelete.IsCollapsed()); 2927 2928 if (NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->StartRef().IsSet()) || 2929 NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->EndRef().IsSet())) { 2930 return NS_ERROR_FAILURE; 2931 } 2932 2933 if (aRangesToDelete.Ranges().Length() == 1) { 2934 Result<EditorRawDOMRange, nsresult> result = ExtendOrShrinkRangeToDelete( 2935 aHTMLEditor, aRangesToDelete.LimitersAndCaretDataRef(), 2936 EditorRawDOMRange(aRangesToDelete.FirstRangeRef())); 2937 if (MOZ_UNLIKELY(result.isErr())) { 2938 NS_WARNING( 2939 "AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete() failed"); 2940 return NS_ERROR_FAILURE; 2941 } 2942 EditorRawDOMRange newRange(result.unwrap()); 2943 if (MOZ_UNLIKELY(NS_FAILED(aRangesToDelete.FirstRangeRef()->SetStartAndEnd( 2944 newRange.StartRef().ToRawRangeBoundary(), 2945 newRange.EndRef().ToRawRangeBoundary())))) { 2946 NS_WARNING("nsRange::SetStartAndEnd() failed"); 2947 return NS_ERROR_FAILURE; 2948 } 2949 if (MOZ_UNLIKELY( 2950 NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->IsPositioned()))) { 2951 return NS_ERROR_FAILURE; 2952 } 2953 if (NS_WARN_IF(aRangesToDelete.FirstRangeRef()->Collapsed())) { 2954 return NS_OK; // Hmm, there is nothing to delete...? 2955 } 2956 } 2957 2958 if (!aHTMLEditor.IsPlaintextMailComposer()) { 2959 EditorDOMRange firstRange(aRangesToDelete.FirstRangeRef()); 2960 EditorDOMRange extendedRange = 2961 WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries( 2962 {WSRunScanner::Option::OnlyEditableNodes}, 2963 EditorDOMRange(aRangesToDelete.FirstRangeRef())); 2964 if (firstRange != extendedRange) { 2965 nsresult rv = aRangesToDelete.FirstRangeRef()->SetStartAndEnd( 2966 extendedRange.StartRef().ToRawRangeBoundary(), 2967 extendedRange.EndRef().ToRawRangeBoundary()); 2968 if (NS_FAILED(rv)) { 2969 NS_WARNING("nsRange::SetStartAndEnd() failed"); 2970 return NS_ERROR_FAILURE; 2971 } 2972 } 2973 } 2974 2975 if (aRangesToDelete.FirstRangeRef()->GetStartContainer() == 2976 aRangesToDelete.FirstRangeRef()->GetEndContainer()) { 2977 if (!aRangesToDelete.FirstRangeRef()->Collapsed()) { 2978 nsresult rv = ComputeRangesToDeleteRangesWithTransaction( 2979 aHTMLEditor, aDirectionAndAmount, aRangesToDelete, aEditingHost); 2980 NS_WARNING_ASSERTION( 2981 NS_SUCCEEDED(rv), 2982 "AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction(" 2983 ") failed"); 2984 return rv; 2985 } 2986 // `DeleteUnnecessaryNodes()` may delete parent elements, but it does not 2987 // affect computing target ranges. Therefore, we don't need to touch 2988 // aRangesToDelete in this case. 2989 return NS_OK; 2990 } 2991 2992 Element* startCiteNode = aHTMLEditor.GetMostDistantAncestorMailCiteElement( 2993 *aRangesToDelete.FirstRangeRef()->GetStartContainer()); 2994 Element* endCiteNode = aHTMLEditor.GetMostDistantAncestorMailCiteElement( 2995 *aRangesToDelete.FirstRangeRef()->GetEndContainer()); 2996 2997 if (startCiteNode && !endCiteNode) { 2998 aDirectionAndAmount = nsIEditor::eNext; 2999 } else if (!startCiteNode && endCiteNode) { 3000 aDirectionAndAmount = nsIEditor::ePrevious; 3001 } 3002 3003 for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { 3004 if (MOZ_UNLIKELY(range->Collapsed())) { 3005 continue; 3006 } 3007 AutoBlockElementsJoiner joiner(*this); 3008 if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range, 3009 aEditingHost)) { 3010 return NS_ERROR_FAILURE; 3011 } 3012 nsresult rv = joiner.ComputeRangeToDelete( 3013 aHTMLEditor, aRangesToDelete, aDirectionAndAmount, range, 3014 aSelectionWasCollapsed, aEditingHost); 3015 if (NS_FAILED(rv)) { 3016 NS_WARNING("AutoBlockElementsJoiner::ComputeRangeToDelete() failed"); 3017 return rv; 3018 } 3019 } 3020 return NS_OK; 3021 } 3022 3023 Result<EditActionResult, nsresult> 3024 HTMLEditor::AutoDeleteRangesHandler::HandleDeleteNonCollapsedRanges( 3025 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 3026 nsIEditor::EStripWrappers aStripWrappers, 3027 AutoClonedSelectionRangeArray& aRangesToDelete, 3028 SelectionWasCollapsed aSelectionWasCollapsed, const Element& aEditingHost) { 3029 MOZ_ASSERT(aHTMLEditor.IsTopLevelEditSubActionDataAvailable()); 3030 MOZ_ASSERT(!aRangesToDelete.IsCollapsed()); 3031 3032 if (NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->StartRef().IsSet()) || 3033 NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->EndRef().IsSet())) { 3034 return Err(NS_ERROR_FAILURE); 3035 } 3036 3037 MOZ_ASSERT_IF(aRangesToDelete.Ranges().Length() == 1, 3038 aRangesToDelete.IsFirstRangeEditable(aEditingHost)); 3039 3040 // Else we have a non-collapsed selection. First adjust the selection. 3041 // XXX Why do we extend selection only when there is only one range? 3042 if (aRangesToDelete.Ranges().Length() == 1) { 3043 Result<EditorRawDOMRange, nsresult> result = ExtendOrShrinkRangeToDelete( 3044 aHTMLEditor, aRangesToDelete.LimitersAndCaretDataRef(), 3045 EditorRawDOMRange(aRangesToDelete.FirstRangeRef())); 3046 if (MOZ_UNLIKELY(result.isErr())) { 3047 NS_WARNING( 3048 "AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete() failed"); 3049 return Err(NS_ERROR_FAILURE); 3050 } 3051 EditorRawDOMRange newRange(result.unwrap()); 3052 if (NS_FAILED(aRangesToDelete.FirstRangeRef()->SetStartAndEnd( 3053 newRange.StartRef().ToRawRangeBoundary(), 3054 newRange.EndRef().ToRawRangeBoundary()))) { 3055 NS_WARNING("nsRange::SetStartAndEnd() failed"); 3056 return Err(NS_ERROR_FAILURE); 3057 } 3058 if (NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->IsPositioned())) { 3059 return Err(NS_ERROR_FAILURE); 3060 } 3061 if (NS_WARN_IF(aRangesToDelete.FirstRangeRef()->Collapsed())) { 3062 // Hmm, there is nothing to delete...? 3063 // In this case, the callers want collapsed selection. Therefore, we need 3064 // to change the `Selection` here. 3065 nsresult rv = aHTMLEditor.CollapseSelectionTo( 3066 aRangesToDelete.GetFirstRangeStartPoint<EditorRawDOMPoint>()); 3067 if (NS_FAILED(rv)) { 3068 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 3069 return Err(rv); 3070 } 3071 return EditActionResult::HandledResult(); 3072 } 3073 MOZ_ASSERT(aRangesToDelete.IsFirstRangeEditable(aEditingHost)); 3074 } 3075 3076 // Remember that we did a ranged delete for the benefit of AfterEditInner(). 3077 aHTMLEditor.TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange = true; 3078 3079 // Figure out if the endpoints are in nodes that can be merged. Adjust 3080 // surrounding white-space in preparation to delete selection. 3081 if (!aHTMLEditor.IsPlaintextMailComposer()) { 3082 { 3083 AutoTrackDOMRange firstRangeTracker(aHTMLEditor.RangeUpdaterRef(), 3084 &aRangesToDelete.FirstRangeRef()); 3085 for (OwningNonNull<nsRange>& range : Reversed(aRangesToDelete.Ranges())) { 3086 if (MOZ_UNLIKELY(!range->IsPositioned() || range->Collapsed())) { 3087 continue; 3088 } 3089 Maybe<AutoTrackDOMRange> trackRange; 3090 if (range != aRangesToDelete.FirstRangeRef()) { 3091 trackRange.emplace(aHTMLEditor.RangeUpdaterRef(), &range); 3092 } 3093 Result<EditorDOMRange, nsresult> rangeToDeleteOrError = 3094 WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToJoin( 3095 aHTMLEditor, EditorDOMRange(range)); 3096 if (MOZ_UNLIKELY(rangeToDeleteOrError.isErr())) { 3097 NS_WARNING( 3098 "WhiteSpaceVisibilityKeeper::" 3099 "NormalizeSurroundingWhiteSpacesToJoin() failed"); 3100 return rangeToDeleteOrError.propagateErr(); 3101 } 3102 trackRange.reset(); 3103 EditorDOMRange rangeToDelete = rangeToDeleteOrError.unwrap(); 3104 if (MOZ_LIKELY(rangeToDelete.IsPositionedAndValidInComposedDoc())) { 3105 nsresult rv = range->SetStartAndEnd( 3106 rangeToDelete.StartRef().ToRawRangeBoundary(), 3107 rangeToDelete.EndRef().ToRawRangeBoundary()); 3108 if (NS_FAILED(rv)) { 3109 NS_WARNING("nsRange::SetStartAndEnd() failed"); 3110 return Err(rv); 3111 } 3112 } 3113 } 3114 } 3115 aRangesToDelete.RemoveCollapsedRanges(); 3116 if (MOZ_UNLIKELY(aRangesToDelete.IsCollapsed())) { 3117 return EditActionResult::HandledResult(); 3118 } 3119 if (NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->IsPositioned()) || 3120 (aHTMLEditor.MaybeNodeRemovalsObservedByDevTools() && 3121 NS_WARN_IF(!aRangesToDelete.IsFirstRangeEditable(aEditingHost)))) { 3122 NS_WARNING( 3123 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() made the first " 3124 "range invalid"); 3125 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 3126 } 3127 } 3128 3129 // XXX This is odd. We do we simply use `DeleteRangesWithTransaction()` 3130 // only when **first** range is in same container? 3131 if (aRangesToDelete.FirstRangeRef()->GetStartContainer() == 3132 aRangesToDelete.FirstRangeRef()->GetEndContainer()) { 3133 // Because of previous DOM tree changes, the range may be collapsed. 3134 // If we've already removed all contents in the range, we shouldn't 3135 // delete anything around the caret. 3136 if (!aRangesToDelete.FirstRangeRef()->Collapsed()) { 3137 const auto stripWrappers = [&]() -> nsIEditor::EStripWrappers { 3138 if (mOriginalStripWrappers == nsIEditor::eStrip && 3139 aEditingHost.IsContentEditablePlainTextOnly()) { 3140 return nsIEditor::eNoStrip; 3141 } 3142 return mOriginalStripWrappers; 3143 }(); 3144 AutoTrackDOMRange firstRangeTracker(aHTMLEditor.RangeUpdaterRef(), 3145 &aRangesToDelete.FirstRangeRef()); 3146 Result<CaretPoint, nsresult> caretPointOrError = 3147 aHTMLEditor.DeleteRangesWithTransaction( 3148 aDirectionAndAmount, stripWrappers, aRangesToDelete); 3149 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 3150 NS_WARNING("HTMLEditor::DeleteRangesWithTransaction() failed"); 3151 return caretPointOrError.propagateErr(); 3152 } 3153 firstRangeTracker.FlushAndStopTracking(); 3154 nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo( 3155 aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, 3156 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 3157 SuggestCaret::AndIgnoreTrivialError}); 3158 if (NS_FAILED(rv)) { 3159 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 3160 return Err(rv); 3161 } 3162 NS_WARNING_ASSERTION( 3163 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 3164 "CaretPoint::SuggestCaretPointTo() failed, but ignored"); 3165 if (NS_WARN_IF(!aRangesToDelete.FirstRangeRef()->IsPositioned()) || 3166 (aHTMLEditor.MaybeNodeRemovalsObservedByDevTools() && 3167 NS_WARN_IF(!aRangesToDelete.IsFirstRangeEditable(aEditingHost)))) { 3168 NS_WARNING( 3169 "HTMLEditor::DeleteRangesWithTransaction() made the first range " 3170 "invalid"); 3171 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 3172 } 3173 } 3174 // However, even if the range is removed, we may need to clean up the 3175 // containers which become empty. 3176 EditorDOMRange rangeToCleanUp(aRangesToDelete.FirstRangeRef()); 3177 AutoTrackDOMRange trackRangeToCleanUp(aHTMLEditor.RangeUpdaterRef(), 3178 &rangeToCleanUp); 3179 nsresult rv = 3180 DeleteUnnecessaryNodes(aHTMLEditor, rangeToCleanUp, aEditingHost); 3181 if (NS_FAILED(rv)) { 3182 NS_WARNING("AutoDeleteRangesHandler::DeleteUnnecessaryNodes() failed"); 3183 return Err(rv); 3184 } 3185 trackRangeToCleanUp.FlushAndStopTracking(); 3186 if (NS_WARN_IF(!rangeToCleanUp.IsPositionedAndValidInComposedDoc())) { 3187 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 3188 } 3189 const auto& pointToPutCaret = 3190 !nsIEditor::DirectionIsBackspace(aDirectionAndAmount) || 3191 (aHTMLEditor.TopLevelEditSubActionDataRef() 3192 .mDidDeleteEmptyParentBlocks && 3193 (aHTMLEditor.GetEditAction() == EditAction::eDrop || 3194 aHTMLEditor.GetEditAction() == EditAction::eDeleteByDrag)) 3195 ? rangeToCleanUp.StartRef() 3196 : rangeToCleanUp.EndRef(); 3197 rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret); 3198 if (NS_FAILED(rv)) { 3199 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 3200 return Err(rv); 3201 } 3202 return EditActionResult::HandledResult(); 3203 } 3204 3205 if (NS_WARN_IF( 3206 !aRangesToDelete.FirstRangeRef()->GetStartContainer()->IsContent()) || 3207 NS_WARN_IF( 3208 !aRangesToDelete.FirstRangeRef()->GetEndContainer()->IsContent())) { 3209 return Err(NS_ERROR_FAILURE); 3210 } 3211 3212 // Figure out mailcite ancestors 3213 RefPtr<Element> startCiteNode = 3214 aHTMLEditor.GetMostDistantAncestorMailCiteElement( 3215 *aRangesToDelete.FirstRangeRef()->GetStartContainer()); 3216 RefPtr<Element> endCiteNode = 3217 aHTMLEditor.GetMostDistantAncestorMailCiteElement( 3218 *aRangesToDelete.FirstRangeRef()->GetEndContainer()); 3219 3220 // If we only have a mailcite at one of the two endpoints, set the 3221 // directionality of the deletion so that the selection will end up 3222 // outside the mailcite. 3223 if (startCiteNode && !endCiteNode) { 3224 aDirectionAndAmount = nsIEditor::eNext; 3225 } else if (!startCiteNode && endCiteNode) { 3226 aDirectionAndAmount = nsIEditor::ePrevious; 3227 } 3228 3229 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); 3230 auto ret = EditActionResult::IgnoredResult(); 3231 for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { 3232 if (MOZ_UNLIKELY(range->Collapsed())) { 3233 continue; 3234 } 3235 AutoBlockElementsJoiner joiner(*this); 3236 if (!joiner.PrepareToDeleteNonCollapsedRange(aHTMLEditor, range, 3237 aEditingHost)) { 3238 return Err(NS_ERROR_FAILURE); 3239 } 3240 Result<EditActionResult, nsresult> result = 3241 joiner.Run(aHTMLEditor, aRangesToDelete.LimitersAndCaretDataRef(), 3242 aDirectionAndAmount, aStripWrappers, MOZ_KnownLive(range), 3243 aSelectionWasCollapsed, aEditingHost); 3244 if (MOZ_UNLIKELY(result.isErr())) { 3245 NS_WARNING("AutoBlockElementsJoiner::Run() failed"); 3246 return result; 3247 } 3248 ret |= result.inspect(); 3249 } 3250 return ret; 3251 } 3252 3253 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: 3254 PrepareToDeleteNonCollapsedRange(const HTMLEditor& aHTMLEditor, 3255 const nsRange& aRangeToDelete, 3256 const Element& aEditingHost) { 3257 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 3258 MOZ_ASSERT(!aRangeToDelete.Collapsed()); 3259 3260 mLeftContent = HTMLEditUtils::GetInclusiveAncestorElement( 3261 *aRangeToDelete.GetStartContainer()->AsContent(), 3262 HTMLEditUtils::ClosestEditableBlockElement, 3263 BlockInlineCheck::UseComputedDisplayOutsideStyle); 3264 mRightContent = HTMLEditUtils::GetInclusiveAncestorElement( 3265 *aRangeToDelete.GetEndContainer()->AsContent(), 3266 HTMLEditUtils::ClosestEditableBlockElement, 3267 BlockInlineCheck::UseComputedDisplayOutsideStyle); 3268 // Note that mLeftContent and/or mRightContent can be nullptr if editing host 3269 // is an inline element. If both editable ancestor block is exactly same 3270 // one or one reaches an inline editing host, we can just delete the content 3271 // in ranges. 3272 if (mLeftContent == mRightContent || !mLeftContent || !mRightContent) { 3273 MOZ_ASSERT_IF( 3274 !mLeftContent || !mRightContent, 3275 aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost() == 3276 aRangeToDelete.GetEndContainer()->AsContent()->GetEditingHost()); 3277 mMode = Mode::DeleteContentInRange; 3278 return true; 3279 } 3280 3281 // If left block and right block are adjacent siblings and they are same 3282 // type of elements, we can merge them after deleting the selected contents. 3283 // MOOSE: this could conceivably screw up a table.. fix me. 3284 if (mLeftContent->GetParentNode() == mRightContent->GetParentNode() && 3285 HTMLEditUtils::CanContentsBeJoined(*mLeftContent, *mRightContent) && 3286 // XXX What's special about these three types of block? 3287 (mLeftContent->IsHTMLElement(nsGkAtoms::p) || 3288 HTMLEditUtils::IsListItemElement(*mLeftContent) || 3289 HTMLEditUtils::IsHeadingElement(*mLeftContent))) { 3290 mMode = Mode::JoinBlocksInSameParent; 3291 return true; 3292 } 3293 3294 // If the range starts immediately after a line end and ends in a 3295 // child right block, we should not unwrap the right block unless the 3296 // right block will have no nodes. 3297 if (mRightContent->IsInclusiveDescendantOf(mLeftContent)) { 3298 // FYI: Chrome does not remove the right child block even if there will be 3299 // only single <br> or a comment node in it. Therefore, we should use this 3300 // rough check. 3301 const WSScanResult nextVisibleThingOfEndBoundary = 3302 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 3303 {WSRunScanner::Option::OnlyEditableNodes}, 3304 EditorRawDOMPoint(aRangeToDelete.EndRef())); 3305 if (!nextVisibleThingOfEndBoundary.ReachedCurrentBlockBoundary()) { 3306 MOZ_ASSERT(mLeftContent->IsElement()); 3307 Result<Element*, nsresult> mostDistantBlockOrError = 3308 AutoBlockElementsJoiner:: 3309 GetMostDistantBlockAncestorIfPointIsStartAtBlock( 3310 EditorRawDOMPoint(mRightContent, 0), aEditingHost, 3311 mLeftContent->AsElement()); 3312 MOZ_ASSERT(mostDistantBlockOrError.isOk()); 3313 if (MOZ_LIKELY(mostDistantBlockOrError.inspect())) { 3314 const WSScanResult prevVisibleThingOfStartBoundary = 3315 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 3316 {WSRunScanner::Option::OnlyEditableNodes}, 3317 EditorRawDOMPoint(aRangeToDelete.StartRef())); 3318 if (prevVisibleThingOfStartBoundary.ReachedBRElement()) { 3319 // If the range start after a <br> followed by the block boundary, 3320 // we want to delete the <br> or following <br> element unless it's 3321 // not a part of empty line like `<div>abc<br>{<div>]def`. 3322 const WSScanResult nextVisibleThingOfBR = 3323 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 3324 {WSRunScanner::Option::OnlyEditableNodes}, 3325 EditorRawDOMPoint::After( 3326 *prevVisibleThingOfStartBoundary.GetContent())); 3327 MOZ_ASSERT(!nextVisibleThingOfBR.ReachedCurrentBlockBoundary()); 3328 if (!nextVisibleThingOfBR.ReachedOtherBlockElement() || 3329 nextVisibleThingOfBR.GetContent() != 3330 mostDistantBlockOrError.inspect()) { 3331 // The range selects a non-empty line or a child block at least. 3332 mMode = Mode::DeletePrecedingLinesAndContentInRange; 3333 return true; 3334 } 3335 const WSScanResult prevVisibleThingOfBR = 3336 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 3337 {WSRunScanner::Option::OnlyEditableNodes}, 3338 EditorRawDOMPoint( 3339 prevVisibleThingOfStartBoundary.GetContent())); 3340 if (prevVisibleThingOfBR.ReachedBRElement() || 3341 prevVisibleThingOfBR.ReachedPreformattedLineBreak() || 3342 prevVisibleThingOfBR.ReachedBlockBoundary()) { 3343 // The preceding <br> causes an empty line. 3344 mMode = Mode::DeletePrecedingLinesAndContentInRange; 3345 return true; 3346 } 3347 } else if (prevVisibleThingOfStartBoundary 3348 .ReachedPreformattedLineBreak()) { 3349 const WSScanResult nextVisibleThingOfLineBreak = 3350 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 3351 {WSRunScanner::Option::OnlyEditableNodes}, 3352 prevVisibleThingOfStartBoundary 3353 .PointAfterReachedContent<EditorRawDOMPoint>()); 3354 MOZ_ASSERT( 3355 !nextVisibleThingOfLineBreak.ReachedCurrentBlockBoundary()); 3356 if (!nextVisibleThingOfLineBreak.ReachedOtherBlockElement() || 3357 nextVisibleThingOfLineBreak.GetContent() != 3358 mostDistantBlockOrError.inspect()) { 3359 // The range selects a non-empty line or a child block at least. 3360 mMode = Mode::DeletePrecedingLinesAndContentInRange; 3361 return true; 3362 } 3363 const WSScanResult prevVisibleThingOfLineBreak = 3364 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 3365 {WSRunScanner::Option::OnlyEditableNodes}, 3366 prevVisibleThingOfStartBoundary 3367 .PointAtReachedContent<EditorRawDOMPoint>()); 3368 if (prevVisibleThingOfLineBreak.ReachedBRElement() || 3369 prevVisibleThingOfLineBreak.ReachedPreformattedLineBreak() || 3370 prevVisibleThingOfLineBreak.ReachedBlockBoundary()) { 3371 // The preceding line break causes an empty line. 3372 mMode = Mode::DeletePrecedingLinesAndContentInRange; 3373 return true; 3374 } 3375 } else if (prevVisibleThingOfStartBoundary 3376 .ReachedCurrentBlockBoundary()) { 3377 MOZ_ASSERT(prevVisibleThingOfStartBoundary.ElementPtr() == 3378 mLeftContent); 3379 const WSScanResult firstVisibleThingInBlock = 3380 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 3381 {WSRunScanner::Option::OnlyEditableNodes}, 3382 EditorRawDOMPoint( 3383 prevVisibleThingOfStartBoundary.ElementPtr(), 0)); 3384 if (!firstVisibleThingInBlock.ReachedOtherBlockElement() || 3385 firstVisibleThingInBlock.ElementPtr() != 3386 mostDistantBlockOrError.inspect()) { 3387 mMode = Mode::DeletePrecedingLinesAndContentInRange; 3388 return true; 3389 } 3390 } else if (prevVisibleThingOfStartBoundary.ReachedOtherBlockElement()) { 3391 const WSScanResult firstVisibleThingAfterBlock = 3392 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 3393 {WSRunScanner::Option::OnlyEditableNodes}, 3394 EditorRawDOMPoint::After( 3395 *prevVisibleThingOfStartBoundary.ElementPtr())); 3396 if (!firstVisibleThingAfterBlock.ReachedOtherBlockElement() || 3397 firstVisibleThingAfterBlock.ElementPtr() != 3398 mostDistantBlockOrError.inspect()) { 3399 mMode = Mode::DeletePrecedingLinesAndContentInRange; 3400 return true; 3401 } 3402 } 3403 } 3404 } 3405 } 3406 3407 mMode = Mode::DeleteNonCollapsedRange; 3408 return true; 3409 } 3410 3411 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: 3412 ComputeRangeToDeleteContentInRange( 3413 const HTMLEditor& aHTMLEditor, 3414 nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete, 3415 const Element& aEditingHost) const { 3416 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 3417 MOZ_ASSERT(!aRangeToDelete.Collapsed()); 3418 MOZ_ASSERT(mMode == Mode::DeleteContentInRange); 3419 MOZ_ASSERT(aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost()); 3420 MOZ_ASSERT( 3421 aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost() == 3422 aRangeToDelete.GetEndContainer()->AsContent()->GetEditingHost()); 3423 MOZ_ASSERT(!mLeftContent == !mRightContent); 3424 MOZ_ASSERT_IF(mLeftContent, mLeftContent->IsElement()); 3425 MOZ_ASSERT_IF(mLeftContent, 3426 aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf( 3427 mLeftContent)); 3428 MOZ_ASSERT_IF(mRightContent, mRightContent->IsElement()); 3429 MOZ_ASSERT_IF( 3430 mRightContent, 3431 aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent)); 3432 MOZ_ASSERT_IF( 3433 !mLeftContent, 3434 HTMLEditUtils::IsInlineContent( 3435 *aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost(), 3436 BlockInlineCheck::UseComputedDisplayOutsideStyle)); 3437 3438 nsresult rv = 3439 mDeleteRangesHandlerConst.ComputeRangeToDeleteRangeWithTransaction( 3440 aHTMLEditor, aDirectionAndAmount, aRangeToDelete, aEditingHost); 3441 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3442 "AutoDeleteRangesHandler::" 3443 "ComputeRangeToDeleteRangeWithTransaction() failed"); 3444 return rv; 3445 } 3446 3447 Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: 3448 AutoBlockElementsJoiner::DeleteContentInRange( 3449 HTMLEditor& aHTMLEditor, 3450 const LimitersAndCaretData& aLimitersAndCaretData, 3451 nsIEditor::EDirection aDirectionAndAmount, 3452 nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete, 3453 const Element& aEditingHost) { 3454 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 3455 MOZ_ASSERT(!aRangeToDelete.Collapsed()); 3456 MOZ_ASSERT(mMode == Mode::DeleteContentInRange); 3457 MOZ_ASSERT(mDeleteRangesHandler); 3458 MOZ_ASSERT(aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost()); 3459 MOZ_ASSERT( 3460 aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost() == 3461 aRangeToDelete.GetEndContainer()->AsContent()->GetEditingHost()); 3462 MOZ_ASSERT_IF(mLeftContent, mLeftContent->IsElement()); 3463 MOZ_ASSERT_IF(mLeftContent, 3464 aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf( 3465 mLeftContent)); 3466 MOZ_ASSERT_IF(mRightContent, mRightContent->IsElement()); 3467 MOZ_ASSERT_IF( 3468 mRightContent, 3469 aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent)); 3470 MOZ_ASSERT_IF( 3471 !mLeftContent, 3472 HTMLEditUtils::IsInlineContent( 3473 *aRangeToDelete.GetStartContainer()->AsContent()->GetEditingHost(), 3474 BlockInlineCheck::UseComputedDisplayOutsideStyle)); 3475 3476 const OwningNonNull<nsRange> rangeToDelete(aRangeToDelete); 3477 Result<EditorDOMRange, nsresult> rangeToDeleteOrError = 3478 WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToJoin( 3479 aHTMLEditor, EditorDOMRange(rangeToDelete)); 3480 if (MOZ_UNLIKELY(rangeToDeleteOrError.isErr())) { 3481 NS_WARNING( 3482 "WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToJoin() " 3483 "failed"); 3484 return rangeToDeleteOrError.propagateErr(); 3485 } 3486 nsresult rv = rangeToDeleteOrError.unwrap().SetToRange(rangeToDelete); 3487 if (NS_FAILED(rv)) { 3488 NS_WARNING("EditorDOMRange::SetToRange() failed"); 3489 return Err(rv); 3490 } 3491 if (!rangeToDelete->Collapsed()) { 3492 AutoClonedSelectionRangeArray rangesToDelete(*rangeToDelete, 3493 aLimitersAndCaretData); 3494 AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(), 3495 &rangeToDelete); 3496 Result<CaretPoint, nsresult> caretPointOrError = 3497 aHTMLEditor.DeleteRangesWithTransaction(aDirectionAndAmount, 3498 aStripWrappers, rangesToDelete); 3499 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 3500 if (NS_WARN_IF(caretPointOrError.inspectErr() == 3501 NS_ERROR_EDITOR_DESTROYED)) { 3502 return Err(NS_ERROR_EDITOR_DESTROYED); 3503 } 3504 NS_WARNING( 3505 "HTMLEditor::DeleteRangesWithTransaction() failed, but ignored"); 3506 } else { 3507 nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo( 3508 aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, 3509 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 3510 SuggestCaret::AndIgnoreTrivialError}); 3511 if (NS_FAILED(rv)) { 3512 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 3513 return Err(rv); 3514 } 3515 NS_WARNING_ASSERTION( 3516 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 3517 "CaretPoint::SuggestCaretPointTo() failed, but ignored"); 3518 } 3519 } 3520 3521 if (NS_WARN_IF(!rangeToDelete->IsPositioned())) { 3522 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 3523 } 3524 3525 EditorDOMRange rangeToCleanUp(*rangeToDelete); 3526 { 3527 AutoTrackDOMRange trackRangeToCleanUp(aHTMLEditor.RangeUpdaterRef(), 3528 &rangeToCleanUp); 3529 nsresult rv = mDeleteRangesHandler->DeleteUnnecessaryNodes( 3530 aHTMLEditor, rangeToCleanUp, aEditingHost); 3531 if (NS_FAILED(rv)) { 3532 NS_WARNING("AutoDeleteRangesHandler::DeleteUnnecessaryNodes() failed"); 3533 return Err(rv); 3534 } 3535 } 3536 const auto& pointToPutCaret = 3537 !nsIEditor::DirectionIsBackspace(aDirectionAndAmount) || 3538 (aHTMLEditor.TopLevelEditSubActionDataRef() 3539 .mDidDeleteEmptyParentBlocks && 3540 (aHTMLEditor.GetEditAction() == EditAction::eDrop || 3541 aHTMLEditor.GetEditAction() == EditAction::eDeleteByDrag)) 3542 ? rangeToCleanUp.StartRef() 3543 : rangeToCleanUp.EndRef(); 3544 rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret); 3545 if (NS_FAILED(rv)) { 3546 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 3547 return Err(rv); 3548 } 3549 return EditActionResult::HandledResult(); 3550 } 3551 3552 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: 3553 ComputeRangeToJoinBlockElementsInSameParent( 3554 const HTMLEditor& aHTMLEditor, 3555 nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete, 3556 const Element& aEditingHost) const { 3557 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 3558 MOZ_ASSERT(!aRangeToDelete.Collapsed()); 3559 MOZ_ASSERT(mMode == Mode::JoinBlocksInSameParent); 3560 MOZ_ASSERT(mLeftContent); 3561 MOZ_ASSERT(mLeftContent->IsElement()); 3562 MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf( 3563 mLeftContent)); 3564 MOZ_ASSERT(mRightContent); 3565 MOZ_ASSERT(mRightContent->IsElement()); 3566 MOZ_ASSERT( 3567 aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent)); 3568 MOZ_ASSERT(mLeftContent->GetParentNode() == mRightContent->GetParentNode()); 3569 3570 nsresult rv = 3571 mDeleteRangesHandlerConst.ComputeRangeToDeleteRangeWithTransaction( 3572 aHTMLEditor, aDirectionAndAmount, aRangeToDelete, aEditingHost); 3573 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3574 "AutoDeleteRangesHandler::" 3575 "ComputeRangeToDeleteRangeWithTransaction() failed"); 3576 return rv; 3577 } 3578 3579 Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: 3580 AutoBlockElementsJoiner::JoinBlockElementsInSameParent( 3581 HTMLEditor& aHTMLEditor, 3582 const LimitersAndCaretData& aLimitersAndCaretData, 3583 nsIEditor::EDirection aDirectionAndAmount, 3584 nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete, 3585 SelectionWasCollapsed aSelectionWasCollapsed, 3586 const Element& aEditingHost) { 3587 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 3588 MOZ_ASSERT(!aRangeToDelete.Collapsed()); 3589 MOZ_ASSERT(mMode == Mode::JoinBlocksInSameParent); 3590 MOZ_ASSERT(mLeftContent); 3591 MOZ_ASSERT(mLeftContent->IsElement()); 3592 MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf( 3593 mLeftContent)); 3594 MOZ_ASSERT(mRightContent); 3595 MOZ_ASSERT(mRightContent->IsElement()); 3596 MOZ_ASSERT( 3597 aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent)); 3598 MOZ_ASSERT(mLeftContent->GetParentNode() == mRightContent->GetParentNode()); 3599 3600 const bool backspaceInRightBlock = 3601 aSelectionWasCollapsed == SelectionWasCollapsed::Yes && 3602 nsIEditor::DirectionIsBackspace(aDirectionAndAmount); 3603 3604 const OwningNonNull<nsRange> rangeToDelete(aRangeToDelete); 3605 3606 // If mLeftContent ends with an invisible line break, we should delete it 3607 // before joining the blocks because that will appear as a visible line break 3608 // if JoinNodesDeepWithTransaction() moves the first line content of 3609 // mRightContent to end of the line break in mLeftContent. 3610 if (HTMLEditUtils::IsBlockElement( 3611 *mLeftContent, BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 3612 const WSScanResult lastThingInLeftBlock = 3613 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 3614 {}, EditorRawDOMPoint::AtEndOf(*mLeftContent)); 3615 if (lastThingInLeftBlock.ReachedLineBreak()) { 3616 const EditorLineBreak lineBreak = 3617 lastThingInLeftBlock.CreateEditorLineBreak<EditorLineBreak>(); 3618 // FIXME: If the line break is wrapped in an non-editable inline element, 3619 // we should delete its parent too or should fail. 3620 Result<EditorDOMPoint, nsresult> exLineBreakPointOrError = 3621 aHTMLEditor.DeleteLineBreakWithTransaction( 3622 lineBreak, nsIEditor::eNoStrip, aEditingHost); 3623 if (MOZ_UNLIKELY(exLineBreakPointOrError.isErr())) { 3624 NS_WARNING("HTMLEditor::DeleteLineBreakWithTransaction() failed"); 3625 return exLineBreakPointOrError.propagateErr(); 3626 } 3627 } 3628 } 3629 3630 // Then, normalizer white-spaces at end of mLeftContent and at start of 3631 // mRightContent to keep their visibility. 3632 Result<EditorDOMRange, nsresult> rangeToDeleteOrError = 3633 WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToJoin( 3634 aHTMLEditor, EditorDOMRange(*rangeToDelete)); 3635 if (MOZ_UNLIKELY(rangeToDeleteOrError.isErr())) { 3636 NS_WARNING( 3637 "WhiteSpaceVisibilityKeeper::NormalizeSurroundingWhiteSpacesToJoin() " 3638 "failed"); 3639 return rangeToDeleteOrError.propagateErr(); 3640 } 3641 nsresult rv = rangeToDeleteOrError.unwrap().SetToRange(rangeToDelete); 3642 if (NS_FAILED(rv)) { 3643 NS_WARNING("EditorDOMRange::SetToRange() failed"); 3644 return Err(rv); 3645 } 3646 3647 // Finally, delete the selected content and move first line of mRightContent 3648 // to end of mLeftContent. 3649 if (!rangeToDelete->Collapsed()) { 3650 AutoClonedSelectionRangeArray rangesToDelete(rangeToDelete, 3651 aLimitersAndCaretData); 3652 Result<CaretPoint, nsresult> caretPointOrError = 3653 aHTMLEditor.DeleteRangesWithTransaction(aDirectionAndAmount, 3654 aStripWrappers, rangesToDelete); 3655 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 3656 NS_WARNING("HTMLEditor::DeleteRangesWithTransaction() failed"); 3657 return caretPointOrError.propagateErr(); 3658 } 3659 3660 nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo( 3661 aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, 3662 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 3663 SuggestCaret::AndIgnoreTrivialError}); 3664 if (NS_FAILED(rv)) { 3665 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 3666 return Err(rv); 3667 } 3668 NS_WARNING_ASSERTION( 3669 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 3670 "CaretPoint::SuggestCaretPointTo() failed, but ignored"); 3671 } 3672 3673 if (NS_WARN_IF(!mLeftContent->GetParentNode()) || 3674 NS_WARN_IF(!mRightContent->GetParentNode()) || 3675 NS_WARN_IF(mLeftContent->GetParentNode() != 3676 mRightContent->GetParentNode())) { 3677 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 3678 } 3679 3680 auto startOfRightContent = 3681 HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>( 3682 *mRightContent, {EditablePointOption::RecognizeInvisibleWhiteSpaces, 3683 EditablePointOption::StopAtComment}); 3684 AutoTrackDOMPoint trackStartOfRightContent(aHTMLEditor.RangeUpdaterRef(), 3685 &startOfRightContent); 3686 Result<EditorDOMPoint, nsresult> atFirstChildOfTheLastRightNodeOrError = 3687 JoinNodesDeepWithTransaction(aHTMLEditor, MOZ_KnownLive(*mLeftContent), 3688 MOZ_KnownLive(*mRightContent)); 3689 if (MOZ_UNLIKELY(atFirstChildOfTheLastRightNodeOrError.isErr())) { 3690 NS_WARNING("HTMLEditor::JoinNodesDeepWithTransaction() failed"); 3691 return atFirstChildOfTheLastRightNodeOrError.propagateErr(); 3692 } 3693 MOZ_ASSERT(atFirstChildOfTheLastRightNodeOrError.inspect().IsSet()); 3694 trackStartOfRightContent.FlushAndStopTracking(); 3695 if (NS_WARN_IF(!startOfRightContent.IsSet()) || 3696 NS_WARN_IF(!startOfRightContent.GetContainer()->IsInComposedDoc())) { 3697 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 3698 } 3699 3700 // If we're deleting selection (not replacing with new content) and the joined 3701 // point follows a text node, we should put caret to end of the preceding text 3702 // node because the other browsers insert following inputs into there. 3703 if (MayEditActionDeleteAroundCollapsedSelection( 3704 aHTMLEditor.GetEditAction())) { 3705 const WSScanResult maybePreviousText = 3706 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 3707 {}, startOfRightContent, &aEditingHost); 3708 if (maybePreviousText.IsContentEditable() && 3709 maybePreviousText.InVisibleOrCollapsibleCharacters()) { 3710 nsresult rv = aHTMLEditor.CollapseSelectionTo( 3711 maybePreviousText.PointAfterReachedContent<EditorRawDOMPoint>()); 3712 if (NS_FAILED(rv)) { 3713 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 3714 return Err(rv); 3715 } 3716 // If we prefer to use style in the previous line, we should forget 3717 // previous styles since the caret position has all styles which we want 3718 // to use with new content. 3719 if (backspaceInRightBlock) { 3720 aHTMLEditor.TopLevelEditSubActionDataRef() 3721 .mCachedPendingStyles->Clear(); 3722 } 3723 // And we don't want to keep extending a link at ex-end of the previous 3724 // paragraph. 3725 if (HTMLEditor::GetLinkElement(maybePreviousText.TextPtr())) { 3726 aHTMLEditor.mPendingStylesToApplyToNewContent 3727 ->ClearLinkAndItsSpecifiedStyle(); 3728 } 3729 return EditActionResult::HandledResult(); 3730 } 3731 } 3732 3733 // Otherwise, we should put caret at start of the right content. 3734 rv = aHTMLEditor.CollapseSelectionTo( 3735 atFirstChildOfTheLastRightNodeOrError.inspect()); 3736 if (NS_FAILED(rv)) { 3737 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 3738 return Err(rv); 3739 } 3740 return EditActionResult::HandledResult(); 3741 } 3742 3743 Result<bool, nsresult> 3744 HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: 3745 ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure( 3746 const HTMLEditor& aHTMLEditor, nsRange& aRange, 3747 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed) 3748 const { 3749 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 3750 3751 AutoTArray<OwningNonNull<nsIContent>, 10> arrayOfTopChildren; 3752 DOMSubtreeIterator iter; 3753 nsresult rv = iter.Init(aRange); 3754 if (NS_FAILED(rv)) { 3755 NS_WARNING("DOMSubtreeIterator::Init() failed"); 3756 return Err(rv); 3757 } 3758 iter.AppendAllNodesToArray(arrayOfTopChildren); 3759 return NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure( 3760 aHTMLEditor, arrayOfTopChildren, aSelectionWasCollapsed); 3761 } 3762 3763 Result<DeleteRangeResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: 3764 AutoBlockElementsJoiner::DeleteNodesEntirelyInRangeButKeepTableStructure( 3765 HTMLEditor& aHTMLEditor, 3766 const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContent, 3767 PutCaretTo aPutCaretTo) { 3768 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 3769 3770 DeleteRangeResult deleteContentResult = DeleteRangeResult::IgnoredResult(); 3771 for (const auto& content : aArrayOfContent) { 3772 // XXX After here, the child contents in the array may have been moved 3773 // to somewhere or removed. We should handle it. 3774 // 3775 // MOZ_KnownLive because 'aArrayOfContent' is guaranteed to 3776 // keep it alive. 3777 AutoTrackDOMDeleteRangeResult trackDeleteContentResult( 3778 aHTMLEditor.RangeUpdaterRef(), &deleteContentResult); 3779 Result<DeleteRangeResult, nsresult> deleteResult = 3780 DeleteContentButKeepTableStructure(aHTMLEditor, MOZ_KnownLive(content)); 3781 if (MOZ_UNLIKELY(deleteResult.isErr())) { 3782 if (NS_WARN_IF(deleteResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) { 3783 return Err(NS_ERROR_EDITOR_DESTROYED); 3784 } 3785 NS_WARNING( 3786 "AutoBlockElementsJoiner::DeleteContentButKeepTableStructure() " 3787 "failed, but ignored"); 3788 continue; 3789 } 3790 trackDeleteContentResult.FlushAndStopTracking(); 3791 deleteContentResult |= deleteResult.unwrap(); 3792 } 3793 if (deleteContentResult.Handled()) { 3794 EditorDOMPoint pointToPutCaret = 3795 aPutCaretTo == PutCaretTo::StartOfRange 3796 ? deleteContentResult.DeleteRangeRef().StartRef() 3797 : deleteContentResult.DeleteRangeRef().EndRef(); 3798 deleteContentResult |= CaretPoint(std::move(pointToPutCaret)); 3799 } 3800 return std::move(deleteContentResult); 3801 } 3802 3803 bool HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: 3804 NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure( 3805 const HTMLEditor& aHTMLEditor, 3806 const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, 3807 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed) 3808 const { 3809 switch (mMode) { 3810 case Mode::DeletePrecedingLinesAndContentInRange: 3811 case Mode::DeleteBRElement: 3812 case Mode::DeletePrecedingBRElementOfBlock: 3813 case Mode::DeletePrecedingPreformattedLineBreak: 3814 return false; 3815 default: 3816 break; 3817 } 3818 3819 // If original selection was collapsed, we need always to join the nodes. 3820 // XXX Why? 3821 if (aSelectionWasCollapsed == 3822 AutoDeleteRangesHandler::SelectionWasCollapsed::No) { 3823 return true; 3824 } 3825 // If something visible is deleted, no need to join. Visible means 3826 // all nodes except non-visible textnodes and breaks. 3827 if (aArrayOfContents.IsEmpty()) { 3828 return true; 3829 } 3830 for (const OwningNonNull<nsIContent>& content : aArrayOfContents) { 3831 if (content->IsText()) { 3832 if (HTMLEditUtils::IsInVisibleTextFrames(aHTMLEditor.GetPresContext(), 3833 *content->AsText())) { 3834 return false; 3835 } 3836 continue; 3837 } 3838 // XXX If it's an element node, we should check whether it has visible 3839 // frames or not. 3840 if (!content->IsElement() || 3841 HTMLEditUtils::IsEmptyNode( 3842 *content->AsElement(), 3843 {EmptyCheckOption::TreatSingleBRElementAsVisible, 3844 EmptyCheckOption::TreatNonEditableContentAsInvisible})) { 3845 continue; 3846 } 3847 if (!HTMLEditUtils::IsInvisibleBRElement(*content)) { 3848 return false; 3849 } 3850 } 3851 return true; 3852 } 3853 3854 Result<DeleteRangeResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: 3855 AutoBlockElementsJoiner::DeleteTextAtStartAndEndOfRange( 3856 HTMLEditor& aHTMLEditor, nsRange& aRange, PutCaretTo aPutCaretTo) { 3857 if (MOZ_UNLIKELY(aRange.Collapsed())) { 3858 return DeleteRangeResult::IgnoredResult(); 3859 } 3860 3861 const auto DeleteTextNode = 3862 [&aHTMLEditor](const OwningNonNull<Text>& aTextNode) 3863 MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT 3864 -> Result<DeleteRangeResult, nsresult> { 3865 const nsCOMPtr<nsINode> parentNode = aTextNode->GetParentNode(); 3866 if (NS_WARN_IF(!parentNode)) { 3867 return Err(NS_ERROR_FAILURE); 3868 } 3869 const nsCOMPtr<nsIContent> nextSibling = aTextNode->GetNextSibling(); 3870 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aTextNode); 3871 if (NS_FAILED(rv)) { 3872 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 3873 return Err(rv); 3874 } 3875 if (NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parentNode) || 3876 NS_WARN_IF(!parentNode->IsInComposedDoc())) { 3877 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 3878 } 3879 const auto atRemovedTextNode = nextSibling 3880 ? EditorDOMPoint(nextSibling) 3881 : EditorDOMPoint::AtEndOf(*parentNode); 3882 return DeleteRangeResult(EditorDOMRange(atRemovedTextNode), 3883 atRemovedTextNode); 3884 }; 3885 3886 EditorDOMRange range(aRange); 3887 // If the range is in a text node, delete middle of the text or the text node 3888 // itself. 3889 if (range.StartRef().IsInTextNode() && range.InSameContainer()) { 3890 const OwningNonNull<Text> textNode = *range.StartRef().ContainerAs<Text>(); 3891 if (range.StartRef().IsStartOfContainer() && 3892 range.EndRef().IsEndOfContainer()) { 3893 Result<DeleteRangeResult, nsresult> deleteTextNodeResult = 3894 DeleteTextNode(textNode); 3895 NS_WARNING_ASSERTION( 3896 deleteTextNodeResult.isOk(), 3897 "DeleteTextNode() failed to delete the selected Text node"); 3898 return deleteTextNodeResult; 3899 } 3900 MOZ_ASSERT(range.EndRef().Offset() - range.StartRef().Offset() > 0); 3901 Result<CaretPoint, nsresult> caretPointOrError = 3902 aHTMLEditor.DeleteTextWithTransaction( 3903 textNode, range.StartRef().Offset(), 3904 range.EndRef().Offset() - range.StartRef().Offset()); 3905 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 3906 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed"); 3907 return caretPointOrError.propagateErr(); 3908 } 3909 const EditorDOMPoint atRemovedText = 3910 caretPointOrError.unwrap().UnwrapCaretPoint(); 3911 if (NS_WARN_IF(!atRemovedText.IsSetAndValidInComposedDoc())) { 3912 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 3913 } 3914 return DeleteRangeResult(EditorDOMRange(atRemovedText), atRemovedText); 3915 } 3916 3917 // If the range starts in a text node and ends in a different node, delete 3918 // the text after the start boundary. 3919 auto deleteStartTextResultOrError = 3920 [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT 3921 -> Result<DeleteRangeResult, nsresult> { 3922 if (!range.StartRef().IsInTextNode() || 3923 range.StartRef().IsEndOfContainer()) { 3924 return DeleteRangeResult::IgnoredResult(); 3925 } 3926 AutoTrackDOMRange trackRange(aHTMLEditor.RangeUpdaterRef(), &range); 3927 const OwningNonNull<Text> textNode = *range.StartRef().ContainerAs<Text>(); 3928 if (range.StartRef().IsStartOfContainer()) { 3929 Result<DeleteRangeResult, nsresult> deleteTextNodeResult = 3930 DeleteTextNode(textNode); 3931 NS_WARNING_ASSERTION( 3932 deleteTextNodeResult.isOk(), 3933 "DeleteTextNode() failed to delete the start Text node"); 3934 return deleteTextNodeResult; 3935 } 3936 Result<CaretPoint, nsresult> caretPointOrError = 3937 aHTMLEditor.DeleteTextWithTransaction( 3938 textNode, range.StartRef().Offset(), 3939 textNode->TextDataLength() - range.StartRef().Offset()); 3940 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 3941 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed"); 3942 return caretPointOrError.propagateErr(); 3943 } 3944 trackRange.FlushAndStopTracking(); 3945 const EditorDOMPoint atRemovedText = 3946 caretPointOrError.unwrap().UnwrapCaretPoint(); 3947 if (NS_WARN_IF(!atRemovedText.IsSetAndValidInComposedDoc())) { 3948 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 3949 } 3950 return DeleteRangeResult(EditorDOMRange(atRemovedText), atRemovedText); 3951 }(); 3952 if (MOZ_UNLIKELY(deleteStartTextResultOrError.isErr())) { 3953 return deleteStartTextResultOrError.propagateErr(); 3954 } 3955 DeleteRangeResult deleteStartTextResult = 3956 deleteStartTextResultOrError.unwrap(); 3957 3958 // If the range ends in a text node and starts from a different node, delete 3959 // the text before the end boundary. 3960 auto deleteEndTextResultOrError = 3961 [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT 3962 -> Result<DeleteRangeResult, nsresult> { 3963 if (!range.EndRef().IsInTextNode() || range.EndRef().IsStartOfContainer()) { 3964 return DeleteRangeResult::IgnoredResult(); 3965 } 3966 AutoTrackDOMRange trackRange(aHTMLEditor.RangeUpdaterRef(), &range); 3967 AutoTrackDOMDeleteRangeResult trackDeleteStartTextResult( 3968 aHTMLEditor.RangeUpdaterRef(), &deleteStartTextResult); 3969 const OwningNonNull<Text> textNode = *range.EndRef().ContainerAs<Text>(); 3970 if (range.EndRef().IsEndOfContainer()) { 3971 Result<DeleteRangeResult, nsresult> deleteTextNodeResult = 3972 DeleteTextNode(textNode); 3973 NS_WARNING_ASSERTION( 3974 deleteTextNodeResult.isOk(), 3975 "DeleteTextNode() failed to delete the end Text node"); 3976 return deleteTextNodeResult; 3977 } 3978 Result<CaretPoint, nsresult> caretPointOrError = 3979 aHTMLEditor.DeleteTextWithTransaction(textNode, 0, 3980 range.EndRef().Offset()); 3981 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 3982 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed"); 3983 return caretPointOrError.propagateErr(); 3984 } 3985 trackRange.FlushAndStopTracking(); 3986 const EditorDOMPoint atRemovedText = 3987 caretPointOrError.unwrap().UnwrapCaretPoint(); 3988 if (NS_WARN_IF(!atRemovedText.IsSetAndValidInComposedDoc())) { 3989 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 3990 } 3991 return DeleteRangeResult(EditorDOMRange(atRemovedText), atRemovedText); 3992 }(); 3993 if (MOZ_UNLIKELY(deleteEndTextResultOrError.isErr())) { 3994 return deleteEndTextResultOrError.propagateErr(); 3995 } 3996 DeleteRangeResult deleteEndTextResult = deleteEndTextResultOrError.unwrap(); 3997 3998 if (!deleteStartTextResult.Handled() && !deleteEndTextResult.Handled()) { 3999 deleteStartTextResult.IgnoreCaretPointSuggestion(); 4000 deleteEndTextResult.IgnoreCaretPointSuggestion(); 4001 return DeleteRangeResult::IgnoredResult(); 4002 } 4003 4004 EditorDOMPoint pointToPutCaret = 4005 aPutCaretTo == PutCaretTo::EndOfRange 4006 ? (deleteEndTextResult.Handled() 4007 ? deleteEndTextResult.UnwrapCaretPoint() 4008 : EditorDOMPoint()) 4009 : (deleteStartTextResult.Handled() 4010 ? deleteStartTextResult.UnwrapCaretPoint() 4011 : EditorDOMPoint()); 4012 deleteStartTextResult |= deleteEndTextResult; 4013 deleteStartTextResult.ForgetCaretPointSuggestion(); 4014 if (pointToPutCaret.IsSet()) { 4015 deleteStartTextResult |= CaretPoint(std::move(pointToPutCaret)); 4016 } 4017 return std::move(deleteStartTextResult); 4018 } 4019 4020 // static 4021 template <typename EditorDOMPointType> 4022 Result<Element*, nsresult> HTMLEditor::AutoDeleteRangesHandler:: 4023 AutoBlockElementsJoiner::GetMostDistantBlockAncestorIfPointIsStartAtBlock( 4024 const EditorDOMPointType& aPoint, const Element& aEditingHost, 4025 const Element* aAncestorLimiter /* = nullptr */) { 4026 MOZ_ASSERT(aPoint.IsSetAndValid()); 4027 MOZ_ASSERT(aPoint.IsInComposedDoc()); 4028 4029 if (!aAncestorLimiter) { 4030 aAncestorLimiter = &aEditingHost; 4031 } 4032 4033 const auto ReachedCurrentBlockBoundaryWhichWeCanCross = 4034 [&aEditingHost, aAncestorLimiter](const WSScanResult& aScanResult) { 4035 // When the scan result is "reached current block boundary", it may not 4036 // be so literally. 4037 return aScanResult.ReachedCurrentBlockBoundary() && 4038 HTMLEditUtils::IsRemovableFromParentNode( 4039 *aScanResult.ElementPtr()) && 4040 aScanResult.ElementPtr() != &aEditingHost && 4041 aScanResult.ElementPtr() != aAncestorLimiter && 4042 // Don't cross <body>, <head> and <html> 4043 !aScanResult.ElementPtr()->IsAnyOfHTMLElements( 4044 nsGkAtoms::body, nsGkAtoms::head, nsGkAtoms::html) && 4045 // Don't cross table elements 4046 !HTMLEditUtils::IsAnyTableElementExceptColumnElement( 4047 *aScanResult.ElementPtr()); 4048 }; 4049 4050 const WSScanResult prevVisibleThing = 4051 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 4052 {WSRunScanner::Option::OnlyEditableNodes}, aPoint, aAncestorLimiter); 4053 if (!ReachedCurrentBlockBoundaryWhichWeCanCross(prevVisibleThing)) { 4054 return nullptr; 4055 } 4056 MOZ_ASSERT( 4057 HTMLEditUtils::IsBlockElement(*prevVisibleThing.ElementPtr(), 4058 BlockInlineCheck::UseComputedDisplayStyle)); 4059 for (Element* ancestorBlock = prevVisibleThing.ElementPtr(); ancestorBlock;) { 4060 const WSScanResult prevVisibleThing = 4061 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 4062 {WSRunScanner::Option::OnlyEditableNodes}, 4063 EditorRawDOMPoint(ancestorBlock), aAncestorLimiter); 4064 if (!ReachedCurrentBlockBoundaryWhichWeCanCross(prevVisibleThing)) { 4065 return ancestorBlock; 4066 } 4067 MOZ_ASSERT(HTMLEditUtils::IsBlockElement( 4068 *prevVisibleThing.ElementPtr(), 4069 BlockInlineCheck::UseComputedDisplayStyle)); 4070 ancestorBlock = prevVisibleThing.ElementPtr(); 4071 } 4072 return Err(NS_ERROR_FAILURE); 4073 } 4074 4075 void HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: 4076 ExtendRangeToDeleteNonCollapsedRange( 4077 const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete, 4078 const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const { 4079 MOZ_ASSERT_IF(aComputeRangeFor == ComputeRangeFor::GetTargetRanges, 4080 aRangeToDelete.IsPositioned()); 4081 MOZ_ASSERT(!aRangeToDelete.Collapsed()); 4082 MOZ_ASSERT(mLeftContent); 4083 MOZ_ASSERT(mLeftContent->IsElement()); 4084 MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf( 4085 mLeftContent)); 4086 MOZ_ASSERT(mRightContent); 4087 MOZ_ASSERT(mRightContent->IsElement()); 4088 MOZ_ASSERT( 4089 aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent)); 4090 4091 const DebugOnly<Result<bool, nsresult>> extendRangeResult = 4092 AutoDeleteRangesHandler:: 4093 ExtendRangeToContainAncestorInlineElementsAtStart(aRangeToDelete, 4094 aEditingHost); 4095 NS_WARNING_ASSERTION(extendRangeResult.value.isOk(), 4096 "AutoDeleteRangesHandler::" 4097 "ExtendRangeToContainAncestorInlineElementsAtStart() " 4098 "failed, but ignored"); 4099 if (mMode != Mode::DeletePrecedingLinesAndContentInRange) { 4100 return; 4101 } 4102 4103 // If we're computing for beforeinput.getTargetRanges() and the inputType 4104 // is not a simple deletion like replacing selected content with new 4105 // content, the range should end at the original end boundary of the given 4106 // range even if we're deleting only preceding lines of the right child 4107 // block. 4108 const bool preserveEndBoundary = 4109 aComputeRangeFor == ComputeRangeFor::GetTargetRanges && 4110 !MayEditActionDeleteAroundCollapsedSelection(aHTMLEditor.GetEditAction()); 4111 // We need to delete only the preceding lines of the right block. Therefore, 4112 // we need to shrink the range to ends before the right block if the range 4113 // does not contain any meaningful content in the right block. 4114 const Result<Element*, nsresult> inclusiveAncestorCurrentBlockOrError = 4115 AutoBlockElementsJoiner::GetMostDistantBlockAncestorIfPointIsStartAtBlock( 4116 EditorRawDOMPoint(aRangeToDelete.EndRef()), aEditingHost, 4117 mLeftContent->AsElement()); 4118 MOZ_ASSERT(inclusiveAncestorCurrentBlockOrError.isOk()); 4119 MOZ_ASSERT_IF(inclusiveAncestorCurrentBlockOrError.inspect(), 4120 mRightContent->IsInclusiveDescendantOf( 4121 inclusiveAncestorCurrentBlockOrError.inspect())); 4122 if (MOZ_UNLIKELY(!inclusiveAncestorCurrentBlockOrError.isOk() || 4123 !inclusiveAncestorCurrentBlockOrError.inspect())) { 4124 return; 4125 } 4126 4127 const WSScanResult prevVisibleThingOfStartBoundary = 4128 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 4129 {WSRunScanner::Option::OnlyEditableNodes}, 4130 EditorRawDOMPoint(aRangeToDelete.StartRef())); 4131 // If the range starts after an invisible <br> of empty line immediately 4132 // before the most distant inclusive ancestor of the right block like 4133 // `<br><br>{<div>]abc`, we should delete the last empty line because 4134 // users won't see any reaction of the builtin editor in this case. 4135 if (prevVisibleThingOfStartBoundary.ReachedBRElement() || 4136 prevVisibleThingOfStartBoundary.ReachedPreformattedLineBreak()) { 4137 const WSScanResult prevVisibleThingOfPreviousLineBreak = 4138 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 4139 {WSRunScanner::Option::OnlyEditableNodes}, 4140 prevVisibleThingOfStartBoundary 4141 .PointAtReachedContent<EditorRawDOMPoint>()); 4142 const WSScanResult nextVisibleThingOfPreviousBR = 4143 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 4144 {WSRunScanner::Option::OnlyEditableNodes}, 4145 prevVisibleThingOfStartBoundary 4146 .PointAfterReachedContent<EditorRawDOMPoint>()); 4147 if ((prevVisibleThingOfPreviousLineBreak.ReachedBRElement() || 4148 prevVisibleThingOfPreviousLineBreak.ReachedPreformattedLineBreak()) && 4149 nextVisibleThingOfPreviousBR.ReachedOtherBlockElement() && 4150 nextVisibleThingOfPreviousBR.ElementPtr() == 4151 inclusiveAncestorCurrentBlockOrError.inspect()) { 4152 aRangeToDelete.SetStart(prevVisibleThingOfStartBoundary 4153 .PointAtReachedContent<EditorRawDOMPoint>() 4154 .ToRawRangeBoundary(), 4155 IgnoreErrors()); 4156 } 4157 } 4158 4159 if (preserveEndBoundary) { 4160 return; 4161 } 4162 4163 if (aComputeRangeFor == ComputeRangeFor::GetTargetRanges) { 4164 // When we set the end boundary to around the right block, the new end 4165 // boundary should not after inline ancestors of the line break which won't 4166 // be deleted. 4167 const WSScanResult lastVisibleThingBeforeRightChildBlock = 4168 [&]() -> WSScanResult { 4169 EditorRawDOMPoint scanStartPoint(aRangeToDelete.StartRef()); 4170 WSScanResult lastScanResult = WSScanResult::Error(); 4171 while (true) { 4172 WSScanResult scanResult = 4173 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 4174 {WSRunScanner::Option::OnlyEditableNodes}, scanStartPoint, 4175 mLeftContent->AsElement()); 4176 if (scanResult.ReachedBlockBoundary() || 4177 scanResult.ReachedInlineEditingHostBoundary()) { 4178 return lastScanResult; 4179 } 4180 scanStartPoint = 4181 scanResult.PointAfterReachedContent<EditorRawDOMPoint>(); 4182 lastScanResult = scanResult; 4183 } 4184 }(); 4185 if (lastVisibleThingBeforeRightChildBlock.GetContent()) { 4186 const nsIContent* commonAncestor = nsIContent::FromNode( 4187 nsContentUtils::GetClosestCommonInclusiveAncestor( 4188 aRangeToDelete.StartRef().GetContainer(), 4189 lastVisibleThingBeforeRightChildBlock.GetContent())); 4190 MOZ_ASSERT(commonAncestor); 4191 if (commonAncestor && 4192 !mRightContent->IsInclusiveDescendantOf(commonAncestor)) { 4193 IgnoredErrorResult error; 4194 aRangeToDelete.SetEnd( 4195 EditorRawDOMPoint::AtEndOf(*commonAncestor).ToRawRangeBoundary(), 4196 error); 4197 NS_WARNING_ASSERTION(!error.Failed(), 4198 "nsRange::SetEnd() failed, but ignored"); 4199 return; 4200 } 4201 } 4202 } 4203 4204 IgnoredErrorResult error; 4205 aRangeToDelete.SetEnd( 4206 EditorRawDOMPoint(inclusiveAncestorCurrentBlockOrError.inspect()) 4207 .ToRawRangeBoundary(), 4208 error); 4209 NS_WARNING_ASSERTION(!error.Failed(), 4210 "nsRange::SetEnd() failed, but ignored"); 4211 } 4212 4213 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: 4214 ComputeRangeToDeleteNonCollapsedRange( 4215 const HTMLEditor& aHTMLEditor, 4216 nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete, 4217 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, 4218 const Element& aEditingHost) const { 4219 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 4220 MOZ_ASSERT(!aRangeToDelete.Collapsed()); 4221 MOZ_ASSERT(mLeftContent); 4222 MOZ_ASSERT(mLeftContent->IsElement()); 4223 MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf( 4224 mLeftContent)); 4225 MOZ_ASSERT(mRightContent); 4226 MOZ_ASSERT(mRightContent->IsElement()); 4227 MOZ_ASSERT( 4228 aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf(mRightContent)); 4229 4230 ExtendRangeToDeleteNonCollapsedRange(aHTMLEditor, aRangeToDelete, 4231 aEditingHost, 4232 ComputeRangeFor::GetTargetRanges); 4233 4234 Result<bool, nsresult> result = 4235 ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure( 4236 aHTMLEditor, aRangeToDelete, aSelectionWasCollapsed); 4237 if (result.isErr()) { 4238 NS_WARNING( 4239 "AutoBlockElementsJoiner::" 4240 "ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure() " 4241 "failed"); 4242 return result.unwrapErr(); 4243 } 4244 if (!result.unwrap()) { 4245 return NS_OK; 4246 } 4247 4248 AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent, 4249 *mRightContent); 4250 Result<bool, nsresult> canJoinThem = 4251 joiner.Prepare(aHTMLEditor, aEditingHost); 4252 if (canJoinThem.isErr()) { 4253 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed"); 4254 return canJoinThem.unwrapErr(); 4255 } 4256 4257 if (!canJoinThem.unwrap()) { 4258 return NS_SUCCESS_DOM_NO_OPERATION; 4259 } 4260 4261 if (!joiner.CanJoinBlocks()) { 4262 return NS_OK; 4263 } 4264 4265 nsresult rv = joiner.ComputeRangeToDelete(aHTMLEditor, EditorDOMPoint(), 4266 aRangeToDelete); 4267 NS_WARNING_ASSERTION( 4268 NS_SUCCEEDED(rv), 4269 "AutoInclusiveAncestorBlockElementsJoiner::ComputeRangeToDelete() " 4270 "failed"); 4271 4272 // FIXME: If we'll delete unnecessary following <br>, we need to include it 4273 // into aRangesToDelete. 4274 4275 return rv; 4276 } 4277 4278 Result<EditActionResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: 4279 AutoBlockElementsJoiner::HandleDeleteNonCollapsedRange( 4280 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 4281 nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete, 4282 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, 4283 const Element& aEditingHost) { 4284 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 4285 MOZ_ASSERT(!aRangeToDelete.Collapsed()); 4286 MOZ_ASSERT(mDeleteRangesHandler); 4287 4288 const bool isDeletingLineBreak = 4289 mMode == Mode::DeleteBRElement || 4290 mMode == Mode::DeletePrecedingBRElementOfBlock || 4291 mMode == Mode::DeletePrecedingPreformattedLineBreak; 4292 if (!isDeletingLineBreak) { 4293 MOZ_ASSERT(aRangeToDelete.GetStartContainer()->IsInclusiveDescendantOf( 4294 mLeftContent)); 4295 MOZ_ASSERT(aRangeToDelete.GetEndContainer()->IsInclusiveDescendantOf( 4296 mRightContent)); 4297 ExtendRangeToDeleteNonCollapsedRange(aHTMLEditor, aRangeToDelete, 4298 aEditingHost, 4299 ComputeRangeFor::ToDeleteTheRange); 4300 } 4301 4302 const bool backspaceInRightBlock = 4303 aSelectionWasCollapsed == SelectionWasCollapsed::Yes && 4304 nsIEditor::DirectionIsBackspace(aDirectionAndAmount); 4305 4306 AutoTArray<OwningNonNull<nsIContent>, 10> arrayOfTopChildren; 4307 { 4308 DOMSubtreeIterator iter; 4309 nsresult rv = iter.Init(aRangeToDelete); 4310 if (NS_FAILED(rv)) { 4311 NS_WARNING("DOMSubtreeIterator::Init() failed"); 4312 return Err(rv); 4313 } 4314 iter.AppendAllNodesToArray(arrayOfTopChildren); 4315 } 4316 4317 const bool needsToJoinLater = 4318 NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure( 4319 aHTMLEditor, arrayOfTopChildren, aSelectionWasCollapsed); 4320 const bool joinInclusiveAncestorBlockElements = 4321 !isDeletingLineBreak && needsToJoinLater; 4322 const bool maybeDeleteOnlyFollowingContentOfFollowingBlockBoundary = 4323 !isDeletingLineBreak && 4324 mMode != Mode::DeletePrecedingLinesAndContentInRange && 4325 HTMLEditUtils::PointIsImmediatelyBeforeCurrentBlockBoundary( 4326 EditorRawDOMPoint(aRangeToDelete.StartRef()), 4327 HTMLEditUtils::IgnoreInvisibleLineBreak::Yes); 4328 const PutCaretTo putCaretTo = [&]() { 4329 // When we delete only preceding lines of the right child block, we should 4330 // put caret into start of the right block. 4331 if (mMode == Mode::DeletePrecedingLinesAndContentInRange) { 4332 return PutCaretTo::EndOfRange; 4333 } 4334 // If we're joining blocks: if deleting forward the selection should be 4335 // collapsed to the end of the selection, if deleting backward the selection 4336 // should be collapsed to the beginning of the selection. 4337 if (joinInclusiveAncestorBlockElements) { 4338 return nsIEditor::DirectionIsDelete(aDirectionAndAmount) 4339 ? PutCaretTo::EndOfRange 4340 : PutCaretTo::StartOfRange; 4341 } 4342 // But if we're not joining then the selection should collapse to the 4343 // beginning of the selection if we're deleting forward, because the end of 4344 // the selection will still be in the next block. And same thing for 4345 // deleting backwards (selection should collapse to the end, because the 4346 // beginning will still be in the first block). See Bug 507936. 4347 return nsIEditor::DirectionIsDelete(aDirectionAndAmount) 4348 ? PutCaretTo::StartOfRange 4349 : PutCaretTo::EndOfRange; 4350 }(); 4351 4352 auto deleteContentResultOrError = 4353 [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT 4354 -> Result<DeleteRangeResult, nsresult> { 4355 OwningNonNull<nsRange> rangeToDelete(aRangeToDelete); 4356 AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(), 4357 &rangeToDelete); 4358 4359 // First, delete nodes which are entirely selected except table structure 4360 // elements like <td>, <th>, <caption>. 4361 Result<DeleteRangeResult, nsresult> deleteResultOrError = 4362 DeleteNodesEntirelyInRangeButKeepTableStructure( 4363 aHTMLEditor, arrayOfTopChildren, putCaretTo); 4364 if (MOZ_UNLIKELY(deleteResultOrError.isErr())) { 4365 NS_WARNING( 4366 "AutoBlockElementsJoiner::" 4367 "DeleteNodesEntirelyInRangeButKeepTableStructure() failed"); 4368 return deleteResultOrError.propagateErr(); 4369 } 4370 DeleteRangeResult deleteResult = deleteResultOrError.unwrap(); 4371 // We'll compute caret position below, so, we don't need the caret point 4372 // suggestion of DeleteNodesEntirelyInRangeButKeepTableStructure(). 4373 deleteResult.ForgetCaretPointSuggestion(); 4374 4375 // Check endpoints for possible text deletion. We can assume that if 4376 // text node is found, we can delete to end or to beginning as 4377 // appropriate, since the case where both sel endpoints in same text 4378 // node was already handled (we wouldn't be here) 4379 AutoTrackDOMDeleteRangeResult trackDeleteResult( 4380 aHTMLEditor.RangeUpdaterRef(), &deleteResult); 4381 Result<DeleteRangeResult, nsresult> deleteSurroundingTextResultOrError = 4382 DeleteTextAtStartAndEndOfRange(aHTMLEditor, rangeToDelete, putCaretTo); 4383 if (MOZ_UNLIKELY(deleteSurroundingTextResultOrError.isErr())) { 4384 NS_WARNING( 4385 "AutoBlockElementsJoiner::DeleteTextAtStartAndEndOfRange() failed"); 4386 return deleteSurroundingTextResultOrError.propagateErr(); 4387 } 4388 trackDeleteResult.FlushAndStopTracking(); 4389 trackRangeToDelete.FlushAndStopTracking(); 4390 4391 DeleteRangeResult deleteSurroundingTextResult = 4392 deleteSurroundingTextResultOrError.unwrap(); 4393 // We'll compute caret position below, so, we don't need the caret point 4394 // suggestion of DeleteTextAtStartAndEndOfRange(). 4395 deleteSurroundingTextResult.ForgetCaretPointSuggestion(); 4396 4397 // Merge the deleted range. 4398 deleteResult |= deleteSurroundingTextResult; 4399 4400 if (mRightContent && mMode == Mode::DeletePrecedingLinesAndContentInRange) { 4401 if (NS_WARN_IF(!mRightContent->IsInComposedDoc())) { 4402 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 4403 } 4404 auto pointToPutCaret = 4405 HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>( 4406 *mRightContent, {}); 4407 MOZ_ASSERT(pointToPutCaret.IsSet()); 4408 deleteResult |= CaretPoint(std::move(pointToPutCaret)); 4409 } 4410 return std::move(deleteResult); 4411 }(); 4412 if (MOZ_UNLIKELY(deleteContentResultOrError.isErr())) { 4413 return deleteContentResultOrError.propagateErr(); 4414 } 4415 DeleteRangeResult deleteContentResult = deleteContentResultOrError.unwrap(); 4416 // HandleDeleteLineBreak() should handle the new caret position by itself. 4417 if (isDeletingLineBreak) { 4418 MOZ_ASSERT(!joinInclusiveAncestorBlockElements); 4419 deleteContentResult.IgnoreCaretPointSuggestion(); 4420 return EditActionResult::HandledResult(); 4421 } 4422 4423 auto moveFirstLineResultOrError = 4424 [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT 4425 -> Result<DeleteRangeResult, nsresult> { 4426 if (!joinInclusiveAncestorBlockElements) { 4427 return DeleteRangeResult::IgnoredResult(); 4428 } 4429 4430 MOZ_ASSERT(mLeftContent); 4431 MOZ_ASSERT(mLeftContent->IsElement()); 4432 MOZ_ASSERT(mRightContent); 4433 MOZ_ASSERT(mRightContent->IsElement()); 4434 4435 if (!joinInclusiveAncestorBlockElements) { 4436 return DeleteRangeResult::IgnoredResult(); 4437 } 4438 4439 // Finally, join elements containing either mLeftContent or mRightContent. 4440 // XXX This may join only inline elements despite its name. 4441 AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent, 4442 *mRightContent); 4443 Result<bool, nsresult> canJoinThem = 4444 joiner.Prepare(aHTMLEditor, aEditingHost); 4445 if (canJoinThem.isErr()) { 4446 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Prepare() failed"); 4447 return canJoinThem.propagateErr(); 4448 } 4449 4450 if (!canJoinThem.inspect() || !joiner.CanJoinBlocks()) { 4451 return DeleteRangeResult::IgnoredResult(); 4452 } 4453 4454 OwningNonNull<nsRange> rangeToDelete(aRangeToDelete); 4455 AutoTrackDOMRange trackRangeToDelete(aHTMLEditor.RangeUpdaterRef(), 4456 &rangeToDelete); 4457 AutoTrackDOMDeleteRangeResult trackDeleteContentResult( 4458 aHTMLEditor.RangeUpdaterRef(), &deleteContentResult); 4459 Result<DeleteRangeResult, nsresult> moveFirstLineResultOrError = 4460 joiner.Run(aHTMLEditor, aEditingHost); 4461 if (MOZ_UNLIKELY(moveFirstLineResultOrError.isErr())) { 4462 NS_WARNING("AutoInclusiveAncestorBlockElementsJoiner::Run() failed"); 4463 return moveFirstLineResultOrError.propagateErr(); 4464 } 4465 trackDeleteContentResult.FlushAndStopTracking(); 4466 trackRangeToDelete.FlushAndStopTracking(); 4467 DeleteRangeResult moveFirstLineResult = moveFirstLineResultOrError.unwrap(); 4468 #ifdef DEBUG 4469 if (joiner.ShouldDeleteLeafContentInstead()) { 4470 NS_ASSERTION(moveFirstLineResult.Ignored(), 4471 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` " 4472 "returning ignored, but returned not ignored"); 4473 } else { 4474 NS_ASSERTION(!moveFirstLineResult.Ignored(), 4475 "Assumed `AutoInclusiveAncestorBlockElementsJoiner::Run()` " 4476 "returning handled, but returned ignored"); 4477 } 4478 #endif // #ifdef DEBUG 4479 return std::move(moveFirstLineResult); 4480 }(); 4481 if (MOZ_UNLIKELY(moveFirstLineResultOrError.isErr())) { 4482 deleteContentResult.IgnoreCaretPointSuggestion(); 4483 return moveFirstLineResultOrError.propagateErr(); 4484 } 4485 DeleteRangeResult moveFirstLineResult = moveFirstLineResultOrError.unwrap(); 4486 4487 auto pointToPutCaret = [&]() MOZ_NEVER_INLINE_DEBUG -> EditorDOMPoint { 4488 if (moveFirstLineResult.HasCaretPointSuggestion()) { 4489 MOZ_ASSERT(moveFirstLineResult.Handled()); 4490 if (MayEditActionDeleteAroundCollapsedSelection( 4491 aHTMLEditor.GetEditAction())) { 4492 deleteContentResult.IgnoreCaretPointSuggestion(); 4493 // If we're deleting selection (not replacing with new content) and 4494 // AutoInclusiveAncestorBlockElementsJoiner computed new caret position, 4495 // we should use it. 4496 return moveFirstLineResult.UnwrapCaretPoint(); 4497 } 4498 moveFirstLineResult.IgnoreCaretPointSuggestion(); 4499 } 4500 if (deleteContentResult.HasCaretPointSuggestion()) { 4501 return deleteContentResult.UnwrapCaretPoint(); 4502 } 4503 return EditorDOMPoint(putCaretTo == PutCaretTo::StartOfRange 4504 ? aRangeToDelete.StartRef() 4505 : aRangeToDelete.EndRef()); 4506 }(); 4507 MOZ_ASSERT(pointToPutCaret.IsSetAndValidInComposedDoc()); 4508 4509 { 4510 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(), 4511 &pointToPutCaret); 4512 nsresult rv = mDeleteRangesHandler->DeleteUnnecessaryNodes( 4513 aHTMLEditor, EditorDOMRange(aRangeToDelete), aEditingHost); 4514 if (NS_FAILED(rv)) { 4515 NS_WARNING("AutoDeleteRangesHandler::DeleteUnnecessaryNodes() failed"); 4516 return Err(rv); 4517 } 4518 trackPointToPutCaret.FlushAndStopTracking(); 4519 if (NS_WARN_IF(!pointToPutCaret.IsSetAndValidInComposedDoc())) { 4520 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 4521 } 4522 } 4523 4524 if (aHTMLEditor.IsMailEditor() && 4525 MOZ_LIKELY(pointToPutCaret.IsInContentNode())) { 4526 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(), 4527 &pointToPutCaret); 4528 nsresult rv = aHTMLEditor.DeleteMostAncestorMailCiteElementIfEmpty( 4529 MOZ_KnownLive(*pointToPutCaret.ContainerAs<nsIContent>())); 4530 if (NS_FAILED(rv)) { 4531 NS_WARNING( 4532 "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed"); 4533 return Err(rv); 4534 } 4535 trackPointToPutCaret.FlushAndStopTracking(); 4536 if (NS_WARN_IF(!pointToPutCaret.IsSetAndValidInComposedDoc())) { 4537 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 4538 } 4539 } 4540 4541 const auto EnsureNoFollowingUnnecessaryLineBreak = 4542 [&](const EditorDOMPoint& aPoint) 4543 MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT { 4544 if (!aPoint.IsInContentNode()) { 4545 return NS_OK; 4546 } 4547 AutoTrackDOMDeleteRangeResult trackDeleteContentResult( 4548 aHTMLEditor.RangeUpdaterRef(), &deleteContentResult); 4549 AutoTrackDOMDeleteRangeResult trackMoveFirstLineResult( 4550 aHTMLEditor.RangeUpdaterRef(), &moveFirstLineResult); 4551 AutoTrackDOMPoint trackPointToPutCaret( 4552 aHTMLEditor.RangeUpdaterRef(), &pointToPutCaret); 4553 nsresult rv = 4554 aHTMLEditor.EnsureNoFollowingUnnecessaryLineBreak(aPoint); 4555 NS_WARNING_ASSERTION( 4556 NS_SUCCEEDED(rv), 4557 "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed"); 4558 return rv; 4559 }; 4560 4561 const auto InsertPaddingBRElementIfNeeded = 4562 [&](const EditorDOMPoint& aPoint) 4563 MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT 4564 -> Result<CaretPoint, nsresult> { 4565 if (!aPoint.IsInContentNode()) { 4566 return CaretPoint(EditorDOMPoint()); 4567 } 4568 const bool insertingAtCaretPoint = aPoint == pointToPutCaret; 4569 if (insertingAtCaretPoint && aHTMLEditor.GetTopLevelEditSubAction() != 4570 EditSubAction::eDeleteSelectedContent) { 4571 return CaretPoint(EditorDOMPoint()); 4572 } 4573 if (!insertingAtCaretPoint && 4574 mMode == Mode::DeletePrecedingLinesAndContentInRange) { 4575 return CaretPoint(EditorDOMPoint()); 4576 } 4577 AutoTrackDOMDeleteRangeResult trackDeleteContentResult( 4578 aHTMLEditor.RangeUpdaterRef(), &deleteContentResult); 4579 AutoTrackDOMDeleteRangeResult trackMoveFirstLineResult( 4580 aHTMLEditor.RangeUpdaterRef(), &moveFirstLineResult); 4581 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(), 4582 &pointToPutCaret); 4583 Result<CreateLineBreakResult, nsresult> insertPaddingBRElementOrError = 4584 aHTMLEditor.InsertPaddingBRElementIfNeeded( 4585 aPoint, 4586 aEditingHost.IsContentEditablePlainTextOnly() ? nsIEditor::eNoStrip 4587 : nsIEditor::eStrip, 4588 aEditingHost); 4589 if (MOZ_UNLIKELY(insertPaddingBRElementOrError.isErr())) { 4590 NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed"); 4591 return insertPaddingBRElementOrError.propagateErr(); 4592 } 4593 CreateLineBreakResult insertPaddingBRElement = 4594 insertPaddingBRElementOrError.unwrap(); 4595 if (!insertPaddingBRElement.Handled() || !insertingAtCaretPoint) { 4596 insertPaddingBRElement.IgnoreCaretPointSuggestion(); 4597 return CaretPoint(EditorDOMPoint()); 4598 } 4599 return CaretPoint(insertPaddingBRElement.UnwrapCaretPoint()); 4600 }; 4601 4602 // If we moved content from the right element to the left element, we need to 4603 // maintain padding line break at end of moved content. 4604 if (moveFirstLineResult.Handled() && 4605 moveFirstLineResult.DeleteRangeRef().IsPositioned()) { 4606 nsresult rv = EnsureNoFollowingUnnecessaryLineBreak( 4607 moveFirstLineResult.DeleteRangeRef().EndRef()); 4608 if (NS_FAILED(rv)) { 4609 NS_WARNING("EnsureNoFollowingUnnecessaryLineBreak() failed"); 4610 return Err(rv); 4611 } 4612 // If we moved a child block of the first line (although this is 4613 // logically wrong...), we should not put a <br> after that. 4614 const bool movedLineEndsWithBlockBoundary = [&]() { 4615 Element* const commonAncestor = 4616 Element::FromNodeOrNull(moveFirstLineResult.DeleteRangeRef() 4617 .GetClosestCommonInclusiveAncestor()); 4618 nsIContent* const previousVisibleLeafOrChildBlock = 4619 HTMLEditUtils::GetPreviousNonEmptyLeafContentOrPreviousBlockElement( 4620 moveFirstLineResult.DeleteRangeRef().EndRef(), 4621 {LeafNodeType::LeafNodeOrChildBlock}, 4622 BlockInlineCheck::UseComputedDisplayOutsideStyle, commonAncestor); 4623 if (!previousVisibleLeafOrChildBlock) { 4624 return false; 4625 } 4626 return HTMLEditUtils::IsBlockElement( 4627 *previousVisibleLeafOrChildBlock, 4628 BlockInlineCheck::UseComputedDisplayOutsideStyle) && 4629 moveFirstLineResult.DeleteRangeRef().StartRef().EqualsOrIsBefore( 4630 EditorRawDOMPoint::After(*previousVisibleLeafOrChildBlock)); 4631 }(); 4632 if (MOZ_LIKELY(!movedLineEndsWithBlockBoundary)) { 4633 Result<CaretPoint, nsresult> caretPointOrError = 4634 InsertPaddingBRElementIfNeeded( 4635 moveFirstLineResult.DeleteRangeRef().EndRef()); 4636 if (NS_WARN_IF(caretPointOrError.isErr())) { 4637 return caretPointOrError.propagateErr(); 4638 } 4639 caretPointOrError.unwrap().MoveCaretPointTo( 4640 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 4641 } 4642 } 4643 // If we only deleted content in the range, we need to maintain padding line 4644 // breaks at both deleted range boundaries. 4645 else if (deleteContentResult.DeleteRangeRef().IsPositioned()) { 4646 if (!deleteContentResult.DeleteRangeRef().Collapsed()) { 4647 nsresult rv; 4648 if (NS_WARN_IF( 4649 NS_FAILED(rv = EnsureNoFollowingUnnecessaryLineBreak( 4650 deleteContentResult.DeleteRangeRef().EndRef())))) { 4651 return Err(rv); 4652 } 4653 // If we deleted blocks following current block, we should not insert 4654 // padding line break after current block when we're handling Backspace. 4655 const bool isFollowingBlockDeletedByBackspace = 4656 [&]() MOZ_NEVER_INLINE_DEBUG { 4657 if (putCaretTo == PutCaretTo::EndOfRange) { 4658 return false; 4659 } 4660 if (!HTMLEditUtils::RangeIsAcrossStartBlockBoundary( 4661 deleteContentResult.DeleteRangeRef(), 4662 // XXX UseComputedDisplayStyle? 4663 BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 4664 return false; 4665 } 4666 WSScanResult nextThing = 4667 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 4668 {WSRunScanner::Option::OnlyEditableNodes}, 4669 deleteContentResult.DeleteRangeRef().EndRef()); 4670 return nextThing.ReachedBRElement() || 4671 nextThing.ReachedPreformattedLineBreak() || 4672 nextThing.ReachedHRElement() || 4673 nextThing.ReachedBlockBoundary(); 4674 }(); 4675 if (!isFollowingBlockDeletedByBackspace) { 4676 Result<CaretPoint, nsresult> caretPointOrError = 4677 InsertPaddingBRElementIfNeeded( 4678 deleteContentResult.DeleteRangeRef().EndRef()); 4679 if (NS_WARN_IF(caretPointOrError.isErr())) { 4680 return caretPointOrError.propagateErr(); 4681 } 4682 caretPointOrError.unwrap().MoveCaretPointTo( 4683 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 4684 } 4685 } 4686 // If we deleted content only after current block, we don't need to 4687 // maintain line breaks at start of the deleted range because nothing has 4688 // been changed from the caret point of view. 4689 if (!maybeDeleteOnlyFollowingContentOfFollowingBlockBoundary) { 4690 nsresult rv; 4691 if (NS_WARN_IF(NS_FAILED( 4692 rv = EnsureNoFollowingUnnecessaryLineBreak( 4693 deleteContentResult.DeleteRangeRef().StartRef())))) { 4694 return Err(rv); 4695 } 4696 Result<CaretPoint, nsresult> caretPointOrError = 4697 InsertPaddingBRElementIfNeeded( 4698 deleteContentResult.DeleteRangeRef().StartRef()); 4699 if (NS_WARN_IF(caretPointOrError.isErr())) { 4700 return caretPointOrError.propagateErr(); 4701 } 4702 caretPointOrError.unwrap().MoveCaretPointTo( 4703 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 4704 } 4705 } 4706 4707 nsresult rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret); 4708 if (NS_FAILED(rv)) { 4709 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 4710 return Err(rv); 4711 } 4712 if (mMode == Mode::DeletePrecedingLinesAndContentInRange || 4713 moveFirstLineResult.Handled()) { 4714 // If we prefer to use style in the previous line, we should forget previous 4715 // styles since the caret position has all styles which we want to use with 4716 // new content. 4717 if (backspaceInRightBlock) { 4718 aHTMLEditor.TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear(); 4719 } 4720 // And we don't want to keep extending a link at ex-end of the previous 4721 // paragraph. 4722 if (HTMLEditor::GetLinkElement(pointToPutCaret.GetContainer())) { 4723 aHTMLEditor.mPendingStylesToApplyToNewContent 4724 ->ClearLinkAndItsSpecifiedStyle(); 4725 } 4726 } 4727 return EditActionResult::HandledResult(); 4728 } 4729 4730 nsresult HTMLEditor::AutoDeleteRangesHandler::DeleteUnnecessaryNodes( 4731 HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange, 4732 const Element& aEditingHost) { 4733 MOZ_ASSERT(aHTMLEditor.IsTopLevelEditSubActionDataAvailable()); 4734 MOZ_ASSERT(EditorUtils::IsEditableContent( 4735 *aRange.StartRef().ContainerAs<nsIContent>(), EditorType::HTML)); 4736 MOZ_ASSERT(EditorUtils::IsEditableContent( 4737 *aRange.EndRef().ContainerAs<nsIContent>(), EditorType::HTML)); 4738 4739 EditorDOMRange range(aRange); 4740 4741 // If we're handling DnD, this is called to delete dragging item from the 4742 // tree. In this case, we should remove parent blocks if it becomes empty. 4743 if (aHTMLEditor.GetEditAction() == EditAction::eDrop || 4744 aHTMLEditor.GetEditAction() == EditAction::eDeleteByDrag) { 4745 MOZ_ASSERT(range.Collapsed() || 4746 (range.StartRef().GetContainer()->GetNextSibling() == 4747 range.EndRef().GetContainer() && 4748 range.StartRef().IsEndOfContainer() && 4749 range.EndRef().IsStartOfContainer())); 4750 AutoTrackDOMRange trackRange(aHTMLEditor.RangeUpdaterRef(), &range); 4751 4752 nsresult rv = DeleteParentBlocksWithTransactionIfEmpty( 4753 aHTMLEditor, range.StartRef(), aEditingHost); 4754 if (NS_FAILED(rv)) { 4755 NS_WARNING( 4756 "HTMLEditor::DeleteParentBlocksWithTransactionIfEmpty() failed"); 4757 return rv; 4758 } 4759 aHTMLEditor.TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks = 4760 rv == NS_OK; 4761 // If we removed parent blocks, Selection should be collapsed at where 4762 // the most ancestor empty block has been. 4763 if (aHTMLEditor.TopLevelEditSubActionDataRef() 4764 .mDidDeleteEmptyParentBlocks) { 4765 return NS_OK; 4766 } 4767 } 4768 4769 if (NS_WARN_IF(!range.IsInContentNodes()) || 4770 NS_WARN_IF(!EditorUtils::IsEditableContent( 4771 *range.StartRef().ContainerAs<nsIContent>(), EditorType::HTML)) || 4772 NS_WARN_IF(!EditorUtils::IsEditableContent( 4773 *range.EndRef().ContainerAs<nsIContent>(), EditorType::HTML))) { 4774 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE; 4775 } 4776 4777 // We might have left only collapsed white-space in the start/end nodes 4778 { 4779 AutoTrackDOMRange trackRange(aHTMLEditor.RangeUpdaterRef(), &range); 4780 4781 OwningNonNull<nsIContent> startContainer = 4782 *range.StartRef().ContainerAs<nsIContent>(); 4783 OwningNonNull<nsIContent> endContainer = 4784 *range.EndRef().ContainerAs<nsIContent>(); 4785 nsresult rv = 4786 DeleteNodeIfInvisibleAndEditableTextNode(aHTMLEditor, startContainer); 4787 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 4788 return NS_ERROR_EDITOR_DESTROYED; 4789 } 4790 NS_WARNING_ASSERTION( 4791 NS_SUCCEEDED(rv), 4792 "AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode() " 4793 "failed to remove start node, but ignored"); 4794 // If we've not handled the selection end container, and it's still 4795 // editable, let's handle it. 4796 if (!range.InSameContainer() && 4797 EditorUtils::IsEditableContent( 4798 *range.EndRef().ContainerAs<nsIContent>(), EditorType::HTML)) { 4799 rv = DeleteNodeIfInvisibleAndEditableTextNode(aHTMLEditor, endContainer); 4800 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 4801 return NS_ERROR_EDITOR_DESTROYED; 4802 } 4803 NS_WARNING_ASSERTION( 4804 NS_SUCCEEDED(rv), 4805 "AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode() " 4806 "failed to remove end node, but ignored"); 4807 } 4808 } 4809 4810 if (NS_WARN_IF(!range.IsPositioned())) { 4811 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 4812 } 4813 4814 if (MOZ_LIKELY(range.EndRef().IsInContentNode())) { 4815 AutoTrackDOMRange trackRange(aHTMLEditor.RangeUpdaterRef(), &range); 4816 nsresult rv = 4817 aHTMLEditor.EnsureNoFollowingUnnecessaryLineBreak(range.EndRef()); 4818 if (NS_FAILED(rv)) { 4819 NS_WARNING("HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed"); 4820 return Err(rv); 4821 } 4822 } 4823 if (NS_WARN_IF(!range.IsPositioned())) { 4824 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 4825 } 4826 4827 return NS_OK; 4828 } 4829 4830 nsresult 4831 HTMLEditor::AutoDeleteRangesHandler::DeleteNodeIfInvisibleAndEditableTextNode( 4832 HTMLEditor& aHTMLEditor, nsIContent& aContent) { 4833 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 4834 4835 Text* text = aContent.GetAsText(); 4836 if (!text) { 4837 return NS_OK; 4838 } 4839 4840 if (!HTMLEditUtils::IsRemovableFromParentNode(*text) || 4841 HTMLEditUtils::IsVisibleTextNode(*text)) { 4842 return NS_OK; 4843 } 4844 4845 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aContent); 4846 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 4847 "EditorBase::DeleteNodeWithTransaction() failed"); 4848 return rv; 4849 } 4850 4851 nsresult 4852 HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty( 4853 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint, 4854 const Element& aEditingHost) { 4855 MOZ_ASSERT(aPoint.IsSet()); 4856 MOZ_ASSERT(aHTMLEditor.mPlaceholderBatch); 4857 4858 const WSRunScanner scanner({}, aPoint, &aEditingHost); 4859 4860 // First, check there is visible contents before the point in current block. 4861 const WSScanResult prevVisibleThing = 4862 scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPoint); 4863 if (!prevVisibleThing.ReachedCurrentBlockBoundary() && 4864 !prevVisibleThing.ReachedInlineEditingHostBoundary()) { 4865 // If there is visible node before the point, we shouldn't remove the 4866 // parent block. 4867 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND; 4868 } 4869 MOZ_ASSERT(prevVisibleThing.ElementPtr()); 4870 if (&aEditingHost == prevVisibleThing.ElementPtr() || 4871 HTMLEditUtils::IsRemovableFromParentNode( 4872 *prevVisibleThing.ElementPtr())) { 4873 // If we reach editing host, there is no parent blocks which can be removed. 4874 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND; 4875 } 4876 if (HTMLEditUtils::IsTableCellOrCaptionElement( 4877 *prevVisibleThing.ElementPtr())) { 4878 // If we reach a <td>, <th> or <caption>, we shouldn't remove it even 4879 // becomes empty because removing such element changes the structure of 4880 // the <table>. 4881 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND; 4882 } 4883 4884 // Next, check there is visible contents after the point in current block. 4885 const WSScanResult nextVisibleThing = 4886 scanner.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(aPoint); 4887 if (nextVisibleThing.Failed()) { 4888 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed"); 4889 return NS_ERROR_FAILURE; 4890 } 4891 if (nextVisibleThing.ReachedBRElement()) { 4892 // XXX In my understanding, this is odd. The end reason may not be 4893 // same as the reached <br> element because the equality is 4894 // guaranteed only when ReachedCurrentBlockBoundary() returns true. 4895 // However, looks like that this code assumes that 4896 // GetEndReasonContent() returns the (or a) <br> element. 4897 // If the <br> element is visible, we shouldn't remove the parent block. 4898 if (HTMLEditUtils::IsVisibleBRElement(*nextVisibleThing.BRElementPtr())) { 4899 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND; 4900 } 4901 if (nextVisibleThing.BRElementPtr()->GetNextSibling()) { 4902 const WSScanResult nextVisibleThingAfterBR = 4903 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 4904 {}, EditorRawDOMPoint::After(*nextVisibleThing.BRElementPtr())); 4905 if (MOZ_UNLIKELY(nextVisibleThingAfterBR.Failed())) { 4906 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed"); 4907 return NS_ERROR_FAILURE; 4908 } 4909 if (!nextVisibleThingAfterBR.ReachedCurrentBlockBoundary() && 4910 !nextVisibleThingAfterBR.ReachedInlineEditingHostBoundary()) { 4911 // If we couldn't reach the block's end after the invisible <br>, 4912 // that means that there is visible content. 4913 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND; 4914 } 4915 } 4916 } else if (!nextVisibleThing.ReachedCurrentBlockBoundary() && 4917 !nextVisibleThing.ReachedInlineEditingHostBoundary()) { 4918 // If we couldn't reach the block's end, the block has visible content. 4919 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND; 4920 } 4921 4922 // Delete the parent block. 4923 const nsCOMPtr<nsIContent> nextSibling = 4924 prevVisibleThing.ElementPtr()->GetNextSibling(); 4925 const nsCOMPtr<nsINode> parentNode = 4926 prevVisibleThing.ElementPtr()->GetParentNode(); 4927 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction( 4928 // MOZ_KnownLive because of grabbed by prevVisibleThing. 4929 MOZ_KnownLive(*prevVisibleThing.ElementPtr())); 4930 if (NS_FAILED(rv)) { 4931 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 4932 return rv; 4933 } 4934 // If we reach editing host, return NS_OK. 4935 if (parentNode == &aEditingHost) { 4936 return NS_OK; 4937 } 4938 4939 // Otherwise, we need to check whether we're still in empty block or not. 4940 4941 // If the mutations in the document is observed by DevTools, the next point 4942 // may be now outside of editing host or editing hos has been changed. 4943 if (aHTMLEditor.MaybeNodeRemovalsObservedByDevTools()) { 4944 if (NS_WARN_IF(nextSibling && 4945 !nextSibling->IsInclusiveDescendantOf(&aEditingHost)) || 4946 NS_WARN_IF(!parentNode->IsInclusiveDescendantOf(&aEditingHost))) { 4947 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE; 4948 } 4949 Element* newEditingHost = aHTMLEditor.ComputeEditingHost(); 4950 if (NS_WARN_IF(!newEditingHost) || 4951 NS_WARN_IF(newEditingHost != &aEditingHost)) { 4952 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE; 4953 } 4954 if (NS_WARN_IF( 4955 !EditorUtils::IsDescendantOf(*parentNode, *newEditingHost))) { 4956 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE; 4957 } 4958 } 4959 4960 const EditorDOMPoint nextPoint = nextSibling 4961 ? EditorDOMPoint(nextSibling) 4962 : EditorDOMPoint::AtEndOf(parentNode); 4963 rv = DeleteParentBlocksWithTransactionIfEmpty(aHTMLEditor, nextPoint, 4964 aEditingHost); 4965 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 4966 "AutoDeleteRangesHandler::" 4967 "DeleteParentBlocksWithTransactionIfEmpty() failed"); 4968 return rv; 4969 } 4970 4971 nsresult 4972 HTMLEditor::AutoDeleteRangesHandler::ComputeRangeToDeleteRangeWithTransaction( 4973 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 4974 nsRange& aRangeToDelete, const Element& aEditingHost) const { 4975 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 4976 4977 const EditorBase::HowToHandleCollapsedRange howToHandleCollapsedRange = 4978 EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount); 4979 if (MOZ_UNLIKELY(aRangeToDelete.Collapsed() && 4980 howToHandleCollapsedRange == 4981 EditorBase::HowToHandleCollapsedRange::Ignore)) { 4982 return NS_SUCCESS_DOM_NO_OPERATION; 4983 } 4984 4985 // If it's not collapsed, `DeleteRangeTransaction::Create()` will be called 4986 // with it and `DeleteRangeTransaction` won't modify the range. 4987 if (!aRangeToDelete.Collapsed()) { 4988 return NS_OK; 4989 } 4990 4991 const auto ExtendRangeToSelectCharacterForward = 4992 [](nsRange& aRange, const EditorRawDOMPointInText& aCaretPoint) -> void { 4993 const CharacterDataBuffer& characterDataBuffer = 4994 aCaretPoint.ContainerAs<Text>()->DataBuffer(); 4995 if (!characterDataBuffer.GetLength()) { 4996 return; 4997 } 4998 if (characterDataBuffer.IsHighSurrogateFollowedByLowSurrogateAt( 4999 aCaretPoint.Offset())) { 5000 DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd( 5001 aCaretPoint.ContainerAs<Text>(), aCaretPoint.Offset(), 5002 aCaretPoint.ContainerAs<Text>(), aCaretPoint.Offset() + 2); 5003 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 5004 "nsRange::SetStartAndEnd() failed"); 5005 return; 5006 } 5007 DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd( 5008 aCaretPoint.ContainerAs<Text>(), aCaretPoint.Offset(), 5009 aCaretPoint.ContainerAs<Text>(), aCaretPoint.Offset() + 1); 5010 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 5011 "nsRange::SetStartAndEnd() failed"); 5012 }; 5013 const auto ExtendRangeToSelectCharacterBackward = 5014 [](nsRange& aRange, const EditorRawDOMPointInText& aCaretPoint) -> void { 5015 if (aCaretPoint.IsStartOfContainer()) { 5016 return; 5017 } 5018 const CharacterDataBuffer& characterDataBuffer = 5019 aCaretPoint.ContainerAs<Text>()->DataBuffer(); 5020 if (!characterDataBuffer.GetLength()) { 5021 return; 5022 } 5023 if (characterDataBuffer.IsLowSurrogateFollowingHighSurrogateAt( 5024 aCaretPoint.Offset() - 1)) { 5025 DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd( 5026 aCaretPoint.ContainerAs<Text>(), aCaretPoint.Offset() - 2, 5027 aCaretPoint.ContainerAs<Text>(), aCaretPoint.Offset()); 5028 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 5029 "nsRange::SetStartAndEnd() failed"); 5030 return; 5031 } 5032 DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd( 5033 aCaretPoint.ContainerAs<Text>(), aCaretPoint.Offset() - 1, 5034 aCaretPoint.ContainerAs<Text>(), aCaretPoint.Offset()); 5035 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 5036 "nsRange::SetStartAndEnd() failed"); 5037 }; 5038 5039 // In the other cases, `EditorBase::CreateTransactionForCollapsedRange()` 5040 // will handle the collapsed range. 5041 EditorRawDOMPoint caretPoint(aRangeToDelete.StartRef()); 5042 if (howToHandleCollapsedRange == 5043 EditorBase::HowToHandleCollapsedRange::ExtendBackward && 5044 caretPoint.IsStartOfContainer()) { 5045 nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent( 5046 *caretPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode}, 5047 BlockInlineCheck::Unused, &aEditingHost); 5048 if (!previousEditableContent) { 5049 return NS_OK; 5050 } 5051 if (!previousEditableContent->IsText()) { 5052 IgnoredErrorResult ignoredError; 5053 aRangeToDelete.SelectNode(*previousEditableContent, ignoredError); 5054 NS_WARNING_ASSERTION(!ignoredError.Failed(), 5055 "nsRange::SelectNode() failed"); 5056 return NS_OK; 5057 } 5058 5059 ExtendRangeToSelectCharacterBackward( 5060 aRangeToDelete, 5061 EditorRawDOMPointInText::AtEndOf(*previousEditableContent->AsText())); 5062 return NS_OK; 5063 } 5064 5065 if (howToHandleCollapsedRange == 5066 EditorBase::HowToHandleCollapsedRange::ExtendForward && 5067 caretPoint.IsEndOfContainer()) { 5068 nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent( 5069 *caretPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode}, 5070 BlockInlineCheck::Unused, &aEditingHost); 5071 if (!nextEditableContent) { 5072 return NS_OK; 5073 } 5074 5075 if (!nextEditableContent->IsText()) { 5076 IgnoredErrorResult ignoredError; 5077 aRangeToDelete.SelectNode(*nextEditableContent, ignoredError); 5078 NS_WARNING_ASSERTION(!ignoredError.Failed(), 5079 "nsRange::SelectNode() failed"); 5080 return NS_OK; 5081 } 5082 5083 ExtendRangeToSelectCharacterForward( 5084 aRangeToDelete, 5085 EditorRawDOMPointInText(nextEditableContent->AsText(), 0)); 5086 return NS_OK; 5087 } 5088 5089 if (caretPoint.IsInTextNode()) { 5090 if (howToHandleCollapsedRange == 5091 EditorBase::HowToHandleCollapsedRange::ExtendBackward) { 5092 ExtendRangeToSelectCharacterBackward( 5093 aRangeToDelete, 5094 EditorRawDOMPointInText(caretPoint.ContainerAs<Text>(), 5095 caretPoint.Offset())); 5096 return NS_OK; 5097 } 5098 ExtendRangeToSelectCharacterForward( 5099 aRangeToDelete, EditorRawDOMPointInText(caretPoint.ContainerAs<Text>(), 5100 caretPoint.Offset())); 5101 return NS_OK; 5102 } 5103 5104 nsIContent* editableContent = 5105 howToHandleCollapsedRange == 5106 EditorBase::HowToHandleCollapsedRange::ExtendBackward 5107 ? HTMLEditUtils::GetPreviousContent( 5108 caretPoint, {WalkTreeOption::IgnoreNonEditableNode}, 5109 BlockInlineCheck::Unused, &aEditingHost) 5110 : HTMLEditUtils::GetNextContent( 5111 caretPoint, {WalkTreeOption::IgnoreNonEditableNode}, 5112 BlockInlineCheck::Unused, &aEditingHost); 5113 if (!editableContent) { 5114 return NS_OK; 5115 } 5116 while (editableContent && editableContent->IsCharacterData() && 5117 !editableContent->Length()) { 5118 editableContent = 5119 howToHandleCollapsedRange == 5120 EditorBase::HowToHandleCollapsedRange::ExtendBackward 5121 ? HTMLEditUtils::GetPreviousContent( 5122 *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, 5123 BlockInlineCheck::Unused, &aEditingHost) 5124 : HTMLEditUtils::GetNextContent( 5125 *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, 5126 BlockInlineCheck::Unused, &aEditingHost); 5127 } 5128 if (!editableContent) { 5129 return NS_OK; 5130 } 5131 5132 if (!editableContent->IsText()) { 5133 IgnoredErrorResult ignoredError; 5134 aRangeToDelete.SelectNode(*editableContent, ignoredError); 5135 NS_WARNING_ASSERTION(!ignoredError.Failed(), 5136 "nsRange::SelectNode() failed, but ignored"); 5137 return NS_OK; 5138 } 5139 5140 if (howToHandleCollapsedRange == 5141 EditorBase::HowToHandleCollapsedRange::ExtendBackward) { 5142 ExtendRangeToSelectCharacterBackward( 5143 aRangeToDelete, 5144 EditorRawDOMPointInText::AtEndOf(*editableContent->AsText())); 5145 return NS_OK; 5146 } 5147 ExtendRangeToSelectCharacterForward( 5148 aRangeToDelete, EditorRawDOMPointInText(editableContent->AsText(), 0)); 5149 5150 return NS_OK; 5151 } 5152 5153 template <typename EditorDOMPointType> 5154 Result<CaretPoint, nsresult> HTMLEditor::DeleteTextAndTextNodesWithTransaction( 5155 const EditorDOMPointType& aStartPoint, const EditorDOMPointType& aEndPoint, 5156 TreatEmptyTextNodes aTreatEmptyTextNodes) { 5157 if (NS_WARN_IF(!aStartPoint.IsSet()) || NS_WARN_IF(!aEndPoint.IsSet())) { 5158 return Err(NS_ERROR_INVALID_ARG); 5159 } 5160 5161 // MOOSE: this routine needs to be modified to preserve the integrity of the 5162 // wsFragment info. 5163 5164 if (aStartPoint == aEndPoint) { 5165 // Nothing to delete 5166 return CaretPoint(EditorDOMPoint()); 5167 } 5168 5169 RefPtr<Element> editingHost = ComputeEditingHost(); 5170 auto DeleteEmptyContentNodeWithTransaction = 5171 [this, &aTreatEmptyTextNodes, &editingHost](nsIContent& aContent) 5172 MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> nsresult { 5173 OwningNonNull<nsIContent> nodeToRemove = aContent; 5174 if (aTreatEmptyTextNodes == 5175 TreatEmptyTextNodes::RemoveAllEmptyInlineAncestors) { 5176 Element* emptyParentElementToRemove = 5177 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 5178 nodeToRemove, BlockInlineCheck::UseComputedDisplayOutsideStyle, 5179 editingHost); 5180 if (emptyParentElementToRemove) { 5181 nodeToRemove = *emptyParentElementToRemove; 5182 } 5183 } 5184 nsresult rv = DeleteNodeWithTransaction(nodeToRemove); 5185 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 5186 "EditorBase::DeleteNodeWithTransaction() failed"); 5187 return rv; 5188 }; 5189 5190 if (aStartPoint.GetContainer() == aEndPoint.GetContainer() && 5191 aStartPoint.IsInTextNode()) { 5192 if (aTreatEmptyTextNodes != 5193 TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries && 5194 aStartPoint.IsStartOfContainer() && aEndPoint.IsEndOfContainer()) { 5195 nsresult rv = DeleteEmptyContentNodeWithTransaction( 5196 MOZ_KnownLive(*aStartPoint.template ContainerAs<Text>())); 5197 if (NS_FAILED(rv)) { 5198 NS_WARNING("deleteEmptyContentNodeWithTransaction() failed"); 5199 return Err(rv); 5200 } 5201 return CaretPoint(EditorDOMPoint()); 5202 } 5203 RefPtr<Text> textNode = aStartPoint.template ContainerAs<Text>(); 5204 Result<CaretPoint, nsresult> caretPointOrError = 5205 DeleteTextWithTransaction(*textNode, aStartPoint.Offset(), 5206 aEndPoint.Offset() - aStartPoint.Offset()); 5207 NS_WARNING_ASSERTION(caretPointOrError.isOk(), 5208 "HTMLEditor::DeleteTextWithTransaction() failed"); 5209 return caretPointOrError; 5210 } 5211 5212 RefPtr<nsRange> range = 5213 nsRange::Create(aStartPoint.ToRawRangeBoundary(), 5214 aEndPoint.ToRawRangeBoundary(), IgnoreErrors()); 5215 if (!range) { 5216 NS_WARNING("nsRange::Create() failed"); 5217 return Err(NS_ERROR_FAILURE); 5218 } 5219 5220 // Collect editable text nodes in the given range. 5221 AutoTArray<OwningNonNull<Text>, 16> arrayOfTextNodes; 5222 DOMIterator iter; 5223 if (NS_FAILED(iter.Init(*range))) { 5224 return CaretPoint(EditorDOMPoint()); // Nothing to delete in the range. 5225 } 5226 iter.AppendNodesToArray( 5227 +[](nsINode& aNode, void*) { 5228 MOZ_ASSERT(aNode.IsText()); 5229 return HTMLEditUtils::IsSimplyEditableNode(aNode); 5230 }, 5231 arrayOfTextNodes); 5232 EditorDOMPoint pointToPutCaret; 5233 for (OwningNonNull<Text>& textNode : arrayOfTextNodes) { 5234 if (textNode == aStartPoint.GetContainer()) { 5235 if (aStartPoint.IsEndOfContainer()) { 5236 continue; 5237 } 5238 if (aStartPoint.IsStartOfContainer() && 5239 aTreatEmptyTextNodes != 5240 TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries) { 5241 AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(), 5242 &pointToPutCaret); 5243 nsresult rv = DeleteEmptyContentNodeWithTransaction( 5244 MOZ_KnownLive(*aStartPoint.template ContainerAs<Text>())); 5245 if (NS_FAILED(rv)) { 5246 NS_WARNING("DeleteEmptyContentNodeWithTransaction() failed"); 5247 return Err(rv); 5248 } 5249 continue; 5250 } 5251 AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(), 5252 &pointToPutCaret); 5253 Result<CaretPoint, nsresult> caretPointOrError = 5254 DeleteTextWithTransaction(MOZ_KnownLive(textNode), 5255 aStartPoint.Offset(), 5256 textNode->Length() - aStartPoint.Offset()); 5257 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 5258 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed"); 5259 return caretPointOrError; 5260 } 5261 trackPointToPutCaret.FlushAndStopTracking(); 5262 caretPointOrError.unwrap().MoveCaretPointTo( 5263 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 5264 continue; 5265 } 5266 5267 if (textNode == aEndPoint.GetContainer()) { 5268 if (aEndPoint.IsStartOfContainer()) { 5269 break; 5270 } 5271 if (aEndPoint.IsEndOfContainer() && 5272 aTreatEmptyTextNodes != 5273 TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries) { 5274 AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(), 5275 &pointToPutCaret); 5276 nsresult rv = DeleteEmptyContentNodeWithTransaction( 5277 MOZ_KnownLive(*aEndPoint.template ContainerAs<Text>())); 5278 if (NS_FAILED(rv)) { 5279 NS_WARNING("DeleteEmptyContentNodeWithTransaction() failed"); 5280 return Err(rv); 5281 } 5282 trackPointToPutCaret.FlushAndStopTracking(); 5283 return CaretPoint(std::move(pointToPutCaret)); 5284 } 5285 AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(), 5286 &pointToPutCaret); 5287 Result<CaretPoint, nsresult> caretPointOrError = 5288 DeleteTextWithTransaction(MOZ_KnownLive(textNode), 0, 5289 aEndPoint.Offset()); 5290 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 5291 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed"); 5292 return caretPointOrError; 5293 } 5294 trackPointToPutCaret.FlushAndStopTracking(); 5295 caretPointOrError.unwrap().MoveCaretPointTo( 5296 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 5297 return CaretPoint(std::move(pointToPutCaret)); 5298 } 5299 5300 nsresult rv = 5301 DeleteEmptyContentNodeWithTransaction(MOZ_KnownLive(textNode)); 5302 if (NS_FAILED(rv)) { 5303 NS_WARNING("DeleteEmptyContentNodeWithTransaction() failed"); 5304 return Err(rv); 5305 } 5306 } 5307 5308 return CaretPoint(std::move(pointToPutCaret)); 5309 } 5310 5311 Result<EditorDOMPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler:: 5312 AutoBlockElementsJoiner::JoinNodesDeepWithTransaction( 5313 HTMLEditor& aHTMLEditor, nsIContent& aLeftContent, 5314 nsIContent& aRightContent) { 5315 // While the rightmost children and their descendants of the left node match 5316 // the leftmost children and their descendants of the right node, join them 5317 // up. 5318 5319 nsCOMPtr<nsIContent> leftContentToJoin = &aLeftContent; 5320 nsCOMPtr<nsIContent> rightContentToJoin = &aRightContent; 5321 nsCOMPtr<nsINode> parentNode = aRightContent.GetParentNode(); 5322 5323 EditorDOMPoint ret; 5324 while (leftContentToJoin && rightContentToJoin && parentNode && 5325 HTMLEditUtils::CanContentsBeJoined(*leftContentToJoin, 5326 *rightContentToJoin)) { 5327 // Do the join 5328 Result<JoinNodesResult, nsresult> joinNodesResult = 5329 aHTMLEditor.JoinNodesWithTransaction(*leftContentToJoin, 5330 *rightContentToJoin); 5331 if (MOZ_UNLIKELY(joinNodesResult.isErr())) { 5332 NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed"); 5333 return joinNodesResult.propagateErr(); 5334 } 5335 5336 ret = joinNodesResult.inspect().AtJoinedPoint<EditorDOMPoint>(); 5337 if (NS_WARN_IF(!ret.IsSet())) { 5338 return Err(NS_ERROR_FAILURE); 5339 } 5340 5341 if (parentNode->IsText()) { 5342 // We've joined all the way down to text nodes, we're done! 5343 return ret; 5344 } 5345 5346 // Get new left and right nodes, and begin anew 5347 rightContentToJoin = ret.GetCurrentChildAtOffset(); 5348 if (rightContentToJoin) { 5349 leftContentToJoin = rightContentToJoin->GetPreviousSibling(); 5350 } else { 5351 leftContentToJoin = nullptr; 5352 } 5353 5354 // Skip over non-editable nodes 5355 while (leftContentToJoin && !EditorUtils::IsEditableContent( 5356 *leftContentToJoin, EditorType::HTML)) { 5357 leftContentToJoin = leftContentToJoin->GetPreviousSibling(); 5358 } 5359 if (!leftContentToJoin) { 5360 return ret; 5361 } 5362 5363 while (rightContentToJoin && !EditorUtils::IsEditableContent( 5364 *rightContentToJoin, EditorType::HTML)) { 5365 rightContentToJoin = rightContentToJoin->GetNextSibling(); 5366 } 5367 if (!rightContentToJoin) { 5368 return ret; 5369 } 5370 } 5371 5372 if (!ret.IsSet()) { 5373 NS_WARNING("HTMLEditor::JoinNodesDeepWithTransaction() joined no contents"); 5374 return Err(NS_ERROR_FAILURE); 5375 } 5376 return ret; 5377 } 5378 5379 Result<bool, nsresult> HTMLEditor::AutoDeleteRangesHandler:: 5380 AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner::Prepare( 5381 const HTMLEditor& aHTMLEditor, const Element& aEditingHost) { 5382 mLeftBlockElement = HTMLEditUtils::GetInclusiveAncestorElement( 5383 mInclusiveDescendantOfLeftBlockElement, 5384 HTMLEditUtils::ClosestEditableBlockElementExceptHRElement, 5385 BlockInlineCheck::UseComputedDisplayOutsideStyle); 5386 mRightBlockElement = HTMLEditUtils::GetInclusiveAncestorElement( 5387 mInclusiveDescendantOfRightBlockElement, 5388 HTMLEditUtils::ClosestEditableBlockElementExceptHRElement, 5389 BlockInlineCheck::UseComputedDisplayOutsideStyle); 5390 5391 if (NS_WARN_IF(!IsSet())) { 5392 mCanJoinBlocks = false; 5393 return Err(NS_ERROR_UNEXPECTED); 5394 } 5395 5396 // Don't join the blocks if both of them are basic structure of the HTML 5397 // document (Note that `<body>` can be joined with its children). 5398 if (mLeftBlockElement->IsAnyOfHTMLElements(nsGkAtoms::html, nsGkAtoms::head, 5399 nsGkAtoms::body) && 5400 mRightBlockElement->IsAnyOfHTMLElements(nsGkAtoms::html, nsGkAtoms::head, 5401 nsGkAtoms::body)) { 5402 mCanJoinBlocks = false; 5403 return false; 5404 } 5405 5406 if (HTMLEditUtils::IsAnyTableElementExceptColumnElement(*mLeftBlockElement) || 5407 HTMLEditUtils::IsAnyTableElementExceptColumnElement( 5408 *mRightBlockElement)) { 5409 // Do not try to merge table elements, cancel the deletion. 5410 mCanJoinBlocks = false; 5411 return false; 5412 } 5413 5414 // Bail if both blocks the same 5415 if (IsSameBlockElement()) { 5416 mCanJoinBlocks = true; // XXX Anyway, Run() will ingore this case. 5417 mFallbackToDeleteLeafContent = true; 5418 return true; 5419 } 5420 5421 // Joining a list item to its parent is a NOP. 5422 if (HTMLEditUtils::IsListElement(*mLeftBlockElement) && 5423 HTMLEditUtils::IsListItemElement(*mRightBlockElement) && 5424 mRightBlockElement->GetParentNode() == mLeftBlockElement) { 5425 mCanJoinBlocks = false; 5426 return true; 5427 } 5428 5429 // Special rule here: if we are trying to join list items, and they are in 5430 // different lists, join the lists instead. 5431 if (HTMLEditUtils::IsListItemElement(*mLeftBlockElement) && 5432 HTMLEditUtils::IsListItemElement(*mRightBlockElement)) { 5433 // XXX leftListElement and/or rightListElement may be not list elements. 5434 Element* leftListElement = mLeftBlockElement->GetParentElement(); 5435 Element* rightListElement = mRightBlockElement->GetParentElement(); 5436 EditorDOMPoint atChildInBlock; 5437 if (leftListElement && rightListElement && 5438 leftListElement != rightListElement && 5439 !EditorUtils::IsDescendantOf(*leftListElement, *mRightBlockElement, 5440 &atChildInBlock) && 5441 !EditorUtils::IsDescendantOf(*rightListElement, *mLeftBlockElement, 5442 &atChildInBlock)) { 5443 // There are some special complications if the lists are descendants of 5444 // the other lists' items. Note that it is okay for them to be 5445 // descendants of the other lists themselves, which is the usual case for 5446 // sublists in our implementation. 5447 MOZ_DIAGNOSTIC_ASSERT(!atChildInBlock.IsSet()); 5448 mLeftBlockElement = leftListElement; 5449 mRightBlockElement = rightListElement; 5450 mNewListElementTagNameOfRightListElement = 5451 Some(leftListElement->NodeInfo()->NameAtom()); 5452 } 5453 } 5454 5455 if (!EditorUtils::IsDescendantOf(*mLeftBlockElement, *mRightBlockElement, 5456 &mPointContainingTheOtherBlockElement)) { 5457 (void)EditorUtils::IsDescendantOf(*mRightBlockElement, *mLeftBlockElement, 5458 &mPointContainingTheOtherBlockElement); 5459 } 5460 5461 if (mPointContainingTheOtherBlockElement.GetContainer() == 5462 mRightBlockElement) { 5463 mPrecedingInvisibleBRElement = 5464 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound( 5465 {WSRunScanner::Option::OnlyEditableNodes}, 5466 EditorDOMPoint::AtEndOf(mLeftBlockElement)); 5467 // `WhiteSpaceVisibilityKeeper:: 5468 // MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()` 5469 // returns ignored when: 5470 // - No preceding invisible `<br>` element and 5471 // - mNewListElementTagNameOfRightListElement is nothing and 5472 // - There is no content to move from right block element. 5473 if (!mPrecedingInvisibleBRElement) { 5474 if (CanMergeLeftAndRightBlockElements()) { 5475 // Always marked as handled in this case. 5476 mFallbackToDeleteLeafContent = false; 5477 } else { 5478 // Marked as handled only when it actually moves a content node. 5479 Result<bool, nsresult> firstLineHasContent = 5480 AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine( 5481 mPointContainingTheOtherBlockElement 5482 .NextPoint<EditorDOMPoint>(), 5483 aEditingHost); 5484 mFallbackToDeleteLeafContent = 5485 firstLineHasContent.isOk() && !firstLineHasContent.inspect(); 5486 } 5487 } else { 5488 // Marked as handled when deleting the invisible `<br>` element. 5489 mFallbackToDeleteLeafContent = false; 5490 } 5491 } else if (mPointContainingTheOtherBlockElement.GetContainer() == 5492 mLeftBlockElement) { 5493 mPrecedingInvisibleBRElement = 5494 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound( 5495 {WSRunScanner::Option::OnlyEditableNodes}, 5496 mPointContainingTheOtherBlockElement); 5497 // `WhiteSpaceVisibilityKeeper:: 5498 // MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()` 5499 // returns ignored when: 5500 // - No preceding invisible `<br>` element and 5501 // - mNewListElementTagNameOfRightListElement is some and 5502 // - The right block element has no children 5503 // or, 5504 // - No preceding invisible `<br>` element and 5505 // - mNewListElementTagNameOfRightListElement is nothing and 5506 // - There is no content to move from right block element. 5507 if (!mPrecedingInvisibleBRElement) { 5508 if (CanMergeLeftAndRightBlockElements()) { 5509 // Marked as handled only when it actualy moves a content node. 5510 Result<bool, nsresult> rightBlockHasContent = 5511 aHTMLEditor.CanMoveChildren(*mRightBlockElement, 5512 *mLeftBlockElement); 5513 mFallbackToDeleteLeafContent = 5514 rightBlockHasContent.isOk() && !rightBlockHasContent.inspect(); 5515 } else { 5516 // Marked as handled only when it actually moves a content node. 5517 Result<bool, nsresult> firstLineHasContent = 5518 AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine( 5519 EditorDOMPoint(mRightBlockElement, 0u), aEditingHost); 5520 mFallbackToDeleteLeafContent = 5521 firstLineHasContent.isOk() && !firstLineHasContent.inspect(); 5522 } 5523 } else { 5524 // Marked as handled when deleting the invisible `<br>` element. 5525 mFallbackToDeleteLeafContent = false; 5526 } 5527 } else { 5528 mPrecedingInvisibleBRElement = 5529 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound( 5530 {WSRunScanner::Option::OnlyEditableNodes}, 5531 EditorDOMPoint::AtEndOf(mLeftBlockElement)); 5532 // `WhiteSpaceVisibilityKeeper:: 5533 // MergeFirstLineOfRightBlockElementIntoLeftBlockElement()` always 5534 // return "handled". 5535 mFallbackToDeleteLeafContent = false; 5536 } 5537 5538 mCanJoinBlocks = true; 5539 return true; 5540 } 5541 5542 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner:: 5543 AutoInclusiveAncestorBlockElementsJoiner::ComputeRangeToDelete( 5544 const HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCaretPoint, 5545 nsRange& aRangeToDelete) const { 5546 MOZ_ASSERT(mLeftBlockElement); 5547 MOZ_ASSERT(mRightBlockElement); 5548 5549 if (IsSameBlockElement()) { 5550 if (!aCaretPoint.IsSet()) { 5551 return NS_OK; // The ranges are not collapsed, keep them as-is. 5552 } 5553 nsresult rv = aRangeToDelete.CollapseTo(aCaretPoint.ToRawRangeBoundary()); 5554 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::CollapseTo() failed"); 5555 return rv; 5556 } 5557 5558 EditorDOMPoint pointContainingTheOtherBlock; 5559 if (!EditorUtils::IsDescendantOf(*mLeftBlockElement, *mRightBlockElement, 5560 &pointContainingTheOtherBlock)) { 5561 (void)EditorUtils::IsDescendantOf(*mRightBlockElement, *mLeftBlockElement, 5562 &pointContainingTheOtherBlock); 5563 } 5564 EditorDOMRange range = 5565 WSRunScanner::GetRangeForDeletingBlockElementBoundaries( 5566 {WSRunScanner::Option::OnlyEditableNodes}, *mLeftBlockElement, 5567 *mRightBlockElement, pointContainingTheOtherBlock); 5568 if (!range.IsPositioned()) { 5569 NS_WARNING( 5570 "WSRunScanner::GetRangeForDeletingBlockElementBoundaries() failed"); 5571 return NS_ERROR_FAILURE; 5572 } 5573 if (!aCaretPoint.IsSet()) { 5574 // Don't shrink the original range. 5575 bool noNeedToChangeStart = false; 5576 const EditorDOMPoint atStart(aRangeToDelete.StartRef()); 5577 if (atStart.IsBefore(range.StartRef())) { 5578 // If the range starts from end of a container, and computed block 5579 // boundaries range starts from an invisible `<br>` element, we 5580 // may need to shrink the range. 5581 Element* editingHost = aHTMLEditor.ComputeEditingHost(); 5582 NS_WARNING_ASSERTION(editingHost, "There was no editing host"); 5583 nsIContent* nextContent = 5584 atStart.IsEndOfContainer() && range.StartRef().GetChild() && 5585 HTMLEditUtils::IsInvisibleBRElement( 5586 *range.StartRef().GetChild()) 5587 ? HTMLEditUtils::GetNextContent( 5588 *atStart.ContainerAs<nsIContent>(), 5589 {WalkTreeOption::IgnoreDataNodeExceptText, 5590 WalkTreeOption::StopAtBlockBoundary}, 5591 BlockInlineCheck::UseComputedDisplayOutsideStyle, 5592 editingHost) 5593 : nullptr; 5594 if (!nextContent || nextContent != range.StartRef().GetChild()) { 5595 noNeedToChangeStart = true; 5596 range.SetStart(EditorRawDOMPoint(aRangeToDelete.StartRef())); 5597 } 5598 } 5599 if (range.EndRef().IsBefore(EditorRawDOMPoint(aRangeToDelete.EndRef()))) { 5600 if (noNeedToChangeStart) { 5601 return NS_OK; // We don't need to modify the range. 5602 } 5603 range.SetEnd(EditorRawDOMPoint(aRangeToDelete.EndRef())); 5604 } 5605 } 5606 nsresult rv = 5607 aRangeToDelete.SetStartAndEnd(range.StartRef().ToRawRangeBoundary(), 5608 range.EndRef().ToRawRangeBoundary()); 5609 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 5610 "AutoClonedRangeArray::SetStartAndEnd() failed"); 5611 return rv; 5612 } 5613 5614 Result<DeleteRangeResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: 5615 AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner::Run( 5616 HTMLEditor& aHTMLEditor, const Element& aEditingHost) { 5617 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 5618 MOZ_ASSERT(mLeftBlockElement); 5619 MOZ_ASSERT(mRightBlockElement); 5620 5621 if (IsSameBlockElement() || !mCanJoinBlocks) { 5622 return DeleteRangeResult::IgnoredResult(); 5623 } 5624 5625 const auto ConvertMoveNodeResultToDeleteRangeResult = 5626 [](const EditorDOMPoint& aStartOfRightContent, 5627 MoveNodeResult&& aMoveNodeResult, const Element& aEditingHost) 5628 MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT 5629 -> Result<DeleteRangeResult, nsresult> { 5630 aMoveNodeResult.IgnoreCaretPointSuggestion(); 5631 if (MOZ_UNLIKELY(aMoveNodeResult.Ignored())) { 5632 return DeleteRangeResult::IgnoredResult(); 5633 } 5634 EditorDOMRange movedLineRange = aMoveNodeResult.UnwrapMovedContentRange(); 5635 EditorDOMPoint maybeDeepStartOfRightContent; 5636 if (MOZ_LIKELY(movedLineRange.IsPositioned())) { 5637 if (const Element* const firstMovedElement = 5638 movedLineRange.StartRef().GetChildAs<Element>()) { 5639 maybeDeepStartOfRightContent = 5640 HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>( 5641 *firstMovedElement, 5642 {EditablePointOption::RecognizeInvisibleWhiteSpaces, 5643 EditablePointOption::StopAtComment}); 5644 } else { 5645 maybeDeepStartOfRightContent = movedLineRange.StartRef(); 5646 } 5647 } else { 5648 maybeDeepStartOfRightContent = aStartOfRightContent; 5649 } 5650 if (NS_WARN_IF( 5651 !maybeDeepStartOfRightContent.IsSetAndValidInComposedDoc())) { 5652 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 5653 } 5654 // We should put caret to end of preceding text node if there is. 5655 // Then, users can type text into it like the other browsers. 5656 auto pointToPutCaret = [&]() -> EditorDOMPoint { 5657 const WSScanResult maybePreviousText = 5658 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 5659 {}, maybeDeepStartOfRightContent, &aEditingHost); 5660 if (maybePreviousText.IsContentEditable() && 5661 maybePreviousText.InVisibleOrCollapsibleCharacters()) { 5662 return maybePreviousText.PointAfterReachedContent<EditorDOMPoint>(); 5663 } 5664 return maybeDeepStartOfRightContent; 5665 }(); 5666 return DeleteRangeResult(std::move(movedLineRange), 5667 std::move(pointToPutCaret)); 5668 }; 5669 5670 // If the left block element is in the right block element, move the hard 5671 // line including the right block element to end of the left block. 5672 // However, if we are merging list elements, we don't join them. 5673 if (mPointContainingTheOtherBlockElement.GetContainer() == 5674 mRightBlockElement) { 5675 EditorDOMPoint startOfRightContent = 5676 mPointContainingTheOtherBlockElement.NextPoint(); 5677 if (const Element* const element = 5678 startOfRightContent.GetChildAs<Element>()) { 5679 startOfRightContent = 5680 HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>( 5681 *element, {EditablePointOption::RecognizeInvisibleWhiteSpaces, 5682 EditablePointOption::StopAtComment}); 5683 } 5684 AutoTrackDOMPoint trackStartOfRightBlock(aHTMLEditor.RangeUpdaterRef(), 5685 &startOfRightContent); 5686 Result<MoveNodeResult, nsresult> moveFirstLineResult = 5687 WhiteSpaceVisibilityKeeper:: 5688 MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement( 5689 aHTMLEditor, MOZ_KnownLive(*mLeftBlockElement), 5690 MOZ_KnownLive(*mRightBlockElement), 5691 mPointContainingTheOtherBlockElement, 5692 mNewListElementTagNameOfRightListElement, 5693 MOZ_KnownLive(mPrecedingInvisibleBRElement), aEditingHost); 5694 if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) { 5695 NS_WARNING( 5696 "WhiteSpaceVisibilityKeeper::" 5697 "MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement() " 5698 "failed"); 5699 return moveFirstLineResult.propagateErr(); 5700 } 5701 5702 trackStartOfRightBlock.FlushAndStopTracking(); 5703 return ConvertMoveNodeResultToDeleteRangeResult( 5704 startOfRightContent, moveFirstLineResult.unwrap(), aEditingHost); 5705 } 5706 5707 // If the right block element is in the left block element: 5708 // - move list item elements in the right block element to where the left 5709 // list element is 5710 // - or first hard line in the right block element to where: 5711 // - the left block element is. 5712 // - or the given left content in the left block is. 5713 if (mPointContainingTheOtherBlockElement.GetContainer() == 5714 mLeftBlockElement) { 5715 EditorDOMPoint startOfRightContent = 5716 HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>( 5717 *mRightBlockElement, 5718 {EditablePointOption::RecognizeInvisibleWhiteSpaces, 5719 EditablePointOption::StopAtComment}); 5720 AutoTrackDOMPoint trackStartOfRightBlock(aHTMLEditor.RangeUpdaterRef(), 5721 &startOfRightContent); 5722 Result<MoveNodeResult, nsresult> moveFirstLineResult = 5723 WhiteSpaceVisibilityKeeper:: 5724 MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement( 5725 aHTMLEditor, MOZ_KnownLive(*mLeftBlockElement), 5726 MOZ_KnownLive(*mRightBlockElement), 5727 mPointContainingTheOtherBlockElement, 5728 MOZ_KnownLive(*mInclusiveDescendantOfLeftBlockElement), 5729 mNewListElementTagNameOfRightListElement, 5730 MOZ_KnownLive(mPrecedingInvisibleBRElement), aEditingHost); 5731 if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) { 5732 NS_WARNING( 5733 "WhiteSpaceVisibilityKeeper::" 5734 "MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement() " 5735 "failed"); 5736 return moveFirstLineResult.propagateErr(); 5737 } 5738 trackStartOfRightBlock.FlushAndStopTracking(); 5739 return ConvertMoveNodeResultToDeleteRangeResult( 5740 startOfRightContent, moveFirstLineResult.unwrap(), aEditingHost); 5741 } 5742 5743 // Normal case. Blocks are siblings, or at least close enough. An example 5744 // of the latter is <p>paragraph</p><ul><li>one<li>two<li>three</ul>. The 5745 // first li and the p are not true siblings, but we still want to join them 5746 // if you backspace from li into p. 5747 MOZ_ASSERT(!mPointContainingTheOtherBlockElement.IsSet()); 5748 EditorDOMPoint startOfRightContent = 5749 HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>( 5750 *mRightBlockElement, 5751 {EditablePointOption::RecognizeInvisibleWhiteSpaces, 5752 EditablePointOption::StopAtComment}); 5753 AutoTrackDOMPoint trackStartOfRightBlock(aHTMLEditor.RangeUpdaterRef(), 5754 &startOfRightContent); 5755 Result<MoveNodeResult, nsresult> moveFirstLineResult = 5756 WhiteSpaceVisibilityKeeper:: 5757 MergeFirstLineOfRightBlockElementIntoLeftBlockElement( 5758 aHTMLEditor, MOZ_KnownLive(*mLeftBlockElement), 5759 MOZ_KnownLive(*mRightBlockElement), 5760 mNewListElementTagNameOfRightListElement, 5761 MOZ_KnownLive(mPrecedingInvisibleBRElement), aEditingHost); 5762 if (MOZ_UNLIKELY(moveFirstLineResult.isErr())) { 5763 NS_WARNING( 5764 "WhiteSpaceVisibilityKeeper::" 5765 "MergeFirstLineOfRightBlockElementIntoLeftBlockElement() failed"); 5766 return moveFirstLineResult.propagateErr(); 5767 } 5768 trackStartOfRightBlock.FlushAndStopTracking(); 5769 return ConvertMoveNodeResultToDeleteRangeResult( 5770 startOfRightContent, moveFirstLineResult.unwrap(), aEditingHost); 5771 } 5772 5773 // static 5774 Result<bool, nsresult> 5775 HTMLEditor::AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine( 5776 const EditorDOMPoint& aPointInHardLine, const Element& aEditingHost) { 5777 if (NS_WARN_IF(!aPointInHardLine.IsSet()) || 5778 NS_WARN_IF(aPointInHardLine.IsInNativeAnonymousSubtree())) { 5779 return Err(NS_ERROR_INVALID_ARG); 5780 } 5781 5782 RefPtr<nsRange> oneLineRange = AutoClonedRangeArray:: 5783 CreateRangeWrappingStartAndEndLinesContainingBoundaries( 5784 aPointInHardLine, aPointInHardLine, 5785 EditSubAction::eMergeBlockContents, 5786 BlockInlineCheck::UseComputedDisplayOutsideStyle, aEditingHost); 5787 if (!oneLineRange || oneLineRange->Collapsed() || 5788 !oneLineRange->IsPositioned() || 5789 !oneLineRange->GetStartContainer()->IsContent() || 5790 !oneLineRange->GetEndContainer()->IsContent()) { 5791 return false; 5792 } 5793 5794 // If there is only a padding `<br>` element in a empty block, it's selected 5795 // by `UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement()`. 5796 // However, it won't be moved. Although it'll be deleted, 5797 // AutoMoveOneLineHandler returns "ignored". Therefore, we should return 5798 // `false` in this case. 5799 if (nsIContent* childContent = oneLineRange->GetChildAtStartOffset()) { 5800 if (childContent->IsHTMLElement(nsGkAtoms::br) && 5801 childContent->GetParent()) { 5802 if (const Element* blockElement = 5803 HTMLEditUtils::GetInclusiveAncestorElement( 5804 *childContent->GetParent(), 5805 HTMLEditUtils::ClosestBlockElement, 5806 BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 5807 if (HTMLEditUtils::IsEmptyNode( 5808 *blockElement, 5809 {EmptyCheckOption::TreatNonEditableContentAsInvisible})) { 5810 return false; 5811 } 5812 } 5813 } 5814 } 5815 5816 EditorRawDOMPoint startPoint(oneLineRange->StartRef()); 5817 EditorRawDOMPoint endPoint(oneLineRange->EndRef()); 5818 // If the range contains only block start boundaries, there is no content to 5819 // move. 5820 if (nsIContent* const startContent = startPoint.GetChild()) { 5821 if (HTMLEditUtils::IsBlockElement( 5822 *startContent, BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 5823 const WSScanResult prevThing = 5824 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary({}, endPoint); 5825 if (prevThing.ReachedCurrentBlockBoundary() && 5826 prevThing.ElementPtr()->IsInclusiveDescendantOf(startContent)) { 5827 return false; 5828 } 5829 } 5830 } 5831 5832 nsINode* commonAncestor = oneLineRange->GetClosestCommonInclusiveAncestor(); 5833 // Currently, we move non-editable content nodes too. 5834 if (!startPoint.IsEndOfContainer()) { 5835 return true; 5836 } 5837 if (!endPoint.IsStartOfContainer()) { 5838 return true; 5839 } 5840 if (startPoint.GetContainer() != commonAncestor) { 5841 while (true) { 5842 EditorRawDOMPoint pointInParent(startPoint.GetContainerAs<nsIContent>()); 5843 if (NS_WARN_IF(!pointInParent.IsInContentNode())) { 5844 return Err(NS_ERROR_FAILURE); 5845 } 5846 if (pointInParent.GetContainer() == commonAncestor) { 5847 startPoint = pointInParent; 5848 break; 5849 } 5850 if (!pointInParent.IsEndOfContainer()) { 5851 return true; 5852 } 5853 } 5854 } 5855 if (endPoint.GetContainer() != commonAncestor) { 5856 while (true) { 5857 EditorRawDOMPoint pointInParent(endPoint.GetContainerAs<nsIContent>()); 5858 if (NS_WARN_IF(!pointInParent.IsInContentNode())) { 5859 return Err(NS_ERROR_FAILURE); 5860 } 5861 if (pointInParent.GetContainer() == commonAncestor) { 5862 endPoint = pointInParent; 5863 break; 5864 } 5865 if (!pointInParent.IsStartOfContainer()) { 5866 return true; 5867 } 5868 } 5869 } 5870 // If start point and end point in the common ancestor are direct siblings, 5871 // there is no content to move or delete. 5872 // E.g., `<b>abc<br>[</b><i>]<br>def</i>`. 5873 return startPoint.GetNextSiblingOfChild() != endPoint.GetChild(); 5874 } 5875 5876 nsresult HTMLEditor::AutoMoveOneLineHandler::Prepare( 5877 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointInHardLine, 5878 const Element& aEditingHost) { 5879 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 5880 MOZ_ASSERT(aPointInHardLine.IsInContentNode()); 5881 MOZ_ASSERT(mPointToInsert.IsSetAndValid()); 5882 5883 MOZ_LOG(gOneLineMoverLog, LogLevel::Info, 5884 ("Prepare(aHTMLEditor=%p, aPointInHardLine=%s, aEditingHost=%s), " 5885 "mPointToInsert=%s, mMoveToEndOfContainer=%s", 5886 &aHTMLEditor, ToString(aPointInHardLine).c_str(), 5887 ToString(aEditingHost).c_str(), ToString(mPointToInsert).c_str(), 5888 ForceMoveToEndOfContainer() ? "MoveToEndOfContainer::Yes" 5889 : "MoveToEndOfContainer::No")); 5890 5891 if (NS_WARN_IF(mPointToInsert.IsInNativeAnonymousSubtree())) { 5892 MOZ_LOG( 5893 gOneLineMoverLog, LogLevel::Error, 5894 ("Failed because mPointToInsert was in a native anonymous subtree")); 5895 return Err(NS_ERROR_INVALID_ARG); 5896 } 5897 5898 mSrcInclusiveAncestorBlock = 5899 aPointInHardLine.IsInContentNode() 5900 ? HTMLEditUtils::GetInclusiveAncestorElement( 5901 *aPointInHardLine.ContainerAs<nsIContent>(), 5902 HTMLEditUtils::ClosestBlockElement, 5903 BlockInlineCheck::UseComputedDisplayOutsideStyle) 5904 : nullptr; 5905 mDestInclusiveAncestorBlock = 5906 mPointToInsert.IsInContentNode() 5907 ? HTMLEditUtils::GetInclusiveAncestorElement( 5908 *mPointToInsert.ContainerAs<nsIContent>(), 5909 HTMLEditUtils::ClosestBlockElement, 5910 BlockInlineCheck::UseComputedDisplayOutsideStyle) 5911 : nullptr; 5912 mMovingToParentBlock = 5913 mDestInclusiveAncestorBlock && mSrcInclusiveAncestorBlock && 5914 mDestInclusiveAncestorBlock != mSrcInclusiveAncestorBlock && 5915 mSrcInclusiveAncestorBlock->IsInclusiveDescendantOf( 5916 mDestInclusiveAncestorBlock); 5917 mTopmostSrcAncestorBlockInDestBlock = 5918 mMovingToParentBlock 5919 ? AutoMoveOneLineHandler:: 5920 GetMostDistantInclusiveAncestorBlockInSpecificAncestorElement( 5921 *mSrcInclusiveAncestorBlock, *mDestInclusiveAncestorBlock) 5922 : nullptr; 5923 MOZ_ASSERT_IF(mMovingToParentBlock, mTopmostSrcAncestorBlockInDestBlock); 5924 5925 mPreserveWhiteSpaceStyle = 5926 AutoMoveOneLineHandler::ConsiderWhetherPreserveWhiteSpaceStyle( 5927 aPointInHardLine.GetContainerAs<nsIContent>(), 5928 mDestInclusiveAncestorBlock); 5929 5930 AutoClonedRangeArray rangesToWrapTheLine(aPointInHardLine); 5931 rangesToWrapTheLine.ExtendRangesToWrapLines( 5932 EditSubAction::eMergeBlockContents, 5933 BlockInlineCheck::UseComputedDisplayOutsideStyle, 5934 mTopmostSrcAncestorBlockInDestBlock ? *mTopmostSrcAncestorBlockInDestBlock 5935 : aEditingHost); 5936 MOZ_ASSERT(rangesToWrapTheLine.Ranges().Length() <= 1u); 5937 mLineRange = EditorDOMRange(rangesToWrapTheLine.FirstRangeRef()); 5938 5939 MOZ_LOG(gOneLineMoverLog, LogLevel::Info, 5940 ("mSrcInclusiveAncestorBlock=%s, mDestInclusiveAncestorBlock=%s, " 5941 "mMovingToParentBlock=%s, mTopmostSrcAncestorBlockInDestBlock=%s, " 5942 "mPreserveWhiteSpaceStyle=%s, mLineRange=%s", 5943 mSrcInclusiveAncestorBlock 5944 ? ToString(*mSrcInclusiveAncestorBlock).c_str() 5945 : "nullptr", 5946 mDestInclusiveAncestorBlock 5947 ? ToString(*mDestInclusiveAncestorBlock).c_str() 5948 : "nullptr", 5949 mMovingToParentBlock ? "true" : "false", 5950 mTopmostSrcAncestorBlockInDestBlock 5951 ? ToString(*mTopmostSrcAncestorBlockInDestBlock).c_str() 5952 : "nullptr", 5953 ToString(mPreserveWhiteSpaceStyle).c_str(), 5954 ToString(mLineRange).c_str())); 5955 5956 return NS_OK; 5957 } 5958 5959 Result<CaretPoint, nsresult> 5960 HTMLEditor::AutoMoveOneLineHandler::SplitToMakeTheLineIsolated( 5961 HTMLEditor& aHTMLEditor, const nsIContent& aNewContainer, 5962 const Element& aEditingHost, 5963 nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents) const { 5964 AutoClonedRangeArray rangesToWrapTheLine(mLineRange); 5965 Result<EditorDOMPoint, nsresult> splitResult = 5966 rangesToWrapTheLine 5967 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries( 5968 aHTMLEditor, BlockInlineCheck::UseComputedDisplayOutsideStyle, 5969 aEditingHost, &aNewContainer); 5970 if (MOZ_UNLIKELY(splitResult.isErr())) { 5971 NS_WARNING( 5972 "AutoClonedRangeArray::" 5973 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() failed"); 5974 return Err(splitResult.unwrapErr()); 5975 } 5976 EditorDOMPoint pointToPutCaret; 5977 if (splitResult.inspect().IsSet()) { 5978 pointToPutCaret = splitResult.unwrap(); 5979 } 5980 nsresult rv = rangesToWrapTheLine.CollectEditTargetNodes( 5981 aHTMLEditor, aOutArrayOfContents, EditSubAction::eMergeBlockContents, 5982 AutoClonedRangeArray::CollectNonEditableNodes::Yes); 5983 if (NS_FAILED(rv)) { 5984 NS_WARNING( 5985 "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::" 5986 "eMergeBlockContents, CollectNonEditableNodes::Yes) failed"); 5987 return Err(rv); 5988 } 5989 return CaretPoint(pointToPutCaret); 5990 } 5991 5992 // static 5993 Element* HTMLEditor::AutoMoveOneLineHandler:: 5994 GetMostDistantInclusiveAncestorBlockInSpecificAncestorElement( 5995 Element& aBlockElement, const Element& aAncestorElement) { 5996 MOZ_ASSERT(aBlockElement.IsInclusiveDescendantOf(&aAncestorElement)); 5997 MOZ_ASSERT(HTMLEditUtils::IsBlockElement( 5998 aBlockElement, BlockInlineCheck::UseComputedDisplayOutsideStyle)); 5999 6000 if (&aBlockElement == &aAncestorElement) { 6001 return nullptr; 6002 } 6003 6004 Element* lastBlockAncestor = &aBlockElement; 6005 for (Element* element : aBlockElement.InclusiveAncestorsOfType<Element>()) { 6006 if (element == &aAncestorElement) { 6007 return lastBlockAncestor; 6008 } 6009 if (HTMLEditUtils::IsBlockElement( 6010 *lastBlockAncestor, 6011 BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 6012 lastBlockAncestor = element; 6013 } 6014 } 6015 return nullptr; 6016 } 6017 6018 // static 6019 HTMLEditor::PreserveWhiteSpaceStyle 6020 HTMLEditor::AutoMoveOneLineHandler::ConsiderWhetherPreserveWhiteSpaceStyle( 6021 const nsIContent* aContentInLine, 6022 const Element* aInclusiveAncestorBlockOfInsertionPoint) { 6023 if (MOZ_UNLIKELY(!aInclusiveAncestorBlockOfInsertionPoint)) { 6024 return PreserveWhiteSpaceStyle::No; 6025 } 6026 6027 // If we move content from or to <pre>, we don't need to preserve the 6028 // white-space style for compatibility with both our traditional behavior 6029 // and the other browsers. 6030 6031 // TODO: If `white-space` is specified by non-UA stylesheet, we should 6032 // preserve it even if the right block is <pre> for compatibility with the 6033 // other browsers. 6034 const auto IsInclusiveDescendantOfPre = [](const nsIContent& aContent) { 6035 // If the content has different `white-space` style from <pre>, we 6036 // shouldn't treat it as a descendant of <pre> because web apps or 6037 // the user intent to treat the white-spaces in aContent not as `pre`. 6038 if (EditorUtils::GetComputedWhiteSpaceStyles(aContent).valueOr(std::pair( 6039 StyleWhiteSpaceCollapse::Collapse, StyleTextWrapMode::Wrap)) != 6040 std::pair(StyleWhiteSpaceCollapse::Preserve, 6041 StyleTextWrapMode::Nowrap)) { 6042 return false; 6043 } 6044 for (const Element* element : 6045 aContent.InclusiveAncestorsOfType<Element>()) { 6046 if (element->IsHTMLElement(nsGkAtoms::pre)) { 6047 return true; 6048 } 6049 } 6050 return false; 6051 }; 6052 if (IsInclusiveDescendantOfPre(*aInclusiveAncestorBlockOfInsertionPoint) || 6053 MOZ_UNLIKELY(!aContentInLine) || 6054 IsInclusiveDescendantOfPre(*aContentInLine)) { 6055 return PreserveWhiteSpaceStyle::No; 6056 } 6057 return PreserveWhiteSpaceStyle::Yes; 6058 } 6059 6060 Result<MoveNodeResult, nsresult> HTMLEditor::AutoMoveOneLineHandler::Run( 6061 HTMLEditor& aHTMLEditor, const Element& aEditingHost) { 6062 EditorDOMPoint pointToInsert(NextInsertionPointRef()); 6063 MOZ_ASSERT(pointToInsert.IsInContentNode()); 6064 6065 MOZ_LOG( 6066 gOneLineMoverLog, LogLevel::Info, 6067 ("Run(aHTMLEditor=%p, aEditingHost=%s), pointToInsert=%s", &aHTMLEditor, 6068 ToString(aEditingHost).c_str(), ToString(pointToInsert).c_str())); 6069 6070 EditorDOMPoint pointToPutCaret; 6071 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents; 6072 { 6073 AutoTrackDOMPoint tackPointToInsert(aHTMLEditor.RangeUpdaterRef(), 6074 &pointToInsert); 6075 6076 Result<CaretPoint, nsresult> splitAtLineEdgesResult = 6077 SplitToMakeTheLineIsolated( 6078 aHTMLEditor, 6079 MOZ_KnownLive(*pointToInsert.ContainerAs<nsIContent>()), 6080 aEditingHost, arrayOfContents); 6081 if (MOZ_UNLIKELY(splitAtLineEdgesResult.isErr())) { 6082 NS_WARNING("AutoMoveOneLineHandler::SplitToMakeTheLineIsolated() failed"); 6083 MOZ_LOG(gOneLineMoverLog, LogLevel::Error, 6084 ("Run: SplitToMakeTheLineIsolated() failed")); 6085 return splitAtLineEdgesResult.propagateErr(); 6086 } 6087 splitAtLineEdgesResult.unwrap().MoveCaretPointTo( 6088 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 6089 MOZ_LOG(gOneLineMoverLog, LogLevel::Verbose, 6090 ("Run: pointToPutCaret=%s", ToString(pointToPutCaret).c_str())); 6091 6092 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult = 6093 aHTMLEditor.MaybeSplitElementsAtEveryBRElement( 6094 arrayOfContents, EditSubAction::eMergeBlockContents); 6095 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) { 6096 NS_WARNING( 6097 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::" 6098 "eMergeBlockContents) failed"); 6099 MOZ_LOG(gOneLineMoverLog, LogLevel::Error, 6100 ("Run: MaybeSplitElementsAtEveryBRElement() failed")); 6101 return splitAtBRElementsResult.propagateErr(); 6102 } 6103 if (splitAtBRElementsResult.inspect().IsSet()) { 6104 pointToPutCaret = splitAtBRElementsResult.unwrap(); 6105 } 6106 MOZ_LOG(gOneLineMoverLog, LogLevel::Verbose, 6107 ("Run: pointToPutCaret=%s", ToString(pointToPutCaret).c_str())); 6108 } 6109 6110 if (!pointToInsert.IsSetAndValid()) { 6111 MOZ_LOG(gOneLineMoverLog, LogLevel::Error, 6112 ("Run: Failed because pointToInsert pointed invalid position")); 6113 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 6114 } 6115 6116 if (aHTMLEditor.AllowsTransactionsToChangeSelection() && 6117 pointToPutCaret.IsSet()) { 6118 nsresult rv = aHTMLEditor.CollapseSelectionTo(pointToPutCaret); 6119 if (NS_FAILED(rv)) { 6120 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 6121 MOZ_LOG(gOneLineMoverLog, LogLevel::Error, 6122 ("Run: Failed because of " 6123 "aHTMLEditor.CollapseSelectionTo(pointToPutCaret) failure")); 6124 return Err(rv); 6125 } 6126 } 6127 6128 if (arrayOfContents.IsEmpty()) { 6129 MOZ_LOG(gOneLineMoverLog, LogLevel::Info, 6130 ("Run: Did nothing because of no content to be moved")); 6131 return MoveNodeResult::IgnoredResult(std::move(pointToInsert)); 6132 } 6133 6134 // Track the range which contains the moved contents. 6135 if (ForceMoveToEndOfContainer()) { 6136 pointToInsert = NextInsertionPointRef(); 6137 } 6138 EditorDOMRange movedContentRange(pointToInsert); 6139 MoveNodeResult moveContentsInLineResult = 6140 MoveNodeResult::IgnoredResult(pointToInsert); 6141 for (const OwningNonNull<nsIContent>& content : arrayOfContents) { 6142 MOZ_LOG(gOneLineMoverLog, LogLevel::Info, 6143 ("Run: content=%s, pointToInsert=%s, movedContentRange=%s, " 6144 "mPointToInsert=%s", 6145 ToString(content.ref()).c_str(), ToString(pointToInsert).c_str(), 6146 ToString(movedContentRange).c_str(), 6147 ToString(mPointToInsert).c_str())); 6148 { 6149 AutoEditorDOMRangeChildrenInvalidator lockOffsets(movedContentRange); 6150 AutoTrackDOMRange trackMovedContentRange(aHTMLEditor.RangeUpdaterRef(), 6151 &movedContentRange); 6152 // If the content is a block element, move all children of it to the 6153 // new container, and then, remove the (probably) empty block element. 6154 if (HTMLEditUtils::IsBlockElement( 6155 content, BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 6156 MOZ_LOG(gOneLineMoverLog, LogLevel::Info, 6157 ("Run: Unwrapping children of content because of a block")); 6158 AutoTrackDOMMoveNodeResult trackMoveContentsInLineResult( 6159 aHTMLEditor.RangeUpdaterRef(), &moveContentsInLineResult); 6160 Result<MoveNodeResult, nsresult> moveChildrenResult = 6161 aHTMLEditor.MoveChildrenWithTransaction( 6162 MOZ_KnownLive(*content->AsElement()), pointToInsert, 6163 mPreserveWhiteSpaceStyle, RemoveIfCommentNode::Yes); 6164 if (MOZ_UNLIKELY(moveChildrenResult.isErr())) { 6165 NS_WARNING("HTMLEditor::MoveChildrenWithTransaction() failed"); 6166 MOZ_LOG(gOneLineMoverLog, LogLevel::Error, 6167 ("Run: MoveChildrenWithTransaction() failed")); 6168 moveContentsInLineResult.IgnoreCaretPointSuggestion(); 6169 return moveChildrenResult; 6170 } 6171 trackMoveContentsInLineResult.FlushAndStopTracking(); 6172 moveContentsInLineResult |= moveChildrenResult.inspect(); 6173 { 6174 AutoTrackDOMMoveNodeResult trackMoveContentsInLineResult( 6175 aHTMLEditor.RangeUpdaterRef(), &moveContentsInLineResult); 6176 // MOZ_KnownLive due to bug 1620312 6177 nsresult rv = 6178 aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(content)); 6179 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 6180 MOZ_LOG(gOneLineMoverLog, LogLevel::Error, 6181 ("Run: Aborted because DeleteNodeWithTransaction() caused " 6182 "destroying the editor")); 6183 moveContentsInLineResult.IgnoreCaretPointSuggestion(); 6184 return Err(NS_ERROR_EDITOR_DESTROYED); 6185 } 6186 if (NS_FAILED(rv)) { 6187 NS_WARNING( 6188 "EditorBase::DeleteNodeWithTransaction() failed, but ignored"); 6189 MOZ_LOG( 6190 gOneLineMoverLog, LogLevel::Warning, 6191 ("Run: Failed to delete content but the error was ignored")); 6192 } 6193 } 6194 } 6195 // If the moving content is a comment node or an empty inline node, we 6196 // don't want it to appear in the dist paragraph. 6197 else if (content->IsComment() || 6198 (content->IsText() && !content->AsText()->TextDataLength()) || 6199 HTMLEditUtils::IsEmptyInlineContainer( 6200 content, 6201 {EmptyCheckOption::TreatSingleBRElementAsVisible, 6202 EmptyCheckOption::TreatListItemAsVisible, 6203 EmptyCheckOption::TreatTableCellAsVisible, 6204 EmptyCheckOption::TreatNonEditableContentAsInvisible}, 6205 BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 6206 nsCOMPtr<nsIContent> emptyContent = 6207 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 6208 content, BlockInlineCheck::UseComputedDisplayOutsideStyle, 6209 &aEditingHost, pointToInsert.ContainerAs<nsIContent>()); 6210 if (!emptyContent) { 6211 emptyContent = content; 6212 } 6213 MOZ_LOG(gOneLineMoverLog, LogLevel::Info, 6214 ("Run: Deleting content because of %s%s", 6215 content->IsComment() ? "a comment node" 6216 : content->IsText() ? "an empty text node" 6217 : "an empty inline container", 6218 content != emptyContent 6219 ? nsPrintfCString(" (deleting topmost empty ancestor: %s)", 6220 ToString(*emptyContent).c_str()) 6221 .get() 6222 : "")); 6223 AutoTrackDOMMoveNodeResult trackMoveContentsInLineResult( 6224 aHTMLEditor.RangeUpdaterRef(), &moveContentsInLineResult); 6225 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(*emptyContent); 6226 if (NS_FAILED(rv)) { 6227 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 6228 MOZ_LOG(gOneLineMoverLog, LogLevel::Error, 6229 ("Run: DeleteNodeWithTransaction() failed")); 6230 moveContentsInLineResult.IgnoreCaretPointSuggestion(); 6231 return Err(rv); 6232 } 6233 } else { 6234 MOZ_LOG(gOneLineMoverLog, LogLevel::Info, ("Run: Moving content")); 6235 AutoTrackDOMMoveNodeResult trackMoveContentsInLineResult( 6236 aHTMLEditor.RangeUpdaterRef(), &moveContentsInLineResult); 6237 // MOZ_KnownLive due to bug 1620312 6238 Result<MoveNodeResult, nsresult> moveNodeOrChildrenResult = 6239 aHTMLEditor.MoveNodeOrChildrenWithTransaction( 6240 MOZ_KnownLive(content), pointToInsert, mPreserveWhiteSpaceStyle, 6241 RemoveIfCommentNode::Yes); 6242 if (MOZ_UNLIKELY(moveNodeOrChildrenResult.isErr())) { 6243 NS_WARNING("HTMLEditor::MoveNodeOrChildrenWithTransaction() failed"); 6244 MOZ_LOG(gOneLineMoverLog, LogLevel::Error, 6245 ("Run: MoveNodeOrChildrenWithTransaction() failed")); 6246 moveContentsInLineResult.IgnoreCaretPointSuggestion(); 6247 return moveNodeOrChildrenResult; 6248 } 6249 trackMoveContentsInLineResult.FlushAndStopTracking(); 6250 moveContentsInLineResult |= moveNodeOrChildrenResult.inspect(); 6251 } 6252 } 6253 MOZ_LOG(gOneLineMoverLog, LogLevel::Info, 6254 ("Run: movedContentRange=%s, mPointToInsert=%s", 6255 ToString(movedContentRange).c_str(), 6256 ToString(mPointToInsert).c_str())); 6257 moveContentsInLineResult.ForceToMarkAsHandled(); 6258 if (NS_WARN_IF(!movedContentRange.IsPositioned())) { 6259 MOZ_LOG(gOneLineMoverLog, LogLevel::Error, 6260 ("Run: Failed because movedContentRange was not positioned")); 6261 moveContentsInLineResult.IgnoreCaretPointSuggestion(); 6262 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 6263 } 6264 // For backward compatibility, we should move contents to end of the 6265 // container if the instance is created without specific insertion point. 6266 if (ForceMoveToEndOfContainer()) { 6267 pointToInsert = NextInsertionPointRef(); 6268 MOZ_ASSERT(pointToInsert.IsSet()); 6269 MOZ_ASSERT(movedContentRange.StartRef().EqualsOrIsBefore(pointToInsert)); 6270 movedContentRange.SetEnd(pointToInsert); 6271 MOZ_LOG(gOneLineMoverLog, LogLevel::Debug, 6272 ("Run: Updated movedContentRange end to next insertion point")); 6273 } 6274 // And also if pointToInsert has been made invalid with removing preceding 6275 // children, we should move the content to the end of the container. 6276 else if (aHTMLEditor.MaybeNodeRemovalsObservedByDevTools() && 6277 MOZ_UNLIKELY(!moveContentsInLineResult.NextInsertionPointRef() 6278 .IsSetAndValid())) { 6279 mPointToInsert.SetToEndOf(mPointToInsert.GetContainer()); 6280 pointToInsert = NextInsertionPointRef(); 6281 movedContentRange.SetEnd(pointToInsert); 6282 MOZ_LOG(gOneLineMoverLog, LogLevel::Debug, 6283 ("Run: Updated mPointToInsert to end of container and updated " 6284 "movedContentRange")); 6285 } else { 6286 MOZ_DIAGNOSTIC_ASSERT( 6287 moveContentsInLineResult.NextInsertionPointRef().IsSet()); 6288 mPointToInsert = moveContentsInLineResult.NextInsertionPointRef(); 6289 pointToInsert = NextInsertionPointRef(); 6290 if (!aHTMLEditor.MaybeNodeRemovalsObservedByDevTools() || 6291 movedContentRange.EndRef().IsBefore(pointToInsert)) { 6292 MOZ_ASSERT(pointToInsert.IsSet()); 6293 MOZ_ASSERT( 6294 movedContentRange.StartRef().EqualsOrIsBefore(pointToInsert)); 6295 movedContentRange.SetEnd(pointToInsert); 6296 MOZ_LOG(gOneLineMoverLog, LogLevel::Debug, 6297 ("Run: Updated mPointToInsert and updated movedContentRange")); 6298 } else { 6299 MOZ_LOG(gOneLineMoverLog, LogLevel::Debug, 6300 ("Run: Updated only mPointToInsert")); 6301 } 6302 } 6303 } 6304 6305 // Nothing has been moved, we don't need to clean up unnecessary <br> element. 6306 // And also if we're not moving content into a block, we can quit right now. 6307 if (moveContentsInLineResult.Ignored() || 6308 MOZ_UNLIKELY(!mDestInclusiveAncestorBlock)) { 6309 MOZ_LOG(gOneLineMoverLog, LogLevel::Info, 6310 (moveContentsInLineResult.Ignored() 6311 ? "Run: Did nothing for any children" 6312 : "Run: Finished (not dest block)")); 6313 return std::move(moveContentsInLineResult); 6314 } 6315 6316 // If we couldn't track the range to clean up, we should just stop cleaning up 6317 // because returning error from here may change the behavior of web apps using 6318 // mutation event listeners. 6319 if (MOZ_UNLIKELY(!movedContentRange.IsPositioned() || 6320 movedContentRange.Collapsed())) { 6321 MOZ_LOG(gOneLineMoverLog, LogLevel::Info, 6322 (!movedContentRange.IsPositioned() 6323 ? "Run: Finished (Couldn't track moved line)" 6324 : "Run: Finished (Moved line was empty)")); 6325 return std::move(moveContentsInLineResult); 6326 } 6327 6328 { 6329 AutoTrackDOMMoveNodeResult trackMoveContentsInLineResult( 6330 aHTMLEditor.RangeUpdaterRef(), &moveContentsInLineResult); 6331 nsresult rv = DeleteUnnecessaryTrailingLineBreakInMovedLineEnd( 6332 aHTMLEditor, movedContentRange, aEditingHost); 6333 if (NS_FAILED(rv)) { 6334 NS_WARNING( 6335 "AutoMoveOneLineHandler::" 6336 "DeleteUnnecessaryTrailingLineBreakInMovedLineEnd() failed"); 6337 MOZ_LOG( 6338 gOneLineMoverLog, LogLevel::Error, 6339 ("Run: DeleteUnnecessaryTrailingLineBreakInMovedLineEnd() failed")); 6340 moveContentsInLineResult.IgnoreCaretPointSuggestion(); 6341 return Err(rv); 6342 } 6343 } 6344 6345 MOZ_LOG(gOneLineMoverLog, LogLevel::Info, ("Run: Finished")); 6346 return std::move(moveContentsInLineResult); 6347 } 6348 6349 nsresult HTMLEditor::AutoMoveOneLineHandler:: 6350 DeleteUnnecessaryTrailingLineBreakInMovedLineEnd( 6351 HTMLEditor& aHTMLEditor, const EditorDOMRange& aMovedContentRange, 6352 const Element& aEditingHost) const { 6353 MOZ_ASSERT(mDestInclusiveAncestorBlock); 6354 MOZ_ASSERT(aMovedContentRange.IsPositioned()); 6355 MOZ_ASSERT(!aMovedContentRange.Collapsed()); 6356 6357 // If we didn't preserve white-space for backward compatibility and 6358 // white-space becomes not preformatted, we need to clean it up the last text 6359 // node if it ends with a preformatted line break. 6360 if (mPreserveWhiteSpaceStyle == PreserveWhiteSpaceStyle::No) { 6361 const RefPtr<Text> textNodeEndingWithUnnecessaryLineBreak = [&]() -> Text* { 6362 Text* lastTextNode = Text::FromNodeOrNull( 6363 mMovingToParentBlock 6364 ? HTMLEditUtils::GetPreviousContent( 6365 *mTopmostSrcAncestorBlockInDestBlock, 6366 {WalkTreeOption::StopAtBlockBoundary}, 6367 BlockInlineCheck::UseComputedDisplayOutsideStyle, 6368 mDestInclusiveAncestorBlock) 6369 : HTMLEditUtils::GetLastLeafContent( 6370 *mDestInclusiveAncestorBlock, 6371 {LeafNodeType::LeafNodeOrNonEditableNode})); 6372 if (!lastTextNode || 6373 !HTMLEditUtils::IsSimplyEditableNode(*lastTextNode)) { 6374 return nullptr; 6375 } 6376 const CharacterDataBuffer& characterDataBuffer = 6377 lastTextNode->DataBuffer(); 6378 const char16_t lastCh = 6379 characterDataBuffer.GetLength() 6380 ? characterDataBuffer.CharAt(characterDataBuffer.GetLength() - 1u) 6381 : 0; 6382 return lastCh == HTMLEditUtils::kNewLine && 6383 !EditorUtils::IsNewLinePreformatted(*lastTextNode) 6384 ? lastTextNode 6385 : nullptr; 6386 }(); 6387 if (textNodeEndingWithUnnecessaryLineBreak) { 6388 if (textNodeEndingWithUnnecessaryLineBreak->TextDataLength() == 1u) { 6389 const RefPtr<Element> inlineElement = 6390 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 6391 *textNodeEndingWithUnnecessaryLineBreak, 6392 BlockInlineCheck::UseComputedDisplayOutsideStyle, 6393 &aEditingHost); 6394 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction( 6395 inlineElement ? static_cast<nsIContent&>(*inlineElement) 6396 : static_cast<nsIContent&>( 6397 *textNodeEndingWithUnnecessaryLineBreak)); 6398 if (NS_FAILED(rv)) { 6399 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 6400 return Err(rv); 6401 } 6402 } else { 6403 Result<CaretPoint, nsresult> caretPointOrError = 6404 aHTMLEditor.DeleteTextWithTransaction( 6405 *textNodeEndingWithUnnecessaryLineBreak, 6406 textNodeEndingWithUnnecessaryLineBreak->TextDataLength() - 1u, 6407 1u); 6408 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 6409 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed"); 6410 return caretPointOrError.propagateErr(); 6411 } 6412 nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo( 6413 aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, 6414 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 6415 SuggestCaret::AndIgnoreTrivialError}); 6416 if (NS_FAILED(rv)) { 6417 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 6418 return Err(rv); 6419 } 6420 NS_WARNING_ASSERTION( 6421 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 6422 "CaretPoint::SuggestCaretPointTo() failed, but ignored"); 6423 } 6424 } 6425 } 6426 6427 const Maybe<EditorLineBreak> lastLineBreak = 6428 mMovingToParentBlock 6429 ? HTMLEditUtils::GetUnnecessaryLineBreak<EditorLineBreak>( 6430 *mTopmostSrcAncestorBlockInDestBlock, 6431 ScanLineBreak::BeforeBlock) 6432 : HTMLEditUtils::GetUnnecessaryLineBreak<EditorLineBreak>( 6433 *mDestInclusiveAncestorBlock, ScanLineBreak::AtEndOfBlock); 6434 if (lastLineBreak.isNothing() || 6435 !lastLineBreak->IsDeletableFromComposedDoc()) { 6436 return NS_OK; 6437 } 6438 const auto atUnnecessaryLineBreak = lastLineBreak->To<EditorRawDOMPoint>(); 6439 if (NS_WARN_IF(!atUnnecessaryLineBreak.IsSet())) { 6440 return NS_ERROR_FAILURE; 6441 } 6442 // If the found unnecessary line break is not what we moved above, we 6443 // shouldn't remove it. E.g., the web app may have inserted it intentionally. 6444 MOZ_ASSERT(aMovedContentRange.StartRef().IsSetAndValid()); 6445 MOZ_ASSERT(aMovedContentRange.EndRef().IsSetAndValid()); 6446 if (!aMovedContentRange.Contains(atUnnecessaryLineBreak)) { 6447 return NS_OK; 6448 } 6449 6450 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor); 6451 Result<EditorDOMPoint, nsresult> lineBreakPointOrError = 6452 aHTMLEditor.DeleteLineBreakWithTransaction( 6453 lastLineBreak.ref(), 6454 aEditingHost.IsContentEditablePlainTextOnly() ? nsIEditor::eNoStrip 6455 : nsIEditor::eStrip, 6456 aEditingHost); 6457 if (MOZ_UNLIKELY(lineBreakPointOrError.isErr())) { 6458 NS_WARNING("HTMLEditor::DeleteLineBreakWithTransaction() failed"); 6459 return lineBreakPointOrError.propagateErr(); 6460 } 6461 return NS_OK; 6462 } 6463 6464 Result<bool, nsresult> HTMLEditor::CanMoveNodeOrChildren( 6465 const nsIContent& aContent, const nsINode& aNewContainer) const { 6466 if (HTMLEditUtils::CanNodeContain(aNewContainer, aContent)) { 6467 return true; 6468 } 6469 if (aContent.IsElement()) { 6470 return CanMoveChildren(*aContent.AsElement(), aNewContainer); 6471 } 6472 return true; 6473 } 6474 6475 Result<MoveNodeResult, nsresult> HTMLEditor::MoveNodeOrChildrenWithTransaction( 6476 nsIContent& aContentToMove, const EditorDOMPoint& aPointToInsert, 6477 PreserveWhiteSpaceStyle aPreserveWhiteSpaceStyle, 6478 RemoveIfCommentNode aRemoveIfCommentNode) { 6479 MOZ_ASSERT(IsEditActionDataAvailable()); 6480 MOZ_ASSERT(aPointToInsert.IsInContentNode()); 6481 6482 const auto destWhiteSpaceStyles = 6483 [&]() -> Maybe<std::pair<StyleWhiteSpaceCollapse, StyleTextWrapMode>> { 6484 if (aPreserveWhiteSpaceStyle == PreserveWhiteSpaceStyle::No || 6485 !aPointToInsert.IsInContentNode()) { 6486 return Nothing(); 6487 } 6488 auto styles = EditorUtils::GetComputedWhiteSpaceStyles( 6489 *aPointToInsert.ContainerAs<nsIContent>()); 6490 if (NS_WARN_IF(styles.isSome() && 6491 styles.value().first == 6492 StyleWhiteSpaceCollapse::PreserveSpaces)) { 6493 return Nothing(); 6494 } 6495 return styles; 6496 }(); 6497 const auto srcWhiteSpaceStyles = 6498 [&]() -> Maybe<std::pair<StyleWhiteSpaceCollapse, StyleTextWrapMode>> { 6499 if (aPreserveWhiteSpaceStyle == PreserveWhiteSpaceStyle::No) { 6500 return Nothing(); 6501 } 6502 auto styles = EditorUtils::GetComputedWhiteSpaceStyles(aContentToMove); 6503 if (NS_WARN_IF(styles.isSome() && 6504 styles.value().first == 6505 StyleWhiteSpaceCollapse::PreserveSpaces)) { 6506 return Nothing(); 6507 } 6508 return styles; 6509 }(); 6510 // Get the `white-space` shorthand form for the given collapse + mode pair. 6511 const auto GetWhiteSpaceStyleValue = 6512 [](std::pair<StyleWhiteSpaceCollapse, StyleTextWrapMode> aStyles) { 6513 if (aStyles.second == StyleTextWrapMode::Wrap) { 6514 switch (aStyles.first) { 6515 case StyleWhiteSpaceCollapse::Collapse: 6516 return u"normal"_ns; 6517 case StyleWhiteSpaceCollapse::Preserve: 6518 return u"pre-wrap"_ns; 6519 case StyleWhiteSpaceCollapse::PreserveBreaks: 6520 return u"pre-line"_ns; 6521 case StyleWhiteSpaceCollapse::PreserveSpaces: 6522 return u"preserve-spaces"_ns; 6523 case StyleWhiteSpaceCollapse::BreakSpaces: 6524 return u"break-spaces"_ns; 6525 } 6526 } else { 6527 switch (aStyles.first) { 6528 case StyleWhiteSpaceCollapse::Collapse: 6529 return u"nowrap"_ns; 6530 case StyleWhiteSpaceCollapse::Preserve: 6531 return u"pre"_ns; 6532 case StyleWhiteSpaceCollapse::PreserveBreaks: 6533 return u"nowrap preserve-breaks"_ns; 6534 case StyleWhiteSpaceCollapse::PreserveSpaces: 6535 return u"nowrap preserve-spaces"_ns; 6536 case StyleWhiteSpaceCollapse::BreakSpaces: 6537 return u"nowrap break-spaces"_ns; 6538 } 6539 } 6540 MOZ_ASSERT_UNREACHABLE("all values should be handled above!"); 6541 return u"normal"_ns; 6542 }; 6543 6544 if (aRemoveIfCommentNode == RemoveIfCommentNode::Yes && 6545 aContentToMove.IsComment()) { 6546 EditorDOMPoint pointToInsert(aPointToInsert); 6547 { 6548 AutoTrackDOMPoint trackPointToInsert(RangeUpdaterRef(), &pointToInsert); 6549 nsresult rv = DeleteNodeWithTransaction(aContentToMove); 6550 if (NS_FAILED(rv)) { 6551 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 6552 return Err(rv); 6553 } 6554 } 6555 if (NS_WARN_IF(!pointToInsert.IsSetAndValid())) { 6556 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 6557 } 6558 return MoveNodeResult::HandledResult(std::move(pointToInsert)); 6559 } 6560 6561 // Check if this node can go into the destination node 6562 if (HTMLEditUtils::CanNodeContain(*aPointToInsert.GetContainer(), 6563 aContentToMove)) { 6564 EditorDOMPoint pointToInsert(aPointToInsert); 6565 // Preserve white-space in the new position with using `style` attribute. 6566 // This is additional path from point of view of our traditional behavior. 6567 // Therefore, ignore errors especially if we got unexpected DOM tree. 6568 if (destWhiteSpaceStyles.isSome() && srcWhiteSpaceStyles.isSome() && 6569 destWhiteSpaceStyles.value() != srcWhiteSpaceStyles.value()) { 6570 // Set `white-space` with `style` attribute if it's nsStyledElement. 6571 if (nsStyledElement* styledElement = 6572 nsStyledElement::FromNode(&aContentToMove)) { 6573 DebugOnly<nsresult> rvIgnored = 6574 CSSEditUtils::SetCSSPropertyWithTransaction( 6575 *this, MOZ_KnownLive(*styledElement), *nsGkAtoms::white_space, 6576 GetWhiteSpaceStyleValue(srcWhiteSpaceStyles.value())); 6577 if (NS_WARN_IF(Destroyed())) { 6578 return Err(NS_ERROR_EDITOR_DESTROYED); 6579 } 6580 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 6581 "CSSEditUtils::SetCSSPropertyWithTransaction(" 6582 "nsGkAtoms::white_space) failed, but ignored"); 6583 } 6584 // Otherwise, if the dest container can have <span> element and <span> 6585 // element can have the moving content node, we should insert it. 6586 else if (HTMLEditUtils::CanNodeContain(*aPointToInsert.GetContainer(), 6587 *nsGkAtoms::span) && 6588 HTMLEditUtils::CanNodeContain(*nsGkAtoms::span, 6589 aContentToMove)) { 6590 RefPtr<Element> newSpanElement = CreateHTMLContent(nsGkAtoms::span); 6591 if (NS_WARN_IF(!newSpanElement)) { 6592 return Err(NS_ERROR_FAILURE); 6593 } 6594 nsAutoString styleAttrValue(u"white-space: "_ns); 6595 styleAttrValue.Append( 6596 GetWhiteSpaceStyleValue(srcWhiteSpaceStyles.value())); 6597 IgnoredErrorResult error; 6598 newSpanElement->SetAttr(nsGkAtoms::style, styleAttrValue, error); 6599 NS_WARNING_ASSERTION(!error.Failed(), 6600 "Element::SetAttr(nsGkAtoms::span) failed"); 6601 if (MOZ_LIKELY(!error.Failed())) { 6602 Result<CreateElementResult, nsresult> insertSpanElementResult = 6603 InsertNodeWithTransaction<Element>(*newSpanElement, 6604 aPointToInsert); 6605 if (MOZ_UNLIKELY(insertSpanElementResult.isErr())) { 6606 if (NS_WARN_IF(insertSpanElementResult.inspectErr() == 6607 NS_ERROR_EDITOR_DESTROYED)) { 6608 return Err(NS_ERROR_EDITOR_DESTROYED); 6609 } 6610 NS_WARNING( 6611 "HTMLEditor::InsertNodeWithTransaction() failed, but ignored"); 6612 } else { 6613 // We should move the node into the new <span> to preserve the 6614 // style. 6615 pointToInsert.Set(newSpanElement, 0u); 6616 // We should put caret after aContentToMove after moving it so that 6617 // we do not need the suggested caret point here. 6618 insertSpanElementResult.inspect().IgnoreCaretPointSuggestion(); 6619 } 6620 } 6621 } 6622 } 6623 // If it can, move it there. 6624 Result<MoveNodeResult, nsresult> moveNodeResult = 6625 MoveNodeWithTransaction(aContentToMove, pointToInsert); 6626 NS_WARNING_ASSERTION(moveNodeResult.isOk(), 6627 "HTMLEditor::MoveNodeWithTransaction() failed"); 6628 // XXX This is odd to override the handled state here, but stopping this 6629 // hits an NS_ASSERTION in WhiteSpaceVisibilityKeeper:: 6630 // MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement. 6631 if (moveNodeResult.isOk()) { 6632 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap(); 6633 unwrappedMoveNodeResult.ForceToMarkAsHandled(); 6634 return unwrappedMoveNodeResult; 6635 } 6636 return moveNodeResult; 6637 } 6638 6639 // If it can't, move its children (if any), and then delete it. 6640 auto moveNodeResult = 6641 [&]() MOZ_CAN_RUN_SCRIPT -> Result<MoveNodeResult, nsresult> { 6642 if (!aContentToMove.IsElement()) { 6643 return MoveNodeResult::HandledResult(aPointToInsert); 6644 } 6645 Result<MoveNodeResult, nsresult> moveChildrenResult = 6646 MoveChildrenWithTransaction(MOZ_KnownLive(*aContentToMove.AsElement()), 6647 aPointToInsert, aPreserveWhiteSpaceStyle, 6648 aRemoveIfCommentNode); 6649 NS_WARNING_ASSERTION(moveChildrenResult.isOk(), 6650 "HTMLEditor::MoveChildrenWithTransaction() failed"); 6651 return moveChildrenResult; 6652 }(); 6653 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 6654 return moveNodeResult; // Already warned in the lambda. 6655 } 6656 6657 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap(); 6658 { 6659 AutoTrackDOMMoveNodeResult trackMoveNodeResult(RangeUpdaterRef(), 6660 &unwrappedMoveNodeResult); 6661 nsresult rv = DeleteNodeWithTransaction(aContentToMove); 6662 if (NS_FAILED(rv)) { 6663 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 6664 unwrappedMoveNodeResult.IgnoreCaretPointSuggestion(); 6665 return Err(rv); 6666 } 6667 } 6668 if (!MaybeNodeRemovalsObservedByDevTools()) { 6669 return std::move(unwrappedMoveNodeResult); 6670 } 6671 // Mutation event listener may make `offset` value invalid with 6672 // removing some previous children while we call 6673 // `DeleteNodeWithTransaction()` so that we should adjust it here. 6674 if (unwrappedMoveNodeResult.NextInsertionPointRef() 6675 .IsSetAndValidInComposedDoc()) { 6676 return std::move(unwrappedMoveNodeResult); 6677 } 6678 unwrappedMoveNodeResult |= MoveNodeResult::HandledResult( 6679 EditorDOMPoint::AtEndOf(*aPointToInsert.GetContainer())); 6680 return std::move(unwrappedMoveNodeResult); 6681 } 6682 6683 Result<bool, nsresult> HTMLEditor::CanMoveChildren( 6684 const Element& aElement, const nsINode& aNewContainer) const { 6685 if (NS_WARN_IF(&aElement == &aNewContainer)) { 6686 return Err(NS_ERROR_FAILURE); 6687 } 6688 for (nsIContent* childContent = aElement.GetFirstChild(); childContent; 6689 childContent = childContent->GetNextSibling()) { 6690 Result<bool, nsresult> result = 6691 CanMoveNodeOrChildren(*childContent, aNewContainer); 6692 if (result.isErr() || result.inspect()) { 6693 return result; 6694 } 6695 } 6696 return false; 6697 } 6698 6699 Result<MoveNodeResult, nsresult> HTMLEditor::MoveChildrenWithTransaction( 6700 Element& aElement, const EditorDOMPoint& aPointToInsert, 6701 PreserveWhiteSpaceStyle aPreserveWhiteSpaceStyle, 6702 RemoveIfCommentNode aRemoveIfCommentNode) { 6703 MOZ_ASSERT(aPointToInsert.IsSet()); 6704 6705 if (NS_WARN_IF(&aElement == aPointToInsert.GetContainer())) { 6706 return Err(NS_ERROR_INVALID_ARG); 6707 } 6708 6709 MoveNodeResult moveChildrenResult = 6710 MoveNodeResult::IgnoredResult(aPointToInsert); 6711 while (nsCOMPtr<nsIContent> firstChild = aElement.GetFirstChild()) { 6712 AutoTrackDOMMoveNodeResult trackMoveChildrenResult(RangeUpdaterRef(), 6713 &moveChildrenResult); 6714 Result<MoveNodeResult, nsresult> moveNodeOrChildrenResult = 6715 MoveNodeOrChildrenWithTransaction( 6716 *firstChild, moveChildrenResult.NextInsertionPointRef(), 6717 aPreserveWhiteSpaceStyle, aRemoveIfCommentNode); 6718 if (MOZ_UNLIKELY(moveNodeOrChildrenResult.isErr())) { 6719 NS_WARNING("HTMLEditor::MoveNodeOrChildrenWithTransaction() failed"); 6720 moveChildrenResult.IgnoreCaretPointSuggestion(); 6721 return moveNodeOrChildrenResult; 6722 } 6723 trackMoveChildrenResult.FlushAndStopTracking(); 6724 moveChildrenResult |= moveNodeOrChildrenResult.inspect(); 6725 } 6726 return moveChildrenResult; 6727 } 6728 6729 nsresult HTMLEditor::MoveAllChildren(nsINode& aContainer, 6730 const EditorRawDOMPoint& aPointToInsert) { 6731 if (!aContainer.HasChildren()) { 6732 return NS_OK; 6733 } 6734 nsIContent* const firstChild = aContainer.GetFirstChild(); 6735 if (NS_WARN_IF(!firstChild)) { 6736 return NS_ERROR_FAILURE; 6737 } 6738 nsIContent* const lastChild = aContainer.GetLastChild(); 6739 if (NS_WARN_IF(!lastChild)) { 6740 return NS_ERROR_FAILURE; 6741 } 6742 nsresult rv = MoveChildrenBetween( 6743 // MOZ_KnownLive is okay for here because MoveChildrenBetween() will grab 6744 // all children with an array of strong pointer. Therefore, we don't need 6745 // to guarantee the lifetime of firstChild and lastChild here unless we'd 6746 // start to refer after this call. 6747 MOZ_KnownLive(*firstChild), MOZ_KnownLive(*lastChild), aPointToInsert); 6748 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6749 "HTMLEditor::MoveChildrenBetween() failed"); 6750 return rv; 6751 } 6752 6753 nsresult HTMLEditor::MoveChildrenBetween( 6754 nsIContent& aFirstChild, nsIContent& aLastChild, 6755 const EditorRawDOMPoint& aPointToInsert) { 6756 nsCOMPtr<nsINode> oldContainer = aFirstChild.GetParentNode(); 6757 if (NS_WARN_IF(oldContainer != aLastChild.GetParentNode()) || 6758 NS_WARN_IF(!aPointToInsert.IsInContentNode()) || 6759 NS_WARN_IF(!aPointToInsert.CanContainerHaveChildren())) { 6760 return NS_ERROR_INVALID_ARG; 6761 } 6762 6763 // First, store all children which should be moved to the new container. 6764 AutoTArray<nsCOMPtr<nsIContent>, 10> children; 6765 for (nsIContent* child = &aFirstChild; child; 6766 child = child->GetNextSibling()) { 6767 children.AppendElement(child); 6768 if (child == &aLastChild) { 6769 break; 6770 } 6771 } 6772 6773 if (NS_WARN_IF(children.LastElement() != &aLastChild)) { 6774 return NS_ERROR_INVALID_ARG; 6775 } 6776 6777 const nsCOMPtr<nsIContent> newContainer = 6778 aPointToInsert.ContainerAs<nsIContent>(); 6779 nsCOMPtr<nsIContent> nextNode = aPointToInsert.GetChild(); 6780 for (size_t i = children.Length(); i > 0; --i) { 6781 nsCOMPtr<nsIContent>& child = children[i - 1]; 6782 if (child->GetParentNode() != oldContainer) { 6783 // If the child has been moved to different container, we shouldn't 6784 // touch it. 6785 continue; 6786 } 6787 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*child))) { 6788 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE; 6789 } 6790 { 6791 nsresult rv = AutoNodeAPIWrapper(*this, *oldContainer) 6792 .RemoveChild(MOZ_KnownLive(*child)); 6793 if (NS_FAILED(rv)) { 6794 NS_WARNING("AutoNodeAPIWrapper::RemoveChild() failed"); 6795 return rv; 6796 } 6797 } 6798 if (NS_WARN_IF(nextNode && nextNode->GetParentNode() != newContainer) || 6799 NS_WARN_IF(newContainer->IsInComposedDoc() && 6800 !HTMLEditUtils::IsSimplyEditableNode(*newContainer))) { 6801 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 6802 } 6803 { 6804 nsresult rv = AutoNodeAPIWrapper(*this, *newContainer) 6805 .InsertBefore(MOZ_KnownLive(*child), nextNode); 6806 if (NS_FAILED(rv)) { 6807 NS_WARNING("AutoNodeAPIWrapper::InsertBefore() failed"); 6808 return rv; 6809 } 6810 } 6811 // If the child was inserted or appended properly, the following children 6812 // should be inserted before it. Otherwise, keep using current position. 6813 if (child->GetParentNode() == newContainer) { 6814 nextNode = child; 6815 } 6816 } 6817 return NS_OK; 6818 } 6819 6820 nsresult HTMLEditor::MovePreviousSiblings( 6821 nsIContent& aChild, const EditorRawDOMPoint& aPointToInsert) { 6822 if (NS_WARN_IF(!aChild.GetParentNode())) { 6823 return NS_ERROR_INVALID_ARG; 6824 } 6825 nsIContent* const firstChild = aChild.GetParentNode()->GetFirstChild(); 6826 if (NS_WARN_IF(!firstChild)) { 6827 return NS_ERROR_FAILURE; 6828 } 6829 nsIContent* const lastChild = 6830 &aChild == firstChild ? firstChild : aChild.GetPreviousSibling(); 6831 if (NS_WARN_IF(!lastChild)) { 6832 return NS_ERROR_FAILURE; 6833 } 6834 nsresult rv = MoveChildrenBetween( 6835 // MOZ_KnownLive is okay for here because MoveChildrenBetween() will grab 6836 // all children with an array of strong pointer. Therefore, we don't need 6837 // to guarantee the lifetime of firstChild and lastChild here unless we'd 6838 // start to refer after this call. 6839 MOZ_KnownLive(*firstChild), MOZ_KnownLive(*lastChild), aPointToInsert); 6840 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6841 "HTMLEditor::MoveChildrenBetween() failed"); 6842 return rv; 6843 } 6844 6845 nsresult HTMLEditor::MoveInclusiveNextSiblings( 6846 nsIContent& aChild, const EditorRawDOMPoint& aPointToInsert) { 6847 if (NS_WARN_IF(!aChild.GetParentNode())) { 6848 return NS_ERROR_INVALID_ARG; 6849 } 6850 nsIContent* const lastChild = aChild.GetParentNode()->GetLastChild(); 6851 if (NS_WARN_IF(!lastChild)) { 6852 return NS_ERROR_FAILURE; 6853 } 6854 nsresult rv = MoveChildrenBetween( 6855 // MOZ_KnownLive is okay for here because MoveChildrenBetween() will grab 6856 // all children with an array of strong pointer. Therefore, we don't need 6857 // to guarantee the lifetime of lastChild here unless we'd start to refer 6858 // after this call. 6859 aChild, MOZ_KnownLive(*lastChild), aPointToInsert); 6860 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6861 "HTMLEditor::MoveChildrenBetween() failed"); 6862 return rv; 6863 } 6864 6865 Result<DeleteRangeResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: 6866 AutoBlockElementsJoiner::DeleteContentButKeepTableStructure( 6867 HTMLEditor& aHTMLEditor, nsIContent& aContent) { 6868 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 6869 6870 if (!HTMLEditUtils::IsAnyTableElementExceptTableElementAndColumElement( 6871 aContent)) { 6872 nsCOMPtr<nsINode> parentNode = aContent.GetParentNode(); 6873 if (NS_WARN_IF(!parentNode)) { 6874 return Err(NS_ERROR_FAILURE); 6875 } 6876 nsCOMPtr<nsIContent> nextSibling = aContent.GetNextSibling(); 6877 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aContent); 6878 if (NS_FAILED(rv)) { 6879 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 6880 return Err(rv); 6881 } 6882 if (NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parentNode) || 6883 NS_WARN_IF(!parentNode->IsInComposedDoc())) { 6884 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 6885 } 6886 return DeleteRangeResult( 6887 EditorDOMRange(nextSibling ? EditorDOMPoint(nextSibling) 6888 : EditorDOMPoint::AtEndOf(*parentNode)), 6889 EditorDOMPoint()); 6890 } 6891 6892 // XXX For performance, this should just call 6893 // DeleteContentButKeepTableStructure() while there are children in 6894 // aContent. If we need to avoid infinite loop because mutation event 6895 // listeners can add unexpected nodes into aContent, we should just loop 6896 // only original count of the children. 6897 AutoTArray<OwningNonNull<nsIContent>, 10> childList; 6898 for (nsIContent* child = aContent.GetFirstChild(); child; 6899 child = child->GetNextSibling()) { 6900 childList.AppendElement(*child); 6901 } 6902 6903 for (const auto& child : childList) { 6904 // MOZ_KnownLive because 'childList' is guaranteed to 6905 // keep it alive. 6906 Result<DeleteRangeResult, nsresult> deleteChildResult = 6907 DeleteContentButKeepTableStructure(aHTMLEditor, MOZ_KnownLive(child)); 6908 if (MOZ_UNLIKELY(deleteChildResult.isErr())) { 6909 NS_WARNING("HTMLEditor::DeleteContentButKeepTableStructure() failed"); 6910 return deleteChildResult.propagateErr(); 6911 } 6912 deleteChildResult.unwrap().IgnoreCaretPointSuggestion(); 6913 } 6914 if (NS_WARN_IF(!aContent.IsInComposedDoc())) { 6915 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 6916 } 6917 6918 // Insert a <br> into new empty table cell or caption because we don't have a 6919 // change to do it for the middle of the range. Note that this does not 6920 // handle first cell/caption and end cell/caption at the deleting range. They 6921 // should be handled by upper level because we may need to delete unnecessary 6922 // new empty inline ancestors in the cells/captions. 6923 if (!HTMLEditUtils::IsTableCellOrCaptionElement(aContent) || 6924 aContent.GetChildCount()) { 6925 return DeleteRangeResult(EditorDOMRange(EditorDOMPoint(&aContent, 0u), 6926 EditorDOMPoint::AtEndOf(aContent)), 6927 EditorDOMPoint()); 6928 } 6929 Result<CreateLineBreakResult, nsresult> insertLineBreakResultOrError = 6930 aHTMLEditor.InsertLineBreak(WithTransaction::Yes, 6931 LineBreakType::BRElement, 6932 EditorDOMPoint(&aContent, 0)); 6933 if (MOZ_UNLIKELY(insertLineBreakResultOrError.isErr())) { 6934 NS_WARNING( 6935 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 6936 "LineBreakType::BRElement) failed"); 6937 return insertLineBreakResultOrError.propagateErr(); 6938 } 6939 CreateLineBreakResult insertLineBreakResult = 6940 insertLineBreakResultOrError.unwrap(); 6941 insertLineBreakResult.IgnoreCaretPointSuggestion(); 6942 return DeleteRangeResult(EditorDOMRange(EditorDOMPoint(&aContent, 0u)), 6943 EditorDOMPoint()); 6944 } 6945 6946 nsresult HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty( 6947 nsIContent& aContent) { 6948 MOZ_ASSERT(IsEditActionDataAvailable()); 6949 6950 // The element must be `<blockquote type="cite">` or 6951 // `<span _moz_quote="true">`. 6952 RefPtr<Element> mailCiteElement = 6953 GetMostDistantAncestorMailCiteElement(aContent); 6954 if (!mailCiteElement) { 6955 return NS_OK; 6956 } 6957 bool seenBR = false; 6958 if (!HTMLEditUtils::IsEmptyNode( 6959 *mailCiteElement, 6960 {EmptyCheckOption::TreatListItemAsVisible, 6961 EmptyCheckOption::TreatTableCellAsVisible, 6962 EmptyCheckOption::TreatNonEditableContentAsInvisible}, 6963 &seenBR)) { 6964 return NS_OK; 6965 } 6966 EditorDOMPoint atEmptyMailCiteElement(mailCiteElement); 6967 { 6968 AutoEditorDOMPointChildInvalidator lockOffset(atEmptyMailCiteElement); 6969 nsresult rv = DeleteNodeWithTransaction(*mailCiteElement); 6970 if (NS_FAILED(rv)) { 6971 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 6972 return rv; 6973 } 6974 } 6975 6976 if (!atEmptyMailCiteElement.IsSet() || !seenBR) { 6977 NS_WARNING_ASSERTION( 6978 atEmptyMailCiteElement.IsSet(), 6979 "Mutation event listener might changed the DOM tree during " 6980 "EditorBase::DeleteNodeWithTransaction(), but ignored"); 6981 return NS_OK; 6982 } 6983 6984 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 6985 InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement, 6986 atEmptyMailCiteElement); 6987 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 6988 NS_WARNING( 6989 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 6990 "LineBreakType::BRElement) failed"); 6991 return insertBRElementResultOrError.unwrapErr(); 6992 } 6993 CreateLineBreakResult insertBRElementResult = 6994 insertBRElementResultOrError.unwrap(); 6995 MOZ_ASSERT(insertBRElementResult.Handled()); 6996 nsresult rv = insertBRElementResult.SuggestCaretPointTo( 6997 *this, {SuggestCaret::AndIgnoreTrivialError}); 6998 if (NS_FAILED(rv)) { 6999 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 7000 return rv; 7001 } 7002 NS_WARNING_ASSERTION(rv == NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 7003 "CaretPoint::SuggestCaretPointTo() failed, but ignored"); 7004 return NS_OK; 7005 } 7006 7007 Element* HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter:: 7008 ScanEmptyBlockInclusiveAncestor(const HTMLEditor& aHTMLEditor, 7009 nsIContent& aStartContent) { 7010 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 7011 MOZ_ASSERT(!mEmptyInclusiveAncestorBlockElement); 7012 7013 // If we are inside an empty block, delete it. 7014 // Note: do NOT delete table elements this way. 7015 // Note: do NOT delete non-editable block element. 7016 Element* editableBlockElement = HTMLEditUtils::GetInclusiveAncestorElement( 7017 aStartContent, HTMLEditUtils::ClosestEditableBlockElement, 7018 BlockInlineCheck::UseComputedDisplayOutsideStyle); 7019 if (!editableBlockElement) { 7020 return nullptr; 7021 } 7022 // XXX Perhaps, this is slow loop. If empty blocks are nested, then, 7023 // each block checks whether it's empty or not. However, descendant 7024 // blocks are checked again and again by IsEmptyNode(). Perhaps, it 7025 // should be able to take "known empty element" for avoiding same checks. 7026 while (editableBlockElement && 7027 HTMLEditUtils::IsRemovableFromParentNode(*editableBlockElement) && 7028 !HTMLEditUtils::IsAnyTableElementExceptColumnElement( 7029 *editableBlockElement) && 7030 HTMLEditUtils::IsEmptyNode(*editableBlockElement)) { 7031 // If the removable empty list item is a child of editing host list element, 7032 // we should not delete it. 7033 if (HTMLEditUtils::IsListItemElement(*editableBlockElement)) { 7034 Element* const parentElement = editableBlockElement->GetParentElement(); 7035 if (parentElement && HTMLEditUtils::IsListElement(*parentElement) && 7036 !HTMLEditUtils::IsRemovableFromParentNode(*parentElement) && 7037 HTMLEditUtils::IsEmptyNode(*parentElement)) { 7038 break; 7039 } 7040 } 7041 mEmptyInclusiveAncestorBlockElement = editableBlockElement; 7042 editableBlockElement = HTMLEditUtils::GetAncestorElement( 7043 *mEmptyInclusiveAncestorBlockElement, 7044 HTMLEditUtils::ClosestEditableBlockElement, 7045 BlockInlineCheck::UseComputedDisplayOutsideStyle); 7046 } 7047 if (!mEmptyInclusiveAncestorBlockElement) { 7048 return nullptr; 7049 } 7050 7051 // XXX Because of not checking whether found block element is editable 7052 // in the above loop, empty ediable block element may be overwritten 7053 // with empty non-editable clock element. Therefore, we fail to 7054 // remove the found empty nodes. 7055 if (NS_WARN_IF(!mEmptyInclusiveAncestorBlockElement->IsEditable()) || 7056 NS_WARN_IF(!mEmptyInclusiveAncestorBlockElement->GetParentElement())) { 7057 mEmptyInclusiveAncestorBlockElement = nullptr; 7058 } 7059 return mEmptyInclusiveAncestorBlockElement; 7060 } 7061 7062 nsresult HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter:: 7063 ComputeTargetRanges(const HTMLEditor& aHTMLEditor, 7064 nsIEditor::EDirection aDirectionAndAmount, 7065 const Element& aEditingHost, 7066 AutoClonedSelectionRangeArray& aRangesToDelete) const { 7067 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement); 7068 7069 // We'll delete `mEmptyInclusiveAncestorBlockElement` node from the tree, but 7070 // we should return the range from start/end of next/previous editable content 7071 // to end/start of the element for compatiblity with the other browsers. 7072 switch (aDirectionAndAmount) { 7073 case nsIEditor::eNone: 7074 break; 7075 case nsIEditor::ePrevious: 7076 case nsIEditor::ePreviousWord: 7077 case nsIEditor::eToBeginningOfLine: { 7078 EditorRawDOMPoint startPoint = 7079 HTMLEditUtils::GetPreviousEditablePoint<EditorRawDOMPoint>( 7080 *mEmptyInclusiveAncestorBlockElement, &aEditingHost, 7081 // We'ill delete invisible white-spaces later. 7082 InvisibleWhiteSpaces::Ignore, 7083 // In this case, we won't join table cells so that we should 7084 // get a range which is in a table cell even if it's in a 7085 // table. 7086 TableBoundary::NoCrossAnyTableElement); 7087 if (!startPoint.IsSet()) { 7088 NS_WARNING( 7089 "HTMLEditUtils::GetPreviousEditablePoint() didn't return a valid " 7090 "point"); 7091 return NS_ERROR_FAILURE; 7092 } 7093 nsresult rv = aRangesToDelete.SetStartAndEnd( 7094 startPoint, 7095 EditorRawDOMPoint::AtEndOf(mEmptyInclusiveAncestorBlockElement)); 7096 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 7097 "AutoClonedRangeArray::SetStartAndEnd() failed"); 7098 return rv; 7099 } 7100 case nsIEditor::eNext: 7101 case nsIEditor::eNextWord: 7102 case nsIEditor::eToEndOfLine: { 7103 EditorRawDOMPoint endPoint = 7104 HTMLEditUtils::GetNextEditablePoint<EditorRawDOMPoint>( 7105 *mEmptyInclusiveAncestorBlockElement, &aEditingHost, 7106 // We'ill delete invisible white-spaces. 7107 InvisibleWhiteSpaces::Ignore, 7108 // In this case, we won't join table cells so that we should 7109 // get a range which is in a table cell even if it's in a 7110 // table. 7111 TableBoundary::NoCrossAnyTableElement); 7112 if (!endPoint.IsSet()) { 7113 NS_WARNING( 7114 "HTMLEditUtils::GetNextEditablePoint() didn't return a valid " 7115 "point"); 7116 return NS_ERROR_FAILURE; 7117 } 7118 nsresult rv = aRangesToDelete.SetStartAndEnd( 7119 EditorRawDOMPoint(mEmptyInclusiveAncestorBlockElement, 0), endPoint); 7120 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 7121 "AutoClonedRangeArray::SetStartAndEnd() failed"); 7122 return rv; 7123 } 7124 default: 7125 MOZ_ASSERT_UNREACHABLE("Handle the nsIEditor::EDirection value"); 7126 break; 7127 } 7128 // No direction, let's select the element to be deleted. 7129 nsresult rv = 7130 aRangesToDelete.SelectNode(*mEmptyInclusiveAncestorBlockElement); 7131 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 7132 "AutoClonedRangeArray::SelectNode() failed"); 7133 return rv; 7134 } 7135 7136 Result<CreateLineBreakResult, nsresult> 7137 HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter:: 7138 MaybeInsertBRElementBeforeEmptyListItemElement(HTMLEditor& aHTMLEditor) { 7139 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement); 7140 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement->GetParentElement()); 7141 MOZ_ASSERT( 7142 HTMLEditUtils::IsListItemElement(*mEmptyInclusiveAncestorBlockElement)); 7143 7144 // If the found empty block is a list item element and its grand parent 7145 // (i.e., parent of list element) is NOT a list element, insert <br> 7146 // element before the list element which has the empty list item. 7147 // This odd list structure may occur if `Document.execCommand("indent")` 7148 // is performed for list items. 7149 // XXX Chrome does not remove empty list elements when last content in 7150 // last list item is deleted. We should follow it since current 7151 // behavior is annoying when you type new list item with selecting 7152 // all list items. 7153 if (!HTMLEditUtils::IsFirstChild(*mEmptyInclusiveAncestorBlockElement, 7154 {WalkTreeOption::IgnoreNonEditableNode})) { 7155 return CreateLineBreakResult::NotHandled(); 7156 } 7157 7158 const EditorDOMPoint atParentOfEmptyListItem( 7159 mEmptyInclusiveAncestorBlockElement->GetParentElement()); 7160 if (NS_WARN_IF(!atParentOfEmptyListItem.IsInContentNode())) { 7161 return Err(NS_ERROR_FAILURE); 7162 } 7163 if (HTMLEditUtils::IsListElement( 7164 *atParentOfEmptyListItem.ContainerAs<nsIContent>())) { 7165 return CreateLineBreakResult::NotHandled(); 7166 } 7167 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 7168 aHTMLEditor.InsertLineBreak(WithTransaction::Yes, 7169 LineBreakType::BRElement, 7170 atParentOfEmptyListItem); 7171 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 7172 NS_WARNING( 7173 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 7174 "LineBreakType::BRElement) failed"); 7175 return insertBRElementResultOrError.propagateErr(); 7176 } 7177 CreateLineBreakResult insertBRElementResult = 7178 insertBRElementResultOrError.unwrap(); 7179 nsresult rv = insertBRElementResult.SuggestCaretPointTo( 7180 aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, 7181 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 7182 SuggestCaret::AndIgnoreTrivialError}); 7183 if (NS_FAILED(rv)) { 7184 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 7185 return Err(rv); 7186 } 7187 MOZ_ASSERT(insertBRElementResult.Handled()); 7188 return std::move(insertBRElementResult); 7189 } 7190 7191 Result<CaretPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler:: 7192 AutoEmptyBlockAncestorDeleter::GetNewCaretPosition( 7193 const HTMLEditor& aHTMLEditor, 7194 nsIEditor::EDirection aDirectionAndAmount, 7195 const Element& aEditingHost) const { 7196 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement); 7197 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement->GetParentElement()); 7198 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 7199 7200 switch (aDirectionAndAmount) { 7201 case nsIEditor::eNext: 7202 case nsIEditor::eNextWord: 7203 case nsIEditor::eToEndOfLine: { 7204 // Collapse Selection to next node of after empty block element 7205 // if there is. Otherwise, to just after the empty block. 7206 nsIContent* const nextContentOfEmptyBlock = [&]() -> nsIContent* { 7207 for (EditorRawDOMPoint scanStartPoint = 7208 EditorRawDOMPoint::After(mEmptyInclusiveAncestorBlockElement); 7209 scanStartPoint.IsInContentNode();) { 7210 nsIContent* const nextContent = HTMLEditUtils::GetNextContent( 7211 scanStartPoint, {}, BlockInlineCheck::Unused, &aEditingHost); 7212 // Let's ignore invisible `Text`. 7213 if (nextContent && nextContent->IsText() && 7214 !HTMLEditUtils::IsVisibleTextNode(*nextContent->AsText())) { 7215 scanStartPoint = EditorRawDOMPoint::After(*nextContent); 7216 continue; 7217 } 7218 return nextContent; 7219 } 7220 return nullptr; 7221 }(); 7222 if (nextContentOfEmptyBlock) { 7223 EditorDOMPoint pt = HTMLEditUtils::GetGoodCaretPointFor<EditorDOMPoint>( 7224 *nextContentOfEmptyBlock, aDirectionAndAmount); 7225 if (!pt.IsSet()) { 7226 NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed"); 7227 return Err(NS_ERROR_FAILURE); 7228 } 7229 return CaretPoint(std::move(pt)); 7230 } 7231 EditorDOMPoint afterEmptyBlock = 7232 EditorDOMPoint::After(mEmptyInclusiveAncestorBlockElement); 7233 if (NS_WARN_IF(!afterEmptyBlock.IsSet())) { 7234 return Err(NS_ERROR_FAILURE); 7235 } 7236 return CaretPoint(std::move(afterEmptyBlock)); 7237 } 7238 case nsIEditor::ePrevious: 7239 case nsIEditor::ePreviousWord: 7240 case nsIEditor::eToBeginningOfLine: { 7241 // Collapse Selection to previous editable node of the empty block 7242 // if there is. 7243 nsIContent* const previousContentOfEmptyBlock = [&]() -> nsIContent* { 7244 for (EditorRawDOMPoint scanStartPoint = 7245 EditorRawDOMPoint(mEmptyInclusiveAncestorBlockElement); 7246 scanStartPoint.IsInContentNode();) { 7247 nsIContent* const previousContent = HTMLEditUtils::GetPreviousContent( 7248 scanStartPoint, {WalkTreeOption::IgnoreNonEditableNode}, 7249 BlockInlineCheck::Unused, &aEditingHost); 7250 // Let's ignore invisible `Text`. 7251 if (previousContent && previousContent->IsText() && 7252 !HTMLEditUtils::IsVisibleTextNode(*previousContent->AsText())) { 7253 scanStartPoint = EditorRawDOMPoint(previousContent, 0u); 7254 continue; 7255 } 7256 return previousContent; 7257 } 7258 return nullptr; 7259 }(); 7260 if (previousContentOfEmptyBlock) { 7261 const EditorRawDOMPoint atEndOfPreviousContent = 7262 HTMLEditUtils::GetGoodCaretPointFor<EditorRawDOMPoint>( 7263 *previousContentOfEmptyBlock, aDirectionAndAmount); 7264 if (!atEndOfPreviousContent.IsSet()) { 7265 NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed"); 7266 return Err(NS_ERROR_FAILURE); 7267 } 7268 // If the previous content is between a preceding line break and the 7269 // block boundary of current empty block, let's move caret to the line 7270 // break if there is no visible things between them. 7271 const Maybe<EditorRawLineBreak> precedingLineBreak = 7272 HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem< 7273 EditorRawLineBreak>(atEndOfPreviousContent, aEditingHost); 7274 return precedingLineBreak.isSome() 7275 ? CaretPoint(precedingLineBreak->To<EditorDOMPoint>()) 7276 : CaretPoint(atEndOfPreviousContent.To<EditorDOMPoint>()); 7277 } 7278 // Otherwise, let's put caret next to the deleting block. 7279 auto afterEmptyBlock = 7280 EditorDOMPoint::After(*mEmptyInclusiveAncestorBlockElement); 7281 if (NS_WARN_IF(!afterEmptyBlock.IsSet())) { 7282 return Err(NS_ERROR_FAILURE); 7283 } 7284 return CaretPoint(std::move(afterEmptyBlock)); 7285 } 7286 case nsIEditor::eNone: { 7287 // Collapse selection at the removing block when we are replacing 7288 // selected content. 7289 EditorDOMPoint atEmptyBlock(mEmptyInclusiveAncestorBlockElement); 7290 if (NS_WARN_IF(!atEmptyBlock.IsSet())) { 7291 return Err(NS_ERROR_FAILURE); 7292 } 7293 return CaretPoint(std::move(atEmptyBlock)); 7294 } 7295 default: 7296 MOZ_CRASH( 7297 "AutoEmptyBlockAncestorDeleter doesn't support this action yet"); 7298 return Err(NS_ERROR_FAILURE); 7299 } 7300 } 7301 7302 Result<DeleteRangeResult, nsresult> 7303 HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter::Run( 7304 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 7305 const Element& aEditingHost) { 7306 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement); 7307 MOZ_ASSERT(mEmptyInclusiveAncestorBlockElement->GetParentElement()); 7308 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 7309 7310 { 7311 Result<DeleteRangeResult, nsresult> replaceSubListResultOrError = 7312 MaybeReplaceSubListWithNewListItem(aHTMLEditor); 7313 if (MOZ_UNLIKELY(replaceSubListResultOrError.isErr())) { 7314 NS_WARNING( 7315 "AutoEmptyBlockAncestorDeleter::MaybeReplaceSubListWithNewListItem() " 7316 "failed"); 7317 return replaceSubListResultOrError.propagateErr(); 7318 } 7319 if (replaceSubListResultOrError.inspect().Handled()) { 7320 return replaceSubListResultOrError; 7321 } 7322 } 7323 7324 auto caretPointOrError = [&]() MOZ_CAN_RUN_SCRIPT MOZ_NEVER_INLINE_DEBUG 7325 -> Result<CaretPoint, nsresult> { 7326 if (HTMLEditUtils::IsListItemElement( 7327 *mEmptyInclusiveAncestorBlockElement)) { 7328 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 7329 MaybeInsertBRElementBeforeEmptyListItemElement(aHTMLEditor); 7330 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 7331 NS_WARNING( 7332 "AutoEmptyBlockAncestorDeleter::" 7333 "MaybeInsertBRElementBeforeEmptyListItemElement() failed"); 7334 return insertBRElementResultOrError.propagateErr(); 7335 } 7336 CreateLineBreakResult insertBRElementResult = 7337 insertBRElementResultOrError.unwrap(); 7338 // If a `<br>` element is inserted, caret should be moved to after it. 7339 // XXX This comment is wrong, we're suggesting the line break position... 7340 MOZ_ASSERT_IF(insertBRElementResult.Handled(), 7341 insertBRElementResult->IsHTMLBRElement()); 7342 insertBRElementResult.IgnoreCaretPointSuggestion(); 7343 return CaretPoint( 7344 insertBRElementResult.Handled() 7345 ? insertBRElementResult.AtLineBreak<EditorDOMPoint>() 7346 : EditorDOMPoint()); 7347 } 7348 Result<CaretPoint, nsresult> caretPointOrError = 7349 GetNewCaretPosition(aHTMLEditor, aDirectionAndAmount, aEditingHost); 7350 NS_WARNING_ASSERTION( 7351 caretPointOrError.isOk(), 7352 "AutoEmptyBlockAncestorDeleter::GetNewCaretPosition() failed"); 7353 MOZ_ASSERT_IF(caretPointOrError.isOk(), 7354 caretPointOrError.inspect().HasCaretPointSuggestion()); 7355 return caretPointOrError; 7356 }(); 7357 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 7358 return caretPointOrError.propagateErr(); 7359 } 7360 EditorDOMPoint pointToPutCaret = 7361 caretPointOrError.unwrap().UnwrapCaretPoint(); 7362 const bool unwrapAncestorBlocks = 7363 !HTMLEditUtils::IsListItemElement(*mEmptyInclusiveAncestorBlockElement) && 7364 pointToPutCaret.GetContainer() == 7365 mEmptyInclusiveAncestorBlockElement->GetParentNode(); 7366 EditorDOMPoint atEmptyInclusiveAncestorBlockElement( 7367 mEmptyInclusiveAncestorBlockElement); 7368 { 7369 AutoTrackDOMPoint trackEmptyBlockPoint( 7370 aHTMLEditor.RangeUpdaterRef(), &atEmptyInclusiveAncestorBlockElement); 7371 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(), 7372 &pointToPutCaret); 7373 Result<CaretPoint, nsresult> caretPointOrError = 7374 WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt( 7375 aHTMLEditor, MOZ_KnownLive(*mEmptyInclusiveAncestorBlockElement), 7376 pointToPutCaret, aEditingHost); 7377 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 7378 NS_WARNING( 7379 "WhiteSpaceVisibilityKeeper::" 7380 "DeleteContentNodeAndJoinTextNodesAroundIt() failed"); 7381 return caretPointOrError.propagateErr(); 7382 } 7383 trackPointToPutCaret.Flush(StopTracking::Yes); 7384 caretPointOrError.unwrap().MoveCaretPointTo( 7385 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 7386 trackEmptyBlockPoint.Flush(StopTracking::Yes); 7387 if (NS_WARN_IF(!atEmptyInclusiveAncestorBlockElement 7388 .IsInContentNodeAndValidInComposedDoc()) || 7389 NS_WARN_IF(pointToPutCaret.IsSet() && 7390 !pointToPutCaret.IsInContentNodeAndValidInComposedDoc())) { 7391 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 7392 } 7393 } 7394 EditorDOMPoint pointToInsertLineBreak = 7395 std::move(atEmptyInclusiveAncestorBlockElement); 7396 DeleteRangeResult deleteNodeResult(pointToInsertLineBreak, 7397 std::move(pointToPutCaret)); 7398 if ((aHTMLEditor.IsMailEditor() || aHTMLEditor.IsPlaintextMailComposer()) && 7399 MOZ_LIKELY(pointToInsertLineBreak.IsInContentNode())) { 7400 AutoTrackDOMDeleteRangeResult trackDeleteNodeResult( 7401 aHTMLEditor.RangeUpdaterRef(), &deleteNodeResult); 7402 AutoTrackDOMPoint trackPointToInsertLineBreak(aHTMLEditor.RangeUpdaterRef(), 7403 &pointToInsertLineBreak); 7404 nsresult rv = aHTMLEditor.DeleteMostAncestorMailCiteElementIfEmpty( 7405 MOZ_KnownLive(*pointToInsertLineBreak.ContainerAs<nsIContent>())); 7406 if (NS_FAILED(rv)) { 7407 NS_WARNING( 7408 "HTMLEditor::DeleteMostAncestorMailCiteElementIfEmpty() failed"); 7409 deleteNodeResult.IgnoreCaretPointSuggestion(); 7410 return Err(rv); 7411 } 7412 trackPointToInsertLineBreak.FlushAndStopTracking(); 7413 if (NS_WARN_IF(!pointToInsertLineBreak.IsSetAndValidInComposedDoc())) { 7414 deleteNodeResult.IgnoreCaretPointSuggestion(); 7415 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 7416 } 7417 trackDeleteNodeResult.FlushAndStopTracking(); 7418 deleteNodeResult |= DeleteRangeResult( 7419 EditorDOMRange(pointToInsertLineBreak), EditorDOMPoint()); 7420 } 7421 if (unwrapAncestorBlocks && aHTMLEditor.GetTopLevelEditSubAction() == 7422 EditSubAction::eDeleteSelectedContent) { 7423 AutoTrackDOMDeleteRangeResult trackDeleteNodeResult( 7424 aHTMLEditor.RangeUpdaterRef(), &deleteNodeResult); 7425 Result<CreateLineBreakResult, nsresult> insertPaddingBRElementOrError = 7426 aHTMLEditor.InsertPaddingBRElementIfNeeded( 7427 pointToInsertLineBreak, 7428 aEditingHost.IsContentEditablePlainTextOnly() ? nsIEditor::eNoStrip 7429 : nsIEditor::eStrip, 7430 aEditingHost); 7431 if (MOZ_UNLIKELY(insertPaddingBRElementOrError.isErr())) { 7432 NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed"); 7433 deleteNodeResult.IgnoreCaretPointSuggestion(); 7434 return insertPaddingBRElementOrError.propagateErr(); 7435 } 7436 insertPaddingBRElementOrError.unwrap().IgnoreCaretPointSuggestion(); 7437 } 7438 MOZ_ASSERT(deleteNodeResult.Handled()); 7439 return std::move(deleteNodeResult); 7440 } 7441 7442 Result<DeleteRangeResult, nsresult> HTMLEditor::AutoDeleteRangesHandler:: 7443 AutoEmptyBlockAncestorDeleter::MaybeReplaceSubListWithNewListItem( 7444 HTMLEditor& aHTMLEditor) { 7445 // If we're deleting sublist element and it's the last list item of its parent 7446 // list, we should replace it with a list element. 7447 if (!HTMLEditUtils::IsListElement(mEmptyInclusiveAncestorBlockElement)) { 7448 return DeleteRangeResult::IgnoredResult(); 7449 } 7450 const RefPtr<Element> parentElement = 7451 mEmptyInclusiveAncestorBlockElement->GetParentElement(); 7452 if (!HTMLEditUtils::IsListElement(parentElement) || 7453 !HTMLEditUtils::IsEmptyNode( 7454 *parentElement, 7455 {EmptyCheckOption::TreatNonEditableContentAsInvisible})) { 7456 return DeleteRangeResult::IgnoredResult(); 7457 } 7458 7459 const nsCOMPtr<nsINode> nextSibling = 7460 mEmptyInclusiveAncestorBlockElement->GetNextSibling(); 7461 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction( 7462 MOZ_KnownLive(*mEmptyInclusiveAncestorBlockElement)); 7463 if (NS_FAILED(rv)) { 7464 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 7465 return Err(rv); 7466 } 7467 if (NS_WARN_IF(nextSibling && 7468 nextSibling->GetParentNode() != parentElement) || 7469 NS_WARN_IF(!parentElement->IsInComposedDoc())) { 7470 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 7471 } 7472 const auto pointAtDeletedNode = nextSibling 7473 ? EditorDOMPoint(nextSibling) 7474 : EditorDOMPoint::AtEndOf(*parentElement); 7475 auto deleteNodeResult = 7476 DeleteRangeResult(EditorDOMRange(pointAtDeletedNode), EditorDOMPoint()); 7477 AutoTrackDOMDeleteRangeResult trackDeleteNodeResult( 7478 aHTMLEditor.RangeUpdaterRef(), &deleteNodeResult); 7479 Result<CreateElementResult, nsresult> insertListItemResultOrError = 7480 aHTMLEditor.CreateAndInsertElement( 7481 WithTransaction::Yes, 7482 parentElement->IsHTMLElement(nsGkAtoms::dl) ? *nsGkAtoms::dd 7483 : *nsGkAtoms::li, 7484 pointAtDeletedNode, 7485 [](HTMLEditor& aHTMLEditor, Element& aNewElement, 7486 const EditorDOMPoint& aPointToInsert) -> nsresult { 7487 RefPtr<Element> brElement = 7488 aHTMLEditor.CreateHTMLContent(nsGkAtoms::br); 7489 if (MOZ_UNLIKELY(!brElement)) { 7490 NS_WARNING( 7491 "EditorBase::CreateHTMLContent(nsGkAtoms::br) failed, but " 7492 "ignored"); 7493 return NS_OK; // Just gives up to insert <br> 7494 } 7495 IgnoredErrorResult error; 7496 aNewElement.AppendChild(*brElement, error); 7497 NS_WARNING_ASSERTION(!error.Failed(), 7498 "nsINode::AppendChild() failed, but ignored"); 7499 return NS_OK; 7500 }); 7501 if (MOZ_UNLIKELY(insertListItemResultOrError.isErr())) { 7502 NS_WARNING("HTMLEditor::CreateAndInsertElement() failed"); 7503 deleteNodeResult.IgnoreCaretPointSuggestion(); 7504 return insertListItemResultOrError.propagateErr(); 7505 } 7506 trackDeleteNodeResult.FlushAndStopTracking(); 7507 CreateElementResult insertListItemResult = 7508 insertListItemResultOrError.unwrap(); 7509 insertListItemResult.IgnoreCaretPointSuggestion(); 7510 deleteNodeResult |= 7511 CaretPoint(EditorDOMPoint(insertListItemResult.GetNewNode(), 0u)); 7512 MOZ_ASSERT(deleteNodeResult.Handled()); 7513 return std::move(deleteNodeResult); 7514 } 7515 7516 template <typename EditorDOMRangeType> 7517 Result<EditorRawDOMRange, nsresult> 7518 HTMLEditor::AutoDeleteRangesHandler::ExtendOrShrinkRangeToDelete( 7519 const HTMLEditor& aHTMLEditor, 7520 const LimitersAndCaretData& aLimitersAndCaretData, 7521 const EditorDOMRangeType& aRangeToDelete) const { 7522 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 7523 MOZ_ASSERT(!aRangeToDelete.Collapsed()); 7524 MOZ_ASSERT(aRangeToDelete.IsPositioned()); 7525 7526 const nsIContent* commonAncestor = nsIContent::FromNodeOrNull( 7527 nsContentUtils::GetClosestCommonInclusiveAncestor( 7528 aRangeToDelete.StartRef().GetContainer(), 7529 aRangeToDelete.EndRef().GetContainer())); 7530 if (MOZ_UNLIKELY(NS_WARN_IF(!commonAncestor))) { 7531 return Err(NS_ERROR_FAILURE); 7532 } 7533 7534 // Editing host may be nested and outer one could have focus. Let's use 7535 // the closest editing host instead. 7536 const RefPtr<Element> closestEditingHost = 7537 aHTMLEditor.ComputeEditingHost(*commonAncestor, LimitInBodyElement::No); 7538 if (NS_WARN_IF(!closestEditingHost)) { 7539 return Err(NS_ERROR_FAILURE); 7540 } 7541 7542 // Look for the common ancestor's block element in the editing host. It's 7543 // fine that we get non-editable block element which is ancestor of inline 7544 // editing host because the following code checks editing host too. 7545 const RefPtr<Element> closestBlockAncestorOrInlineEditingHost = [&]() { 7546 // Note that if non-closest editing host has focus, found block may be 7547 // non-editable. 7548 if (Element* const maybeEditableBlockElement = 7549 HTMLEditUtils::GetInclusiveAncestorElement( 7550 *commonAncestor, HTMLEditUtils::ClosestBlockElement, 7551 BlockInlineCheck::UseComputedDisplayStyle, 7552 closestEditingHost)) { 7553 return maybeEditableBlockElement; 7554 } 7555 return closestEditingHost.get(); 7556 }(); 7557 7558 // Set up for loops and cache our root element 7559 // If only one list element is selected, and if the list element is empty, 7560 // we should delete only the list element. Or if the list element is not 7561 // empty, we should make the list has only one empty list item element. 7562 if (const Element* maybeListElement = 7563 HTMLEditUtils::GetElementIfOnlyOneSelected(aRangeToDelete)) { 7564 if (HTMLEditUtils::IsListElement(*maybeListElement) && 7565 !HTMLEditUtils::IsEmptyAnyListElement(*maybeListElement)) { 7566 EditorRawDOMRange range = 7567 HTMLEditUtils::GetRangeSelectingAllContentInAllListItems< 7568 EditorRawDOMRange>(*maybeListElement); 7569 if (range.IsPositioned()) { 7570 if (EditorUtils::IsEditableContent( 7571 *range.StartRef().ContainerAs<nsIContent>(), 7572 EditorType::HTML) && 7573 EditorUtils::IsEditableContent( 7574 *range.EndRef().ContainerAs<nsIContent>(), EditorType::HTML)) { 7575 return range; 7576 } 7577 } 7578 // If the first and/or last list item is not editable, we need to do more 7579 // complicated things probably, but we just delete the list element with 7580 // invisible things around it for now since it must be rare case. 7581 } 7582 // Otherwise, if the list item is empty, we should delete it with invisible 7583 // things around it. 7584 } 7585 7586 // Find previous visible things before start of selection 7587 EditorRawDOMRange rangeToDelete(aRangeToDelete); 7588 if (rangeToDelete.StartRef().GetContainer() != 7589 closestBlockAncestorOrInlineEditingHost) { 7590 for (;;) { 7591 const WSScanResult backwardScanFromStartResult = 7592 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 7593 {WSRunScanner::Option::OnlyEditableNodes}, 7594 rangeToDelete.StartRef()); 7595 if (!backwardScanFromStartResult.ReachedCurrentBlockBoundary() && 7596 !backwardScanFromStartResult.ReachedInlineEditingHostBoundary()) { 7597 break; 7598 } 7599 // We want to keep looking up. But stop if we are crossing table 7600 // element boundaries, or if we hit the root. 7601 if (HTMLEditUtils::IsAnyTableElementExceptColumnElement( 7602 *backwardScanFromStartResult.GetContent()) || 7603 backwardScanFromStartResult.GetContent() == 7604 closestBlockAncestorOrInlineEditingHost || 7605 backwardScanFromStartResult.GetContent() == closestEditingHost) { 7606 break; 7607 } 7608 // Don't cross list element boundary because we don't want to delete list 7609 // element at start position unless it's empty. 7610 if (HTMLEditUtils::IsListElement( 7611 *backwardScanFromStartResult.GetContent()) && 7612 !HTMLEditUtils::IsEmptyAnyListElement( 7613 *backwardScanFromStartResult.ElementPtr())) { 7614 break; 7615 } 7616 // Don't cross flex-item/grid-item boundary to make new content inserted 7617 // into it. 7618 if (backwardScanFromStartResult.ContentIsElement() && 7619 HTMLEditUtils::IsFlexOrGridItem( 7620 *backwardScanFromStartResult.ElementPtr())) { 7621 break; 7622 } 7623 rangeToDelete.SetStart(backwardScanFromStartResult 7624 .PointAtReachedContent<EditorRawDOMPoint>()); 7625 } 7626 if (!aLimitersAndCaretData.NodeIsInLimiters( 7627 rangeToDelete.StartRef().GetContainer())) { 7628 NS_WARNING("Computed start container was out of selection limiter"); 7629 return Err(NS_ERROR_FAILURE); 7630 } 7631 } 7632 7633 // Expand selection endpoint only if we don't pass an invisible `<br>`, or if 7634 // we really needed to pass that `<br>` (i.e., its block is now totally 7635 // selected). 7636 7637 // Find next visible things after end of selection 7638 EditorDOMPoint atFirstInvisibleBRElement; 7639 if (rangeToDelete.EndRef().GetContainer() != 7640 closestBlockAncestorOrInlineEditingHost) { 7641 for (;;) { 7642 const WSScanResult forwardScanFromEndResult = 7643 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 7644 {}, rangeToDelete.EndRef(), 7645 closestBlockAncestorOrInlineEditingHost); 7646 if (forwardScanFromEndResult.ReachedBRElement()) { 7647 if (HTMLEditUtils::IsVisibleBRElement( 7648 *forwardScanFromEndResult.BRElementPtr())) { 7649 break; 7650 } 7651 if (!atFirstInvisibleBRElement.IsSet()) { 7652 atFirstInvisibleBRElement = 7653 rangeToDelete.EndRef().To<EditorDOMPoint>(); 7654 } 7655 rangeToDelete.SetEnd( 7656 EditorRawDOMPoint::After(*forwardScanFromEndResult.BRElementPtr())); 7657 continue; 7658 } 7659 7660 if (forwardScanFromEndResult.ReachedCurrentBlockBoundary() || 7661 forwardScanFromEndResult.ReachedInlineEditingHostBoundary()) { 7662 MOZ_ASSERT(forwardScanFromEndResult.ContentIsElement()); 7663 // We want to keep looking up. But stop if we are crossing table 7664 // element boundaries, or if we hit the root. 7665 if (HTMLEditUtils::IsAnyTableElementExceptColumnElement( 7666 *forwardScanFromEndResult.GetContent()) || 7667 forwardScanFromEndResult.GetContent() == 7668 closestBlockAncestorOrInlineEditingHost) { 7669 break; 7670 } 7671 // Don't cross flex-item/grid-item boundary to make new content inserted 7672 // into it. 7673 if (HTMLEditUtils::IsFlexOrGridItem( 7674 *forwardScanFromEndResult.ElementPtr())) { 7675 break; 7676 } 7677 rangeToDelete.SetEnd( 7678 forwardScanFromEndResult 7679 .PointAfterReachedContent<EditorRawDOMPoint>()); 7680 continue; 7681 } 7682 7683 break; 7684 } 7685 7686 if (!aLimitersAndCaretData.NodeIsInLimiters( 7687 rangeToDelete.EndRef().GetContainer())) { 7688 NS_WARNING("Computed end container was out of selection limiter"); 7689 return Err(NS_ERROR_FAILURE); 7690 } 7691 } 7692 7693 // If range boundaries are in list element, and the positions are very 7694 // start/end of first/last list item, we may need to shrink the ranges for 7695 // preventing to remove only all list item elements. 7696 { 7697 EditorRawDOMRange rangeToDeleteListOrLeaveOneEmptyListItem = 7698 AutoDeleteRangesHandler:: 7699 GetRangeToAvoidDeletingAllListItemsIfSelectingAllOverListElements( 7700 rangeToDelete); 7701 if (rangeToDeleteListOrLeaveOneEmptyListItem.IsPositioned()) { 7702 rangeToDelete = std::move(rangeToDeleteListOrLeaveOneEmptyListItem); 7703 } 7704 } 7705 7706 if (atFirstInvisibleBRElement.IsInContentNode()) { 7707 // Find block node containing invisible `<br>` element. 7708 if (const RefPtr<const Element> editableBlockContainingBRElement = 7709 HTMLEditUtils::GetInclusiveAncestorElement( 7710 *atFirstInvisibleBRElement.ContainerAs<nsIContent>(), 7711 HTMLEditUtils::ClosestEditableBlockElement, 7712 BlockInlineCheck::UseComputedDisplayStyle)) { 7713 if (rangeToDelete.Contains( 7714 EditorRawDOMPoint(editableBlockContainingBRElement))) { 7715 return rangeToDelete; 7716 } 7717 // Otherwise, the new range should end at the invisible `<br>`. 7718 if (!aLimitersAndCaretData.NodeIsInLimiters( 7719 atFirstInvisibleBRElement.GetContainer())) { 7720 NS_WARNING( 7721 "Computed end container (`<br>` element) was out of selection " 7722 "limiter"); 7723 return Err(NS_ERROR_FAILURE); 7724 } 7725 rangeToDelete.SetEnd(atFirstInvisibleBRElement); 7726 } 7727 } 7728 7729 return rangeToDelete; 7730 } 7731 7732 // static 7733 EditorRawDOMRange HTMLEditor::AutoDeleteRangesHandler:: 7734 GetRangeToAvoidDeletingAllListItemsIfSelectingAllOverListElements( 7735 const EditorRawDOMRange& aRangeToDelete) { 7736 MOZ_ASSERT(aRangeToDelete.IsPositionedAndValid()); 7737 7738 auto GetDeepestEditableStartPointOfList = [](Element& aListElement) { 7739 Element* const firstListItemElement = 7740 HTMLEditUtils::GetFirstListItemElement(aListElement); 7741 if (MOZ_UNLIKELY(!firstListItemElement)) { 7742 return EditorRawDOMPoint(); 7743 } 7744 if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(*firstListItemElement, 7745 EditorType::HTML))) { 7746 return EditorRawDOMPoint(firstListItemElement); 7747 } 7748 return HTMLEditUtils::GetDeepestEditableStartPointOf<EditorRawDOMPoint>( 7749 *firstListItemElement, 7750 {EditablePointOption::RecognizeInvisibleWhiteSpaces, 7751 EditablePointOption::StopAtComment}); 7752 }; 7753 7754 auto GetDeepestEditableEndPointOfList = [](Element& aListElement) { 7755 Element* const lastListItemElement = 7756 HTMLEditUtils::GetLastListItemElement(aListElement); 7757 if (MOZ_UNLIKELY(!lastListItemElement)) { 7758 return EditorRawDOMPoint(); 7759 } 7760 if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(*lastListItemElement, 7761 EditorType::HTML))) { 7762 return EditorRawDOMPoint::After(*lastListItemElement); 7763 } 7764 return HTMLEditUtils::GetDeepestEditableEndPointOf<EditorRawDOMPoint>( 7765 *lastListItemElement, 7766 {EditablePointOption::RecognizeInvisibleWhiteSpaces, 7767 EditablePointOption::StopAtComment}); 7768 }; 7769 7770 Element* const startListElement = 7771 aRangeToDelete.StartRef().IsInContentNode() 7772 ? HTMLEditUtils::GetClosestInclusiveAncestorAnyListElement( 7773 *aRangeToDelete.StartRef().ContainerAs<nsIContent>()) 7774 : nullptr; 7775 Element* const endListElement = 7776 aRangeToDelete.EndRef().IsInContentNode() 7777 ? HTMLEditUtils::GetClosestInclusiveAncestorAnyListElement( 7778 *aRangeToDelete.EndRef().ContainerAs<nsIContent>()) 7779 : nullptr; 7780 if (!startListElement && !endListElement) { 7781 return EditorRawDOMRange(); 7782 } 7783 7784 // FIXME: If there are invalid children, we cannot handle first/last list item 7785 // elements properly. In that case, we should treat list elements and list 7786 // item elements as normal block elements. 7787 if (startListElement && 7788 NS_WARN_IF(!HTMLEditUtils::IsValidListElement( 7789 *startListElement, HTMLEditUtils::TreatSubListElementAs::Valid))) { 7790 return EditorRawDOMRange(); 7791 } 7792 if (endListElement && startListElement != endListElement && 7793 NS_WARN_IF(!HTMLEditUtils::IsValidListElement( 7794 *endListElement, HTMLEditUtils::TreatSubListElementAs::Valid))) { 7795 return EditorRawDOMRange(); 7796 } 7797 7798 const bool startListElementIsEmpty = 7799 startListElement && 7800 HTMLEditUtils::IsEmptyAnyListElement(*startListElement); 7801 const bool endListElementIsEmpty = 7802 startListElement == endListElement 7803 ? startListElementIsEmpty 7804 : endListElement && 7805 HTMLEditUtils::IsEmptyAnyListElement(*endListElement); 7806 // If both list elements are empty, we should not shrink the range since 7807 // we want to delete the list. 7808 if (startListElementIsEmpty && endListElementIsEmpty) { 7809 return EditorRawDOMRange(); 7810 } 7811 7812 // There may be invisible white-spaces and there are elements in the 7813 // list items. Therefore, we need to compare the deepest positions 7814 // and range boundaries. 7815 EditorRawDOMPoint deepestStartPointOfStartList = 7816 startListElement ? GetDeepestEditableStartPointOfList(*startListElement) 7817 : EditorRawDOMPoint(); 7818 EditorRawDOMPoint deepestEndPointOfEndList = 7819 endListElement ? GetDeepestEditableEndPointOfList(*endListElement) 7820 : EditorRawDOMPoint(); 7821 if (MOZ_UNLIKELY(!deepestStartPointOfStartList.IsSet() && 7822 !deepestEndPointOfEndList.IsSet())) { 7823 // FIXME: This does not work well if there is non-list-item contents in the 7824 // list elements. Perhaps, for fixing this invalid cases, we need to wrap 7825 // the content into new list item like Chrome. 7826 return EditorRawDOMRange(); 7827 } 7828 7829 // We don't want to shrink the range into empty sublist. 7830 if (deepestStartPointOfStartList.IsSet()) { 7831 for (nsIContent* const maybeList : 7832 deepestStartPointOfStartList.GetContainer() 7833 ->InclusiveAncestorsOfType<nsIContent>()) { 7834 if (aRangeToDelete.StartRef().GetContainer() == maybeList) { 7835 break; 7836 } 7837 if (HTMLEditUtils::IsListElement(*maybeList) && 7838 HTMLEditUtils::IsEmptyAnyListElement(*maybeList->AsElement())) { 7839 deepestStartPointOfStartList.Set(maybeList); 7840 } 7841 } 7842 } 7843 if (deepestEndPointOfEndList.IsSet()) { 7844 for (nsIContent* const maybeList : 7845 deepestEndPointOfEndList.GetContainer() 7846 ->InclusiveAncestorsOfType<nsIContent>()) { 7847 if (aRangeToDelete.EndRef().GetContainer() == maybeList) { 7848 break; 7849 } 7850 if (HTMLEditUtils::IsListElement(*maybeList) && 7851 HTMLEditUtils::IsEmptyAnyListElement(*maybeList->AsElement())) { 7852 deepestEndPointOfEndList.SetAfter(maybeList); 7853 } 7854 } 7855 } 7856 7857 const EditorRawDOMPoint deepestEndPointOfStartList = 7858 startListElement ? GetDeepestEditableEndPointOfList(*startListElement) 7859 : EditorRawDOMPoint(); 7860 MOZ_ASSERT_IF(deepestStartPointOfStartList.IsSet(), 7861 deepestEndPointOfStartList.IsSet()); 7862 MOZ_ASSERT_IF(!deepestStartPointOfStartList.IsSet(), 7863 !deepestEndPointOfStartList.IsSet()); 7864 7865 const bool rangeStartsFromBeginningOfStartList = 7866 deepestStartPointOfStartList.IsSet() && 7867 aRangeToDelete.StartRef().EqualsOrIsBefore(deepestStartPointOfStartList); 7868 const bool rangeEndsByEndingOfStartListOrLater = 7869 !deepestEndPointOfStartList.IsSet() || 7870 deepestEndPointOfStartList.EqualsOrIsBefore(aRangeToDelete.EndRef()); 7871 const bool rangeEndsByEndingOfEndList = 7872 deepestEndPointOfEndList.IsSet() && 7873 deepestEndPointOfEndList.EqualsOrIsBefore(aRangeToDelete.EndRef()); 7874 7875 EditorRawDOMRange newRangeToDelete; 7876 // If all over the list element at start boundary is selected, we should 7877 // shrink the range to start from the first list item to avoid to delete 7878 // all list items. 7879 if (!startListElementIsEmpty && rangeStartsFromBeginningOfStartList && 7880 rangeEndsByEndingOfStartListOrLater) { 7881 newRangeToDelete.SetStart(EditorRawDOMPoint( 7882 deepestStartPointOfStartList.ContainerAs<nsIContent>(), 0u)); 7883 } 7884 // If all over the list element at end boundary is selected, and... 7885 if (!endListElementIsEmpty && rangeEndsByEndingOfEndList) { 7886 // If the range starts before the range at end boundary of the range, 7887 // we want to delete the list completely, thus, we should extend the 7888 // range to contain the list element. 7889 if (aRangeToDelete.StartRef().IsBefore( 7890 EditorRawDOMPoint(endListElement, 0u))) { 7891 newRangeToDelete.SetEnd(EditorRawDOMPoint::After(*endListElement)); 7892 MOZ_ASSERT_IF(newRangeToDelete.StartRef().IsSet(), 7893 newRangeToDelete.IsPositionedAndValid()); 7894 } 7895 // Otherwise, if the range starts in the end list element, we shouldn't 7896 // delete the list. Therefore, we should shrink the range to end by end 7897 // of the last list item element to avoid to delete all list items. 7898 else { 7899 newRangeToDelete.SetEnd(EditorRawDOMPoint::AtEndOf( 7900 *deepestEndPointOfEndList.ContainerAs<nsIContent>())); 7901 MOZ_ASSERT_IF(newRangeToDelete.StartRef().IsSet(), 7902 newRangeToDelete.IsPositionedAndValid()); 7903 } 7904 } 7905 7906 if (!newRangeToDelete.StartRef().IsSet() && 7907 !newRangeToDelete.EndRef().IsSet()) { 7908 return EditorRawDOMRange(); 7909 } 7910 7911 if (!newRangeToDelete.StartRef().IsSet()) { 7912 newRangeToDelete.SetStart(aRangeToDelete.StartRef()); 7913 MOZ_ASSERT(newRangeToDelete.IsPositionedAndValid()); 7914 } 7915 if (!newRangeToDelete.EndRef().IsSet()) { 7916 newRangeToDelete.SetEnd(aRangeToDelete.EndRef()); 7917 MOZ_ASSERT(newRangeToDelete.IsPositionedAndValid()); 7918 } 7919 7920 return newRangeToDelete; 7921 } 7922 7923 } // namespace mozilla