HTMLEditSubActionHandler.cpp (471669B)
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 "EditorBase.h" 8 #include "HTMLEditor.h" 9 #include "HTMLEditorInlines.h" 10 #include "HTMLEditorNestedClasses.h" 11 12 #include <utility> 13 14 #include "AutoClonedRangeArray.h" 15 #include "AutoSelectionRestorer.h" 16 #include "CSSEditUtils.h" 17 #include "EditAction.h" 18 #include "EditorDOMPoint.h" 19 #include "EditorLineBreak.h" 20 #include "EditorUtils.h" 21 #include "HTMLEditHelpers.h" 22 #include "HTMLEditUtils.h" 23 #include "PendingStyles.h" // for SpecifiedStyle 24 #include "WhiteSpaceVisibilityKeeper.h" 25 #include "WSRunScanner.h" 26 27 #include "ErrorList.h" 28 #include "mozilla/Assertions.h" 29 #include "mozilla/Attributes.h" 30 #include "mozilla/AutoRestore.h" 31 #include "mozilla/ContentIterator.h" 32 #include "mozilla/EditorForwards.h" 33 #include "mozilla/IntegerRange.h" 34 #include "mozilla/Logging.h" 35 #include "mozilla/MathAlgorithms.h" 36 #include "mozilla/Maybe.h" 37 #include "mozilla/OwningNonNull.h" 38 #include "mozilla/PresShell.h" 39 #include "mozilla/StaticPrefs_editor.h" 40 #include "mozilla/TextComposition.h" 41 #include "mozilla/UniquePtr.h" 42 #include "mozilla/dom/AncestorIterator.h" 43 #include "mozilla/dom/Element.h" 44 #include "mozilla/dom/ElementInlines.h" 45 #include "mozilla/dom/HTMLBRElement.h" 46 #include "mozilla/dom/RangeBinding.h" 47 #include "mozilla/dom/Selection.h" 48 #include "mozilla/dom/StaticRange.h" 49 #include "nsAtom.h" 50 #include "nsCRT.h" 51 #include "nsCRTGlue.h" 52 #include "nsComponentManagerUtils.h" 53 #include "nsContentUtils.h" 54 #include "nsDebug.h" 55 #include "nsError.h" 56 #include "nsFrameSelection.h" 57 #include "nsGkAtoms.h" 58 #include "nsIContent.h" 59 #include "nsIFrame.h" 60 #include "nsINode.h" 61 #include "nsLiteralString.h" 62 #include "nsPrintfCString.h" 63 #include "nsRange.h" 64 #include "nsReadableUtils.h" 65 #include "nsString.h" 66 #include "nsStringFwd.h" 67 #include "nsStyledElement.h" 68 #include "nsTArray.h" 69 #include "nsTextNode.h" 70 #include "nsThreadUtils.h" 71 72 class nsISupports; 73 74 namespace mozilla { 75 76 extern LazyLogModule gTextInputLog; // Defined in EditorBase.cpp 77 78 using namespace dom; 79 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; 80 using EmptyCheckOptions = HTMLEditUtils::EmptyCheckOptions; 81 using LeafNodeType = HTMLEditUtils::LeafNodeType; 82 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; 83 using WalkTextOption = HTMLEditUtils::WalkTextOption; 84 using WalkTreeDirection = HTMLEditUtils::WalkTreeDirection; 85 using WalkTreeOption = HTMLEditUtils::WalkTreeOption; 86 87 /******************************************************** 88 * first some helpful functors we will use 89 ********************************************************/ 90 91 static bool IsPendingStyleCachePreservingSubAction( 92 EditSubAction aEditSubAction) { 93 switch (aEditSubAction) { 94 case EditSubAction::eDeleteSelectedContent: 95 case EditSubAction::eInsertLineBreak: 96 case EditSubAction::eInsertParagraphSeparator: 97 case EditSubAction::eCreateOrChangeList: 98 case EditSubAction::eIndent: 99 case EditSubAction::eOutdent: 100 case EditSubAction::eSetOrClearAlignment: 101 case EditSubAction::eCreateOrRemoveBlock: 102 case EditSubAction::eFormatBlockForHTMLCommand: 103 case EditSubAction::eMergeBlockContents: 104 case EditSubAction::eRemoveList: 105 case EditSubAction::eCreateOrChangeDefinitionListItem: 106 case EditSubAction::eInsertElement: 107 case EditSubAction::eInsertQuotation: 108 case EditSubAction::eInsertQuotedText: 109 return true; 110 default: 111 return false; 112 } 113 } 114 115 template already_AddRefed<nsRange> 116 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces( 117 const EditorDOMRange& aRange); 118 template already_AddRefed<nsRange> 119 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces( 120 const EditorRawDOMRange& aRange); 121 template already_AddRefed<nsRange> 122 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces( 123 const EditorDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint); 124 template already_AddRefed<nsRange> 125 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces( 126 const EditorRawDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint); 127 template already_AddRefed<nsRange> 128 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces( 129 const EditorDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint); 130 template already_AddRefed<nsRange> 131 HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces( 132 const EditorRawDOMPoint& aStartPoint, const EditorRawDOMPoint& aEndPoint); 133 134 nsresult HTMLEditor::InitEditorContentAndSelection() { 135 MOZ_ASSERT(IsEditActionDataAvailable()); 136 137 // We should do nothing with the result of GetRoot() if only a part of the 138 // document is editable. 139 if (!EntireDocumentIsEditable()) { 140 return NS_OK; 141 } 142 143 nsresult rv = MaybeCreatePaddingBRElementForEmptyEditor(); 144 if (NS_FAILED(rv)) { 145 NS_WARNING( 146 "HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() failed"); 147 return rv; 148 } 149 150 // If the selection hasn't been set up yet, set it up collapsed to the end of 151 // our editable content. 152 // XXX I think that this shouldn't do it in `HTMLEditor` because it maybe 153 // removed by the web app and if they call `Selection::AddRange()` without 154 // checking the range count, it may cause multiple selection ranges. 155 if (!SelectionRef().RangeCount()) { 156 nsresult rv = CollapseSelectionToEndOfLastLeafNodeOfDocument(); 157 if (NS_FAILED(rv)) { 158 NS_WARNING( 159 "HTMLEditor::CollapseSelectionToEndOfLastLeafNodeOfDocument() " 160 "failed"); 161 return rv; 162 } 163 } 164 165 if (IsPlaintextMailComposer()) { 166 // XXX Should we do this in HTMLEditor? It's odd to guarantee that last 167 // empty line is visible only when it's in the plain text mode. 168 nsresult rv = EnsurePaddingBRElementInMultilineEditor(); 169 if (NS_FAILED(rv)) { 170 NS_WARNING( 171 "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed"); 172 return rv; 173 } 174 } 175 176 Element* bodyOrDocumentElement = GetRoot(); 177 if (NS_WARN_IF(!bodyOrDocumentElement && !GetDocument())) { 178 return NS_ERROR_FAILURE; 179 } 180 181 if (!bodyOrDocumentElement) { 182 return NS_OK; 183 } 184 185 // FIXME: This is odd to update the DOM for making users can put caret in 186 // empty table cells and list items. We should make it possible without 187 // the hacky <br>. 188 rv = InsertBRElementToEmptyListItemsAndTableCellsInRange( 189 RawRangeBoundary(bodyOrDocumentElement, 0u), 190 RawRangeBoundary(bodyOrDocumentElement, 191 bodyOrDocumentElement->GetChildCount())); 192 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 193 return NS_ERROR_EDITOR_DESTROYED; 194 } 195 NS_WARNING_ASSERTION( 196 NS_SUCCEEDED(rv), 197 "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange() " 198 "failed, but ignored"); 199 return NS_OK; 200 } 201 202 void HTMLEditor::OnStartToHandleTopLevelEditSubAction( 203 EditSubAction aTopLevelEditSubAction, 204 nsIEditor::EDirection aDirectionOfTopLevelEditSubAction, ErrorResult& aRv) { 205 MOZ_ASSERT(IsEditActionDataAvailable()); 206 MOZ_ASSERT(!aRv.Failed()); 207 208 EditorBase::OnStartToHandleTopLevelEditSubAction( 209 aTopLevelEditSubAction, aDirectionOfTopLevelEditSubAction, aRv); 210 211 MOZ_ASSERT(GetTopLevelEditSubAction() == aTopLevelEditSubAction); 212 MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() == 213 aDirectionOfTopLevelEditSubAction); 214 215 if (NS_WARN_IF(Destroyed())) { 216 aRv.Throw(NS_ERROR_EDITOR_DESTROYED); 217 return; 218 } 219 220 if (!mInitSucceeded) { 221 return; // We should do nothing if we're being initialized. 222 } 223 224 NS_WARNING_ASSERTION( 225 !aRv.Failed(), 226 "EditorBase::OnStartToHandleTopLevelEditSubAction() failed"); 227 228 // Let's work with the latest layout information after (maybe) dispatching 229 // `beforeinput` event. 230 RefPtr<Document> document = GetDocument(); 231 if (NS_WARN_IF(!document)) { 232 aRv.Throw(NS_ERROR_UNEXPECTED); 233 return; 234 } 235 document->FlushPendingNotifications(FlushType::Frames); 236 if (NS_WARN_IF(Destroyed())) { 237 aRv.Throw(NS_ERROR_EDITOR_DESTROYED); 238 return; 239 } 240 241 // Remember where our selection was before edit action took place: 242 const auto atCompositionStart = 243 GetFirstIMESelectionStartPoint<EditorRawDOMPoint>(); 244 if (atCompositionStart.IsSet()) { 245 // If there is composition string, let's remember current composition 246 // range. 247 TopLevelEditSubActionDataRef().mSelectedRange->StoreRange( 248 atCompositionStart, GetLastIMESelectionEndPoint<EditorRawDOMPoint>()); 249 } else { 250 // Get the selection location 251 // XXX This may occur so that I think that we shouldn't throw exception 252 // in this case. 253 if (NS_WARN_IF(!SelectionRef().RangeCount())) { 254 aRv.Throw(NS_ERROR_UNEXPECTED); 255 return; 256 } 257 if (const nsRange* range = SelectionRef().GetRangeAt(0)) { 258 TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(*range); 259 } 260 } 261 262 // Register with range updater to track this as we perturb the doc 263 RangeUpdaterRef().RegisterRangeItem( 264 *TopLevelEditSubActionDataRef().mSelectedRange); 265 266 // Remember current inline styles for deletion and normal insertion ops 267 const bool cacheInlineStyles = [&]() { 268 switch (aTopLevelEditSubAction) { 269 case EditSubAction::eInsertText: 270 case EditSubAction::eInsertTextComingFromIME: 271 case EditSubAction::eDeleteSelectedContent: 272 return true; 273 default: 274 return IsPendingStyleCachePreservingSubAction(aTopLevelEditSubAction); 275 } 276 }(); 277 if (cacheInlineStyles) { 278 const RefPtr<Element> editingHost = 279 ComputeEditingHost(LimitInBodyElement::No); 280 if (NS_WARN_IF(!editingHost)) { 281 aRv.Throw(NS_ERROR_FAILURE); 282 return; 283 } 284 285 nsIContent* const startContainer = 286 HTMLEditUtils::GetContentToPreserveInlineStyles( 287 TopLevelEditSubActionDataRef() 288 .mSelectedRange->StartPoint<EditorRawDOMPoint>(), 289 *editingHost); 290 if (NS_WARN_IF(!startContainer)) { 291 aRv.Throw(NS_ERROR_FAILURE); 292 return; 293 } 294 if (const RefPtr<Element> startContainerElement = 295 startContainer->GetAsElementOrParentElement()) { 296 nsresult rv = CacheInlineStyles(*startContainerElement); 297 if (NS_FAILED(rv)) { 298 NS_WARNING("HTMLEditor::CacheInlineStyles() failed"); 299 aRv.Throw(rv); 300 return; 301 } 302 } 303 } 304 305 // Stabilize the document against contenteditable count changes 306 if (document->GetEditingState() == Document::EditingState::eContentEditable) { 307 document->ChangeContentEditableCount(nullptr, +1); 308 TopLevelEditSubActionDataRef().mRestoreContentEditableCount = true; 309 } 310 311 // Check that selection is in subtree defined by body node 312 nsresult rv = EnsureSelectionInBodyOrDocumentElement(); 313 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 314 aRv.Throw(NS_ERROR_EDITOR_DESTROYED); 315 return; 316 } 317 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 318 "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() " 319 "failed, but ignored"); 320 } 321 322 nsresult HTMLEditor::OnEndHandlingTopLevelEditSubAction() { 323 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); 324 325 nsresult rv; 326 while (true) { 327 if (NS_WARN_IF(Destroyed())) { 328 rv = NS_ERROR_EDITOR_DESTROYED; 329 break; 330 } 331 332 if (!mInitSucceeded) { 333 rv = NS_OK; // We should do nothing if we're being initialized. 334 break; 335 } 336 337 // Do all the tricky stuff 338 rv = OnEndHandlingTopLevelEditSubActionInternal(); 339 NS_WARNING_ASSERTION( 340 NS_SUCCEEDED(rv), 341 "HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() failied"); 342 // Perhaps, we need to do the following jobs even if the editor has been 343 // destroyed since they adjust some states of HTML document but don't 344 // modify the DOM tree nor Selection. 345 346 // Free up selectionState range item 347 if (TopLevelEditSubActionDataRef().mSelectedRange) { 348 RangeUpdaterRef().DropRangeItem( 349 *TopLevelEditSubActionDataRef().mSelectedRange); 350 } 351 352 // Reset the contenteditable count to its previous value 353 if (TopLevelEditSubActionDataRef().mRestoreContentEditableCount) { 354 Document* document = GetDocument(); 355 if (NS_WARN_IF(!document)) { 356 rv = NS_ERROR_FAILURE; 357 break; 358 } 359 if (document->GetEditingState() == 360 Document::EditingState::eContentEditable) { 361 document->ChangeContentEditableCount(nullptr, -1); 362 } 363 } 364 break; 365 } 366 DebugOnly<nsresult> rvIgnored = 367 EditorBase::OnEndHandlingTopLevelEditSubAction(); 368 NS_WARNING_ASSERTION( 369 NS_FAILED(rv) || NS_SUCCEEDED(rvIgnored), 370 "EditorBase::OnEndHandlingTopLevelEditSubAction() failed, but ignored"); 371 MOZ_ASSERT(!GetTopLevelEditSubAction()); 372 MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() == eNone); 373 return rv; 374 } 375 376 nsresult HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() { 377 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); 378 379 // If we just maintained the DOM tree for consistent behavior even after 380 // web apps modified the DOM, we should not touch the DOM in this 381 // post-processor. 382 if (GetTopLevelEditSubAction() == 383 EditSubAction::eMaintainWhiteSpaceVisibility) { 384 return NS_OK; 385 } 386 387 nsresult rv = EnsureSelectionInBodyOrDocumentElement(); 388 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 389 return NS_ERROR_EDITOR_DESTROYED; 390 } 391 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 392 "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() " 393 "failed, but ignored"); 394 395 if (GetTopLevelEditSubAction() == 396 EditSubAction::eCreatePaddingBRElementForEmptyEditor) { 397 return NS_OK; 398 } 399 400 if (TopLevelEditSubActionDataRef().mChangedRange->IsPositioned() && 401 GetTopLevelEditSubAction() != EditSubAction::eUndo && 402 GetTopLevelEditSubAction() != EditSubAction::eRedo) { 403 // don't let any txns in here move the selection around behind our back. 404 // Note that this won't prevent explicit selection setting from working. 405 AutoTransactionsConserveSelection dontChangeMySelection(*this); 406 407 { 408 EditorDOMRange changedRange( 409 *TopLevelEditSubActionDataRef().mChangedRange); 410 if (changedRange.IsPositioned() && 411 changedRange.EnsureNotInNativeAnonymousSubtree()) { 412 bool isBlockLevelSubAction = false; 413 switch (GetTopLevelEditSubAction()) { 414 case EditSubAction::eInsertText: 415 case EditSubAction::eInsertTextComingFromIME: 416 case EditSubAction::eInsertLineBreak: 417 case EditSubAction::eInsertParagraphSeparator: 418 case EditSubAction::eDeleteText: { 419 // XXX We should investigate whether this is really needed because 420 // it seems that the following code does not handle the 421 // white-spaces. 422 RefPtr<nsRange> extendedChangedRange = 423 CreateRangeIncludingAdjuscentWhiteSpaces(changedRange); 424 if (extendedChangedRange) { 425 MOZ_ASSERT(extendedChangedRange->IsPositioned()); 426 // Use extended range temporarily. 427 TopLevelEditSubActionDataRef().mChangedRange = 428 std::move(extendedChangedRange); 429 } 430 break; 431 } 432 case EditSubAction::eCreateOrChangeList: 433 case EditSubAction::eCreateOrChangeDefinitionListItem: 434 case EditSubAction::eRemoveList: 435 case EditSubAction::eFormatBlockForHTMLCommand: 436 case EditSubAction::eCreateOrRemoveBlock: 437 case EditSubAction::eIndent: 438 case EditSubAction::eOutdent: 439 case EditSubAction::eSetOrClearAlignment: 440 case EditSubAction::eSetPositionToAbsolute: 441 case EditSubAction::eSetPositionToStatic: 442 case EditSubAction::eDecreaseZIndex: 443 case EditSubAction::eIncreaseZIndex: 444 isBlockLevelSubAction = true; 445 [[fallthrough]]; 446 default: { 447 Element* editingHost = ComputeEditingHost(); 448 if (MOZ_UNLIKELY(!editingHost)) { 449 break; 450 } 451 RefPtr<nsRange> extendedChangedRange = AutoClonedRangeArray:: 452 CreateRangeWrappingStartAndEndLinesContainingBoundaries( 453 changedRange, GetTopLevelEditSubAction(), 454 isBlockLevelSubAction 455 ? BlockInlineCheck::UseHTMLDefaultStyle 456 : BlockInlineCheck::UseComputedDisplayOutsideStyle, 457 *editingHost); 458 if (!extendedChangedRange) { 459 break; 460 } 461 MOZ_ASSERT(extendedChangedRange->IsPositioned()); 462 // Use extended range temporarily. 463 TopLevelEditSubActionDataRef().mChangedRange = 464 std::move(extendedChangedRange); 465 break; 466 } 467 } 468 } 469 } 470 471 // if we did a ranged deletion or handling backspace key, make sure we have 472 // a place to put caret. 473 // Note we only want to do this if the overall operation was deletion, 474 // not if deletion was done along the way for 475 // EditSubAction::eInsertHTMLSource, EditSubAction::eInsertText, etc. 476 // That's why this is here rather than DeleteSelectionAsSubAction(). 477 // However, we shouldn't insert <br> elements if we've already removed 478 // empty block parents because users may want to disappear the line by 479 // the deletion. 480 // XXX We should make HandleDeleteSelection() store expected container 481 // for handling this here since we cannot trust current selection is 482 // collapsed at deleted point. 483 if (GetTopLevelEditSubAction() == EditSubAction::eDeleteSelectedContent && 484 TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange && 485 !TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks) { 486 const auto newCaretPosition = 487 GetFirstSelectionStartPoint<EditorDOMPoint>(); 488 if (!newCaretPosition.IsSet()) { 489 NS_WARNING("There was no selection range"); 490 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE; 491 } 492 Result<CreateLineBreakResult, nsresult> 493 insertPaddingBRElementResultOrError = 494 InsertPaddingBRElementToMakeEmptyLineVisibleIfNeeded( 495 newCaretPosition); 496 if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { 497 NS_WARNING( 498 "HTMLEditor::" 499 "InsertPaddingBRElementToMakeEmptyLineVisibleIfNeeded() failed"); 500 return insertPaddingBRElementResultOrError.unwrapErr(); 501 } 502 nsresult rv = 503 insertPaddingBRElementResultOrError.unwrap().SuggestCaretPointTo( 504 *this, {SuggestCaret::OnlyIfHasSuggestion}); 505 if (NS_FAILED(rv)) { 506 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 507 return rv; 508 } 509 NS_WARNING_ASSERTION( 510 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 511 "CaretPoint::SuggestCaretPointTo() failed, but ignored"); 512 } 513 514 // add in any needed <br>s, and remove any unneeded ones. 515 nsresult rv = InsertBRElementToEmptyListItemsAndTableCellsInRange( 516 TopLevelEditSubActionDataRef().mChangedRange->StartRef().AsRaw(), 517 TopLevelEditSubActionDataRef().mChangedRange->EndRef().AsRaw()); 518 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 519 return NS_ERROR_EDITOR_DESTROYED; 520 } 521 NS_WARNING_ASSERTION( 522 NS_SUCCEEDED(rv), 523 "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange()" 524 " failed, but ignored"); 525 526 // merge any adjacent text nodes 527 switch (GetTopLevelEditSubAction()) { 528 case EditSubAction::eInsertText: 529 case EditSubAction::eInsertTextComingFromIME: 530 break; 531 default: { 532 nsresult rv = CollapseAdjacentTextNodes( 533 MOZ_KnownLive(*TopLevelEditSubActionDataRef().mChangedRange)); 534 if (NS_WARN_IF(Destroyed())) { 535 return NS_ERROR_EDITOR_DESTROYED; 536 } 537 if (NS_FAILED(rv)) { 538 NS_WARNING("HTMLEditor::CollapseAdjacentTextNodes() failed"); 539 return rv; 540 } 541 break; 542 } 543 } 544 545 // Clean up any empty nodes in the changed range unless they are inserted 546 // intentionally. 547 if (TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements) { 548 nsresult rv = RemoveEmptyNodesIn( 549 EditorDOMRange(*TopLevelEditSubActionDataRef().mChangedRange)); 550 if (NS_FAILED(rv)) { 551 NS_WARNING("HTMLEditor::RemoveEmptyNodesIn() failed"); 552 return rv; 553 } 554 } 555 556 // Adjust selection for insert text, html paste, and delete actions if 557 // we haven't removed new empty blocks. Note that if empty block parents 558 // are removed, Selection should've been adjusted by the method which 559 // did it. 560 if (!TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks && 561 SelectionRef().IsCollapsed()) { 562 switch (GetTopLevelEditSubAction()) { 563 case EditSubAction::eInsertText: 564 case EditSubAction::eInsertTextComingFromIME: 565 case EditSubAction::eInsertLineBreak: 566 case EditSubAction::eInsertParagraphSeparator: 567 case EditSubAction::ePasteHTMLContent: 568 case EditSubAction::eInsertHTMLSource: 569 // XXX AdjustCaretPositionAndEnsurePaddingBRElement() intentionally 570 // does not create padding `<br>` element for empty editor. 571 // Investigate which is better that whether this should does it 572 // or wait MaybeCreatePaddingBRElementForEmptyEditor(). 573 rv = AdjustCaretPositionAndEnsurePaddingBRElement( 574 GetDirectionOfTopLevelEditSubAction()); 575 if (NS_FAILED(rv)) { 576 NS_WARNING( 577 "HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement() " 578 "failed"); 579 return rv; 580 } 581 break; 582 default: 583 break; 584 } 585 } 586 587 // check for any styles which were removed inappropriately 588 bool reapplyCachedStyle; 589 switch (GetTopLevelEditSubAction()) { 590 case EditSubAction::eInsertText: 591 case EditSubAction::eInsertTextComingFromIME: 592 case EditSubAction::eDeleteSelectedContent: 593 reapplyCachedStyle = true; 594 break; 595 default: 596 reapplyCachedStyle = 597 IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction()); 598 break; 599 } 600 601 // If the selection is in empty inline HTML elements, we should delete 602 // them unless it's inserted intentionally. 603 if (mPlaceholderBatch && 604 TopLevelEditSubActionDataRef().mNeedsToCleanUpEmptyElements && 605 SelectionRef().IsCollapsed() && SelectionRef().GetFocusNode()) { 606 RefPtr<Element> mostDistantEmptyInlineAncestor = nullptr; 607 for (Element* ancestor : 608 SelectionRef().GetFocusNode()->InclusiveAncestorsOfType<Element>()) { 609 if (!ancestor->IsHTMLElement() || 610 !HTMLEditUtils::IsRemovableFromParentNode(*ancestor) || 611 !HTMLEditUtils::IsEmptyInlineContainer( 612 *ancestor, {EmptyCheckOption::TreatSingleBRElementAsVisible}, 613 BlockInlineCheck::UseComputedDisplayStyle)) { 614 break; 615 } 616 mostDistantEmptyInlineAncestor = ancestor; 617 } 618 if (mostDistantEmptyInlineAncestor) { 619 nsresult rv = 620 DeleteNodeWithTransaction(*mostDistantEmptyInlineAncestor); 621 if (NS_FAILED(rv)) { 622 NS_WARNING( 623 "EditorBase::DeleteNodeWithTransaction() failed at deleting " 624 "empty inline ancestors"); 625 return rv; 626 } 627 } 628 } 629 630 // But the cached inline styles should be restored from type-in-state later. 631 if (reapplyCachedStyle) { 632 DebugOnly<nsresult> rvIgnored = 633 mPendingStylesToApplyToNewContent->UpdateSelState(*this); 634 NS_WARNING_ASSERTION( 635 NS_SUCCEEDED(rvIgnored), 636 "PendingStyles::UpdateSelState() failed, but ignored"); 637 rvIgnored = ReapplyCachedStyles(); 638 NS_WARNING_ASSERTION( 639 NS_SUCCEEDED(rvIgnored), 640 "HTMLEditor::ReapplyCachedStyles() failed, but ignored"); 641 TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear(); 642 } 643 } 644 645 rv = HandleInlineSpellCheck( 646 TopLevelEditSubActionDataRef().mSelectedRange->StartPoint(), 647 TopLevelEditSubActionDataRef().mChangedRange); 648 if (NS_FAILED(rv)) { 649 NS_WARNING("EditorBase::HandleInlineSpellCheck() failed"); 650 return rv; 651 } 652 653 // detect empty doc 654 // XXX Need to investigate when the padding <br> element is removed because 655 // I don't see the <br> element with testing manually. If it won't be 656 // used, we can get rid of this cost. 657 rv = MaybeCreatePaddingBRElementForEmptyEditor(); 658 if (NS_FAILED(rv)) { 659 NS_WARNING( 660 "EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() failed"); 661 return rv; 662 } 663 664 // adjust selection HINT if needed 665 if (!TopLevelEditSubActionDataRef().mDidExplicitlySetInterLine && 666 SelectionRef().IsCollapsed()) { 667 SetSelectionInterlinePosition(); 668 } 669 670 return NS_OK; 671 } 672 673 Result<EditActionResult, nsresult> HTMLEditor::CanHandleHTMLEditSubAction( 674 CheckSelectionInReplacedElement 675 aCheckSelectionInReplacedElement /* = ::Yes */) const { 676 MOZ_ASSERT(IsEditActionDataAvailable()); 677 678 if (NS_WARN_IF(Destroyed())) { 679 return Err(NS_ERROR_EDITOR_DESTROYED); 680 } 681 682 // If there is not selection ranges, we should ignore the result. 683 if (!SelectionRef().RangeCount()) { 684 return EditActionResult::CanceledResult(); 685 } 686 687 const nsRange* range = SelectionRef().GetRangeAt(0); 688 nsINode* selStartNode = range->GetStartContainer(); 689 if (NS_WARN_IF(!selStartNode) || NS_WARN_IF(!selStartNode->IsContent())) { 690 return Err(NS_ERROR_FAILURE); 691 } 692 693 if (!HTMLEditUtils::IsSimplyEditableNode(*selStartNode)) { 694 return EditActionResult::CanceledResult(); 695 } 696 697 nsINode* selEndNode = range->GetEndContainer(); 698 if (NS_WARN_IF(!selEndNode) || NS_WARN_IF(!selEndNode->IsContent())) { 699 return Err(NS_ERROR_FAILURE); 700 } 701 702 using ReplaceOrVoidElementOption = HTMLEditUtils::ReplaceOrVoidElementOption; 703 704 if (selStartNode == selEndNode) { 705 if (aCheckSelectionInReplacedElement == 706 CheckSelectionInReplacedElement::Yes && 707 HTMLEditUtils::GetInclusiveAncestorReplacedOrVoidElement( 708 *selStartNode->AsContent(), 709 ReplaceOrVoidElementOption::LookForOnlyNonVoidReplacedElement)) { 710 return EditActionResult::CanceledResult(); 711 } 712 return EditActionResult::IgnoredResult(); 713 } 714 715 if (aCheckSelectionInReplacedElement != CheckSelectionInReplacedElement::No && 716 (HTMLEditUtils::GetInclusiveAncestorReplacedOrVoidElement( 717 *selStartNode->AsContent(), 718 ReplaceOrVoidElementOption::LookForOnlyNonVoidReplacedElement) || 719 HTMLEditUtils::GetInclusiveAncestorReplacedOrVoidElement( 720 *selEndNode->AsContent(), 721 ReplaceOrVoidElementOption::LookForOnlyNonVoidReplacedElement))) { 722 return EditActionResult::CanceledResult(); 723 } 724 725 if (!HTMLEditUtils::IsSimplyEditableNode(*selEndNode)) { 726 return EditActionResult::CanceledResult(); 727 } 728 729 // If anchor node is in an HTML element which has inert attribute, we should 730 // do nothing. 731 // XXX HTMLEditor typically uses first range instead of anchor/focus range. 732 // Therefore, referring first range here is more reasonable than 733 // anchor/focus range of Selection. 734 nsIContent* const selAnchorContent = SelectionRef().GetDirection() == eDirNext 735 ? nsIContent::FromNode(selStartNode) 736 : nsIContent::FromNode(selEndNode); 737 if (selAnchorContent && 738 HTMLEditUtils::ContentIsInert(*selAnchorContent->AsContent())) { 739 return EditActionResult::CanceledResult(); 740 } 741 742 // XXX What does it mean the common ancestor is editable? I have no idea. 743 // It should be in same (active) editing host, and even if it's editable, 744 // there may be non-editable contents in the range. 745 nsINode* commonAncestor = range->GetClosestCommonInclusiveAncestor(); 746 if (MOZ_UNLIKELY(!commonAncestor)) { 747 NS_WARNING( 748 "AbstractRange::GetClosestCommonInclusiveAncestor() returned nullptr"); 749 return Err(NS_ERROR_FAILURE); 750 } 751 return HTMLEditUtils::IsSimplyEditableNode(*commonAncestor) 752 ? EditActionResult::IgnoredResult() 753 : EditActionResult::CanceledResult(); 754 } 755 756 MOZ_CAN_RUN_SCRIPT static nsStaticAtom& MarginPropertyAtomForIndent( 757 nsIContent& aContent) { 758 nsAutoString direction; 759 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedProperty( 760 aContent, *nsGkAtoms::direction, direction); 761 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 762 "CSSEditUtils::GetComputedProperty(nsGkAtoms::direction)" 763 " failed, but ignored"); 764 return direction.EqualsLiteral("rtl") ? *nsGkAtoms::marginRight 765 : *nsGkAtoms::marginLeft; 766 } 767 768 nsresult HTMLEditor::EnsureCaretNotAfterInvisibleBRElement( 769 const Element& aEditingHost) { 770 MOZ_ASSERT(IsEditActionDataAvailable()); 771 MOZ_ASSERT(SelectionRef().IsCollapsed()); 772 773 // If we are after a padding `<br>` element for empty last line in the same 774 // block, then move selection to be before it 775 const nsRange* firstRange = SelectionRef().GetRangeAt(0); 776 if (NS_WARN_IF(!firstRange)) { 777 return NS_ERROR_FAILURE; 778 } 779 780 EditorRawDOMPoint atSelectionStart(firstRange->StartRef()); 781 if (NS_WARN_IF(!atSelectionStart.IsSet())) { 782 return NS_ERROR_FAILURE; 783 } 784 MOZ_ASSERT(atSelectionStart.IsSetAndValid()); 785 786 if (!atSelectionStart.IsInContentNode()) { 787 return NS_OK; 788 } 789 790 nsIContent* previousBRElement = HTMLEditUtils::GetPreviousContent( 791 atSelectionStart, {}, BlockInlineCheck::UseComputedDisplayStyle, 792 &aEditingHost); 793 if (!previousBRElement || !previousBRElement->IsHTMLElement(nsGkAtoms::br) || 794 !previousBRElement->GetParent() || 795 !EditorUtils::IsEditableContent(*previousBRElement->GetParent(), 796 EditorType::HTML) || 797 !HTMLEditUtils::IsInvisibleBRElement(*previousBRElement)) { 798 return NS_OK; 799 } 800 801 const RefPtr<const Element> blockElementAtSelectionStart = 802 HTMLEditUtils::GetInclusiveAncestorElement( 803 *atSelectionStart.ContainerAs<nsIContent>(), 804 HTMLEditUtils::ClosestBlockElement, 805 BlockInlineCheck::UseComputedDisplayStyle); 806 const RefPtr<const Element> parentBlockElementOfBRElement = 807 HTMLEditUtils::GetAncestorElement( 808 *previousBRElement, HTMLEditUtils::ClosestBlockElement, 809 BlockInlineCheck::UseComputedDisplayStyle); 810 811 if (!blockElementAtSelectionStart || 812 blockElementAtSelectionStart != parentBlockElementOfBRElement) { 813 return NS_OK; 814 } 815 816 // If we are here then the selection is right after a padding <br> 817 // element for empty last line that is in the same block as the 818 // selection. We need to move the selection start to be before the 819 // padding <br> element. 820 EditorRawDOMPoint atInvisibleBRElement(previousBRElement); 821 nsresult rv = CollapseSelectionTo(atInvisibleBRElement); 822 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 823 "EditorBase::CollapseSelectionTo() failed"); 824 return rv; 825 } 826 827 nsresult HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() { 828 MOZ_ASSERT(IsEditActionDataAvailable()); 829 830 if (mPaddingBRElementForEmptyEditor) { 831 return NS_OK; 832 } 833 834 // XXX I think that we should not insert a <br> element if we're for a web 835 // content. Probably, this is required only by chrome editors such as 836 // the mail composer of Thunderbird and the composer of SeaMonkey. 837 838 const RefPtr<Element> bodyOrDocumentElement = GetRoot(); 839 if (!bodyOrDocumentElement) { 840 return NS_OK; 841 } 842 843 // Skip adding the padding <br> element for empty editor if body 844 // is read-only. 845 if (!HTMLEditUtils::IsSimplyEditableNode(*bodyOrDocumentElement)) { 846 return NS_OK; 847 } 848 849 // Now we've got the body element. Iterate over the body element's children, 850 // looking for editable content. If no editable content is found, insert the 851 // padding <br> element. 852 EditorType editorType = GetEditorType(); 853 bool isRootEditable = 854 EditorUtils::IsEditableContent(*bodyOrDocumentElement, editorType); 855 for (nsIContent* child = bodyOrDocumentElement->GetFirstChild(); child; 856 child = child->GetNextSibling()) { 857 if (EditorUtils::IsPaddingBRElementForEmptyEditor(*child) || 858 !isRootEditable || EditorUtils::IsEditableContent(*child, editorType) || 859 HTMLEditUtils::IsBlockElement( 860 *child, BlockInlineCheck::UseComputedDisplayStyle)) { 861 return NS_OK; 862 } 863 } 864 865 IgnoredErrorResult ignoredError; 866 AutoEditSubActionNotifier startToHandleEditSubAction( 867 *this, EditSubAction::eCreatePaddingBRElementForEmptyEditor, 868 nsIEditor::eNone, ignoredError); 869 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 870 return ignoredError.StealNSResult(); 871 } 872 NS_WARNING_ASSERTION( 873 !ignoredError.Failed(), 874 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 875 876 Result<CreateElementResult, nsresult> insertPaddingBRElementResultOrError = 877 InsertBRElement(WithTransaction::Yes, 878 BRElementType::PaddingForEmptyEditor, 879 EditorDOMPoint(bodyOrDocumentElement, 0u)); 880 if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { 881 NS_WARNING( 882 "EditorBase::InsertBRElement(WithTransaction::Yes, " 883 "BRElementType::PaddingForEmptyEditor) failed"); 884 return insertPaddingBRElementResultOrError.propagateErr(); 885 } 886 CreateElementResult insertPaddingBRElementResult = 887 insertPaddingBRElementResultOrError.unwrap(); 888 mPaddingBRElementForEmptyEditor = 889 HTMLBRElement::FromNode(insertPaddingBRElementResult.GetNewNode()); 890 nsresult rv = insertPaddingBRElementResult.SuggestCaretPointTo( 891 *this, {SuggestCaret::AndIgnoreTrivialError}); 892 if (NS_FAILED(rv)) { 893 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 894 return rv; 895 } 896 NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 897 "CaretPoint::SuggestCaretPointTo() failed, but ignored"); 898 return NS_OK; 899 } 900 901 nsresult HTMLEditor::EnsureNoPaddingBRElementForEmptyEditor() { 902 MOZ_ASSERT(IsEditActionDataAvailable()); 903 904 if (!mPaddingBRElementForEmptyEditor) { 905 return NS_OK; 906 } 907 908 // If we're an HTML editor, a mutation event listener may recreate padding 909 // <br> element for empty editor again during the call of 910 // DeleteNodeWithTransaction(). So, move it first. 911 RefPtr<HTMLBRElement> paddingBRElement( 912 std::move(mPaddingBRElementForEmptyEditor)); 913 nsresult rv = DeleteNodeWithTransaction(*paddingBRElement); 914 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 915 "EditorBase::DeleteNodeWithTransaction() failed"); 916 return rv; 917 } 918 919 nsresult HTMLEditor::ReflectPaddingBRElementForEmptyEditor() { 920 if (NS_WARN_IF(!mRootElement)) { 921 NS_WARNING("Failed to handle padding BR element due to no root element"); 922 return NS_ERROR_FAILURE; 923 } 924 // The idea here is to see if the magic empty node has suddenly reappeared. If 925 // it has, set our state so we remember it. There is a tradeoff between doing 926 // here and at redo, or doing it everywhere else that might care. Since undo 927 // and redo are relatively rare, it makes sense to take the (small) 928 // performance hit here. 929 nsIContent* firstLeafChild = HTMLEditUtils::GetFirstLeafContent( 930 *mRootElement, {LeafNodeType::OnlyLeafNode}); 931 if (firstLeafChild && 932 EditorUtils::IsPaddingBRElementForEmptyEditor(*firstLeafChild)) { 933 mPaddingBRElementForEmptyEditor = 934 static_cast<HTMLBRElement*>(firstLeafChild); 935 } else { 936 mPaddingBRElementForEmptyEditor = nullptr; 937 } 938 return NS_OK; 939 } 940 941 nsresult HTMLEditor::PrepareInlineStylesForCaret() { 942 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); 943 MOZ_ASSERT(SelectionRef().IsCollapsed()); 944 945 // XXX This method works with the top level edit sub-action, but this 946 // must be wrong if we are handling nested edit action. 947 948 if (TopLevelEditSubActionDataRef().mDidDeleteSelection) { 949 switch (GetTopLevelEditSubAction()) { 950 case EditSubAction::eInsertText: 951 case EditSubAction::eInsertTextComingFromIME: 952 case EditSubAction::eDeleteSelectedContent: { 953 nsresult rv = ReapplyCachedStyles(); 954 if (NS_FAILED(rv)) { 955 NS_WARNING("HTMLEditor::ReapplyCachedStyles() failed"); 956 return rv; 957 } 958 break; 959 } 960 default: 961 break; 962 } 963 } 964 // For most actions we want to clear the cached styles, but there are 965 // exceptions 966 if (!IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction())) { 967 TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear(); 968 } 969 return NS_OK; 970 } 971 972 Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText( 973 const nsAString& aInsertionString, InsertTextFor aPurpose) { 974 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); 975 976 MOZ_LOG( 977 gTextInputLog, LogLevel::Info, 978 ("%p HTMLEditor::HandleInsertText(aInsertionString=\"%s\", aPurpose=%s)", 979 this, NS_ConvertUTF16toUTF8(aInsertionString).get(), 980 ToString(aPurpose).c_str())); 981 982 { 983 Result<EditActionResult, nsresult> result = 984 CanHandleHTMLEditSubAction(CheckSelectionInReplacedElement::No); 985 if (MOZ_UNLIKELY(result.isErr())) { 986 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 987 return result; 988 } 989 if (result.inspect().Canceled()) { 990 return result; 991 } 992 } 993 994 UndefineCaretBidiLevel(); 995 996 // If the selection isn't collapsed, delete it. Don't delete existing inline 997 // tags, because we're hopefully going to insert text (bug 787432). 998 if (!SelectionRef().IsCollapsed() && 999 !InsertingTextForExtantComposition(aPurpose)) { 1000 nsresult rv = 1001 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip); 1002 if (NS_FAILED(rv)) { 1003 NS_WARNING( 1004 "EditorBase::DeleteSelectionAsSubAction(nsIEditor::eNone, " 1005 "nsIEditor::eNoStrip) failed"); 1006 return Err(rv); 1007 } 1008 } 1009 1010 const RefPtr<Element> editingHost = 1011 ComputeEditingHost(LimitInBodyElement::No); 1012 if (NS_WARN_IF(!editingHost)) { 1013 return Err(NS_ERROR_FAILURE); 1014 } 1015 1016 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor(); 1017 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 1018 return Err(NS_ERROR_EDITOR_DESTROYED); 1019 } 1020 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1021 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " 1022 "failed, but ignored"); 1023 1024 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) { 1025 nsresult rv = EnsureCaretNotAfterInvisibleBRElement(*editingHost); 1026 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 1027 return Err(NS_ERROR_EDITOR_DESTROYED); 1028 } 1029 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1030 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " 1031 "failed, but ignored"); 1032 if (NS_SUCCEEDED(rv)) { 1033 nsresult rv = PrepareInlineStylesForCaret(); 1034 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 1035 return Err(NS_ERROR_EDITOR_DESTROYED); 1036 } 1037 NS_WARNING_ASSERTION( 1038 NS_SUCCEEDED(rv), 1039 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); 1040 } 1041 } 1042 1043 EditorDOMPoint pointToInsert = [&]() { 1044 if (InsertingTextForExtantComposition(aPurpose)) { 1045 auto compositionStartPoint = 1046 GetFirstIMESelectionStartPoint<EditorDOMPoint>(); 1047 if (MOZ_LIKELY(compositionStartPoint.IsSet())) { 1048 return compositionStartPoint; 1049 } 1050 } 1051 return GetFirstSelectionStartPoint<EditorDOMPoint>(); 1052 }(); 1053 1054 MOZ_LOG(gTextInputLog, LogLevel::Info, 1055 ("%p HTMLEditor::HandleInsertText(), pointToInsert=%s", this, 1056 ToString(pointToInsert).c_str())); 1057 1058 if (NS_WARN_IF(!pointToInsert.IsSet())) { 1059 return Err(NS_ERROR_FAILURE); 1060 } 1061 1062 // for every property that is set, insert a new inline style node 1063 // XXX I think that if this is second or later composition update, we should 1064 // not change the style because we won't update composition with keeping 1065 // inline elements in composing range. 1066 Result<EditorDOMPoint, nsresult> setStyleResult = 1067 CreateStyleForInsertText(pointToInsert, *editingHost); 1068 if (MOZ_UNLIKELY(setStyleResult.isErr())) { 1069 NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed"); 1070 return setStyleResult.propagateErr(); 1071 } 1072 if (setStyleResult.inspect().IsSet()) { 1073 pointToInsert = setStyleResult.unwrap(); 1074 } 1075 1076 if (NS_WARN_IF(!pointToInsert.IsSetAndValid()) || 1077 NS_WARN_IF(!pointToInsert.IsInContentNode())) { 1078 return Err(NS_ERROR_FAILURE); 1079 } 1080 MOZ_ASSERT(pointToInsert.IsSetAndValid()); 1081 1082 // If the point is not in an element which can contain text nodes, climb up 1083 // the DOM tree. 1084 pointToInsert = HTMLEditUtils::GetPossiblePointToInsert( 1085 pointToInsert, *nsGkAtoms::textTagName, *editingHost); 1086 if (NS_WARN_IF(!pointToInsert.IsSet())) { 1087 return Err(NS_ERROR_FAILURE); 1088 } 1089 MOZ_ASSERT(pointToInsert.IsInContentNode()); 1090 1091 if (InsertingTextForComposition(aPurpose)) { 1092 if (aInsertionString.IsEmpty()) { 1093 // Right now the WhiteSpaceVisibilityKeeper code bails on empty strings, 1094 // but IME needs the InsertTextWithTransaction() call to still happen 1095 // since empty strings are meaningful there. 1096 Result<InsertTextResult, nsresult> insertEmptyTextResultOrError = 1097 InsertTextWithTransaction(aInsertionString, pointToInsert, 1098 InsertTextTo::ExistingTextNodeIfAvailable); 1099 if (MOZ_UNLIKELY(insertEmptyTextResultOrError.isErr())) { 1100 NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed"); 1101 return insertEmptyTextResultOrError.propagateErr(); 1102 } 1103 InsertTextResult insertEmptyTextResult = 1104 insertEmptyTextResultOrError.unwrap(); 1105 // InsertTextWithTransaction() doesn not suggest caret position if it's 1106 // called for IME composition. However, for the safety, let's ignore the 1107 // caret position explicitly. 1108 insertEmptyTextResult.IgnoreCaretPointSuggestion(); 1109 nsresult rv = EnsureNoFollowingUnnecessaryLineBreak( 1110 insertEmptyTextResult.EndOfInsertedTextRef()); 1111 if (NS_FAILED(rv)) { 1112 NS_WARNING( 1113 "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed"); 1114 return Err(rv); 1115 } 1116 const EditorDOMPoint& endOfInsertedText = 1117 insertEmptyTextResult.EndOfInsertedTextRef(); 1118 if (endOfInsertedText.IsInTextNode() && 1119 !endOfInsertedText.IsStartOfContainer()) { 1120 nsresult rv = WhiteSpaceVisibilityKeeper:: 1121 NormalizeVisibleWhiteSpacesWithoutDeletingInvisibleWhiteSpaces( 1122 *this, endOfInsertedText.AsInText().PreviousPoint()); 1123 if (NS_FAILED(rv)) { 1124 NS_WARNING( 1125 "WhiteSpaceVisibilityKeeper::" 1126 "NormalizeVisibleWhiteSpacesWithoutDeletingInvisibleWhiteSpaces()" 1127 " failed"); 1128 return Err(rv); 1129 } 1130 if (NS_WARN_IF( 1131 !endOfInsertedText.IsInContentNodeAndValidInComposedDoc())) { 1132 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1133 } 1134 } 1135 // If we replaced non-empty composition string with an empty string, 1136 // its preceding character may be a collapsible ASCII white-space. 1137 // Therefore, we may need to insert a padding <br> after the white-space. 1138 Result<CreateLineBreakResult, nsresult> 1139 insertPaddingBRElementResultOrError = InsertPaddingBRElementIfNeeded( 1140 insertEmptyTextResult.EndOfInsertedTextRef(), nsIEditor::eNoStrip, 1141 *editingHost); 1142 if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { 1143 NS_WARNING( 1144 "HTMLEditor::InsertPaddingBRElementIfNeeded(eNoStrip) failed"); 1145 return insertPaddingBRElementResultOrError.propagateErr(); 1146 } 1147 insertPaddingBRElementResultOrError.unwrap().IgnoreCaretPointSuggestion(); 1148 // Then, collapse caret after the empty text inserted position, i.e., 1149 // whether the removed composition string was. 1150 if (AllowsTransactionsToChangeSelection()) { 1151 nsresult rv = CollapseSelectionTo(endOfInsertedText); 1152 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 1153 return Err(rv); 1154 } 1155 NS_WARNING_ASSERTION( 1156 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 1157 "CaretPoint::SuggestCaretPointTo() failed, but ignored"); 1158 } 1159 return EditActionResult::HandledResult(); 1160 } 1161 1162 EditorDOMPoint endOfInsertedText; 1163 { 1164 AutoTrackDOMPoint trackPointToInsert(RangeUpdaterRef(), &pointToInsert); 1165 const auto compositionEndPoint = 1166 GetLastIMESelectionEndPoint<EditorDOMPoint>(); 1167 Result<InsertTextResult, nsresult> replaceTextResult = 1168 WhiteSpaceVisibilityKeeper::InsertOrUpdateCompositionString( 1169 *this, aInsertionString, 1170 compositionEndPoint.IsSet() 1171 ? EditorDOMRange(pointToInsert, compositionEndPoint) 1172 : EditorDOMRange(pointToInsert), 1173 aPurpose); 1174 if (MOZ_UNLIKELY(replaceTextResult.isErr())) { 1175 NS_WARNING("WhiteSpaceVisibilityKeeper::ReplaceText() failed"); 1176 return replaceTextResult.propagateErr(); 1177 } 1178 InsertTextResult unwrappedReplaceTextResult = replaceTextResult.unwrap(); 1179 nsresult rv = EnsureNoFollowingUnnecessaryLineBreak( 1180 unwrappedReplaceTextResult.EndOfInsertedTextRef()); 1181 if (NS_FAILED(rv)) { 1182 NS_WARNING( 1183 "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed"); 1184 return Err(rv); 1185 } 1186 endOfInsertedText = unwrappedReplaceTextResult.EndOfInsertedTextRef(); 1187 if (InsertingTextForCommittingComposition(aPurpose)) { 1188 // If we're committing the composition, 1189 // WhiteSpaceVisibilityKeeper::InsertOrUpdateCompositionString() may 1190 // replace the last character of the composition string when it's a 1191 // white-space. Then, Selection will be moved before the last 1192 // character. So, we need to adjust Selection here. 1193 nsresult rv = unwrappedReplaceTextResult.SuggestCaretPointTo( 1194 *this, {SuggestCaret::OnlyIfHasSuggestion, 1195 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 1196 SuggestCaret::AndIgnoreTrivialError}); 1197 if (NS_FAILED(rv)) { 1198 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 1199 return Err(rv); 1200 } 1201 NS_WARNING_ASSERTION( 1202 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 1203 "CaretPoint::SuggestCaretPoint() failed, but ignored"); 1204 } else { 1205 // CompositionTransaction should've set selection so that we should 1206 // ignore caret suggestion. 1207 unwrappedReplaceTextResult.IgnoreCaretPointSuggestion(); 1208 } 1209 } 1210 1211 if (!InsertingTextForCommittingComposition(aPurpose)) { 1212 const auto newCompositionStartPoint = 1213 GetFirstIMESelectionStartPoint<EditorDOMPoint>(); 1214 const auto newCompositionEndPoint = 1215 GetLastIMESelectionEndPoint<EditorDOMPoint>(); 1216 if (NS_WARN_IF(!newCompositionStartPoint.IsSet()) || 1217 NS_WARN_IF(!newCompositionEndPoint.IsSet())) { 1218 // Mutation event listener has changed the DOM tree... 1219 return EditActionResult::HandledResult(); 1220 } 1221 nsresult rv = 1222 TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd( 1223 newCompositionStartPoint.ToRawRangeBoundary(), 1224 newCompositionEndPoint.ToRawRangeBoundary()); 1225 if (NS_FAILED(rv)) { 1226 NS_WARNING("nsRange::SetStartAndEnd() failed"); 1227 return Err(rv); 1228 } 1229 } else { 1230 if (NS_WARN_IF(!endOfInsertedText.IsSetAndValidInComposedDoc()) || 1231 NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) { 1232 return EditActionResult::HandledResult(); 1233 } 1234 nsresult rv = 1235 TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd( 1236 pointToInsert.ToRawRangeBoundary(), 1237 endOfInsertedText.ToRawRangeBoundary()); 1238 if (NS_FAILED(rv)) { 1239 NS_WARNING("nsRange::SetStartAndEnd() failed"); 1240 return Err(rv); 1241 } 1242 } 1243 return EditActionResult::HandledResult(); 1244 } 1245 1246 MOZ_ASSERT(!InsertingTextForComposition(aPurpose)); 1247 1248 // find where we are 1249 EditorDOMPoint currentPoint(pointToInsert); 1250 1251 // is our text going to be PREformatted? 1252 // We remember this so that we know how to handle tabs. 1253 const bool isWhiteSpaceCollapsible = !EditorUtils::IsWhiteSpacePreformatted( 1254 *pointToInsert.ContainerAs<nsIContent>()); 1255 const Maybe<LineBreakType> lineBreakType = GetPreferredLineBreakType( 1256 *pointToInsert.ContainerAs<nsIContent>(), *editingHost); 1257 if (NS_WARN_IF(lineBreakType.isNothing())) { 1258 return Err(NS_ERROR_FAILURE); 1259 } 1260 1261 // turn off the edit listener: we know how to 1262 // build the "doc changed range" ourselves, and it's 1263 // must faster to do it once here than to track all 1264 // the changes one at a time. 1265 AutoRestore<bool> disableListener( 1266 EditSubActionDataRef().mAdjustChangedRangeFromListener); 1267 EditSubActionDataRef().mAdjustChangedRangeFromListener = false; 1268 1269 // don't change my selection in subtransactions 1270 AutoTransactionsConserveSelection dontChangeMySelection(*this); 1271 { 1272 AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToInsert); 1273 1274 const auto GetInsertTextTo = [](int32_t aInclusiveNextLinefeedOffset, 1275 uint32_t aLineStartOffset) { 1276 if (aInclusiveNextLinefeedOffset > 0) { 1277 return aLineStartOffset > 0 1278 // If we'll insert a <br> and we're inserting 2nd or later 1279 // line, we should always create new `Text` since it'll be 1280 // between 2 <br> elements. 1281 ? InsertTextTo::AlwaysCreateNewTextNode 1282 // If we'll insert a <br> and we're inserting first line, 1283 // we should append text to preceding text node, but 1284 // we don't want to insert it to a a following text node 1285 // because of avoiding to split the `Text`. 1286 : InsertTextTo::ExistingTextNodeIfAvailableAndNotStart; 1287 } 1288 // If we're inserting the last line, the text should be inserted to 1289 // start of the following `Text` if there is or middle of the `Text` 1290 // at insertion position if we're inserting only the line. 1291 return InsertTextTo::ExistingTextNodeIfAvailable; 1292 }; 1293 1294 // for efficiency, break out the pre case separately. This is because 1295 // its a lot cheaper to search the input string for only newlines than 1296 // it is to search for both tabs and newlines. 1297 if (!isWhiteSpaceCollapsible || IsPlaintextMailComposer()) { 1298 if (*lineBreakType == LineBreakType::Linefeed) { 1299 // Both Chrome and us inserts a preformatted linefeed with its own 1300 // `Text` node in various cases. However, when inserting multiline 1301 // text, we should insert a `Text` because Chrome does so and the 1302 // comment field in https://discussions.apple.com/ handles the new 1303 // `Text` to split each line into a paragraph. At that time, it's 1304 // not assumed that inserted text is split at every linefeed. 1305 MOZ_ASSERT(*lineBreakType == LineBreakType::Linefeed); 1306 Result<InsertTextResult, nsresult> insertTextResult = 1307 InsertTextWithTransaction( 1308 aInsertionString, currentPoint, 1309 InsertTextTo::ExistingTextNodeIfAvailable); 1310 if (MOZ_UNLIKELY(insertTextResult.isErr())) { 1311 NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed"); 1312 return insertTextResult.propagateErr(); 1313 } 1314 // Ignore the caret suggestion because of `dontChangeMySelection` 1315 // above. 1316 insertTextResult.inspect().IgnoreCaretPointSuggestion(); 1317 if (insertTextResult.inspect().Handled()) { 1318 pointToInsert = currentPoint = insertTextResult.unwrap() 1319 .EndOfInsertedTextRef() 1320 .To<EditorDOMPoint>(); 1321 } else { 1322 pointToInsert = currentPoint; 1323 } 1324 } else { 1325 MOZ_ASSERT(*lineBreakType == LineBreakType::BRElement); 1326 uint32_t nextOffset = 0; 1327 while (nextOffset < aInsertionString.Length()) { 1328 const uint32_t lineStartOffset = nextOffset; 1329 const int32_t inclusiveNextLinefeedOffset = 1330 aInsertionString.FindChar(nsCRT::LF, lineStartOffset); 1331 const uint32_t lineLength = 1332 inclusiveNextLinefeedOffset != -1 1333 ? static_cast<uint32_t>(inclusiveNextLinefeedOffset) - 1334 lineStartOffset 1335 : aInsertionString.Length() - lineStartOffset; 1336 if (lineLength) { 1337 // lineText does not include the preformatted line break. 1338 const nsDependentSubstring lineText(aInsertionString, 1339 lineStartOffset, lineLength); 1340 Result<InsertTextResult, nsresult> insertTextResult = 1341 InsertTextWithTransaction( 1342 lineText, currentPoint, 1343 GetInsertTextTo(inclusiveNextLinefeedOffset, 1344 lineStartOffset)); 1345 if (MOZ_UNLIKELY(insertTextResult.isErr())) { 1346 NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed"); 1347 return insertTextResult.propagateErr(); 1348 } 1349 // Ignore the caret suggestion because of `dontChangeMySelection` 1350 // above. 1351 insertTextResult.inspect().IgnoreCaretPointSuggestion(); 1352 if (insertTextResult.inspect().Handled()) { 1353 pointToInsert = currentPoint = insertTextResult.unwrap() 1354 .EndOfInsertedTextRef() 1355 .To<EditorDOMPoint>(); 1356 } else { 1357 pointToInsert = currentPoint; 1358 } 1359 if (inclusiveNextLinefeedOffset < 0) { 1360 break; // We reached the last line 1361 } 1362 } 1363 MOZ_ASSERT(inclusiveNextLinefeedOffset >= 0); 1364 Result<CreateLineBreakResult, nsresult> insertLineBreakResultOrError = 1365 InsertLineBreak(WithTransaction::Yes, *lineBreakType, 1366 currentPoint); 1367 if (MOZ_UNLIKELY(insertLineBreakResultOrError.isErr())) { 1368 NS_WARNING(nsPrintfCString("HTMLEditor::InsertLineBreak(" 1369 "WithTransaction::Yes, %s) failed", 1370 ToString(*lineBreakType).c_str()) 1371 .get()); 1372 return insertLineBreakResultOrError.propagateErr(); 1373 } 1374 CreateLineBreakResult insertLineBreakResult = 1375 insertLineBreakResultOrError.unwrap(); 1376 // We don't want to update selection here because we've blocked 1377 // InsertNodeTransaction updating selection with 1378 // dontChangeMySelection. 1379 insertLineBreakResult.IgnoreCaretPointSuggestion(); 1380 MOZ_ASSERT(!AllowsTransactionsToChangeSelection()); 1381 1382 nextOffset = inclusiveNextLinefeedOffset + 1; 1383 pointToInsert = 1384 insertLineBreakResult.AfterLineBreak<EditorDOMPoint>(); 1385 currentPoint.SetAfter(&insertLineBreakResult.LineBreakContentRef()); 1386 if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc()) || 1387 NS_WARN_IF(!currentPoint.IsSetAndValidInComposedDoc())) { 1388 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1389 } 1390 } 1391 } 1392 } else { 1393 uint32_t nextOffset = 0; 1394 while (nextOffset < aInsertionString.Length()) { 1395 const uint32_t lineStartOffset = nextOffset; 1396 const int32_t inclusiveNextLinefeedOffset = 1397 aInsertionString.FindChar(nsCRT::LF, lineStartOffset); 1398 const uint32_t lineLength = 1399 inclusiveNextLinefeedOffset != -1 1400 ? static_cast<uint32_t>(inclusiveNextLinefeedOffset) - 1401 lineStartOffset 1402 : aInsertionString.Length() - lineStartOffset; 1403 1404 if (lineLength) { 1405 auto insertTextResult = 1406 [&]() MOZ_CAN_RUN_SCRIPT -> Result<InsertTextResult, nsresult> { 1407 // lineText does not include the preformatted line break. 1408 const nsDependentSubstring lineText(aInsertionString, 1409 lineStartOffset, lineLength); 1410 if (!lineText.Contains(u'\t')) { 1411 return WhiteSpaceVisibilityKeeper::InsertText( 1412 *this, lineText, currentPoint, 1413 GetInsertTextTo(inclusiveNextLinefeedOffset, 1414 lineStartOffset)); 1415 } 1416 nsAutoString formattedLineText(lineText); 1417 formattedLineText.ReplaceSubstring(u"\t"_ns, u" "_ns); 1418 return WhiteSpaceVisibilityKeeper::InsertText( 1419 *this, formattedLineText, currentPoint, 1420 GetInsertTextTo(inclusiveNextLinefeedOffset, lineStartOffset)); 1421 }(); 1422 if (MOZ_UNLIKELY(insertTextResult.isErr())) { 1423 NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed"); 1424 return insertTextResult.propagateErr(); 1425 } 1426 // Ignore the caret suggestion because of `dontChangeMySelection` 1427 // above. 1428 insertTextResult.inspect().IgnoreCaretPointSuggestion(); 1429 if (insertTextResult.inspect().Handled()) { 1430 pointToInsert = currentPoint = 1431 insertTextResult.unwrap().EndOfInsertedTextRef(); 1432 } else { 1433 pointToInsert = currentPoint; 1434 } 1435 if (inclusiveNextLinefeedOffset < 0) { 1436 break; // We reached the last line 1437 } 1438 } 1439 1440 Result<CreateLineBreakResult, nsresult> insertLineBreakResultOrError = 1441 WhiteSpaceVisibilityKeeper::InsertLineBreak(*lineBreakType, *this, 1442 currentPoint); 1443 if (MOZ_UNLIKELY(insertLineBreakResultOrError.isErr())) { 1444 NS_WARNING( 1445 nsPrintfCString( 1446 "WhiteSpaceVisibilityKeeper::InsertLineBreak(%s) failed", 1447 ToString(*lineBreakType).c_str()) 1448 .get()); 1449 return insertLineBreakResultOrError.propagateErr(); 1450 } 1451 CreateLineBreakResult insertLineBreakResult = 1452 insertLineBreakResultOrError.unwrap(); 1453 // TODO: Some methods called for handling non-preformatted text use 1454 // ComputeEditingHost(). Therefore, they depend on the latest 1455 // selection. So we cannot skip updating selection here. 1456 nsresult rv = insertLineBreakResult.SuggestCaretPointTo( 1457 *this, {SuggestCaret::OnlyIfHasSuggestion, 1458 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 1459 SuggestCaret::AndIgnoreTrivialError}); 1460 if (NS_FAILED(rv)) { 1461 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed"); 1462 return Err(rv); 1463 } 1464 NS_WARNING_ASSERTION( 1465 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 1466 "CreateElementResult::SuggestCaretPointTo() failed, but ignored"); 1467 nextOffset = inclusiveNextLinefeedOffset + 1; 1468 pointToInsert = insertLineBreakResult.AfterLineBreak<EditorDOMPoint>(); 1469 currentPoint.SetAfter(&insertLineBreakResult.LineBreakContentRef()); 1470 if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc()) || 1471 NS_WARN_IF(!currentPoint.IsSetAndValidInComposedDoc())) { 1472 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1473 } 1474 } 1475 } 1476 1477 // After this block, pointToInsert is updated by AutoTrackDOMPoint. 1478 } 1479 1480 if (currentPoint.IsSet()) { 1481 // If we appended a collapsible white-space to the end of the text node, 1482 // its following content may be removed by the web app. Then, we need to 1483 // keep it visible even if it becomes immediately before a block boundary. 1484 // For referring the node from our mutation observer, we need to store the 1485 // text node temporarily. 1486 if (currentPoint.IsInTextNode() && 1487 MOZ_LIKELY(!currentPoint.IsStartOfContainer()) && 1488 currentPoint.IsEndOfContainer() && 1489 currentPoint.IsPreviousCharCollapsibleASCIISpace()) { 1490 mLastCollapsibleWhiteSpaceAppendedTextNode = 1491 currentPoint.ContainerAs<Text>(); 1492 } 1493 nsresult rv = EnsureNoFollowingUnnecessaryLineBreak(currentPoint); 1494 if (NS_FAILED(rv)) { 1495 NS_WARNING("HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed"); 1496 return Err(rv); 1497 } 1498 currentPoint.SetInterlinePosition(InterlinePosition::EndOfLine); 1499 rv = CollapseSelectionTo(currentPoint); 1500 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 1501 return Err(NS_ERROR_EDITOR_DESTROYED); 1502 } 1503 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1504 "Selection::Collapse() failed, but ignored"); 1505 1506 // manually update the doc changed range so that AfterEdit will clean up 1507 // the correct portion of the document. 1508 rv = TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd( 1509 pointToInsert.ToRawRangeBoundary(), currentPoint.ToRawRangeBoundary()); 1510 if (NS_FAILED(rv)) { 1511 NS_WARNING("nsRange::SetStartAndEnd() failed"); 1512 return Err(rv); 1513 } 1514 return EditActionResult::HandledResult(); 1515 } 1516 1517 DebugOnly<nsresult> rvIgnored = 1518 SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine); 1519 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1520 "Selection::SetInterlinePosition(InterlinePosition::" 1521 "EndOfLine) failed, but ignored"); 1522 rv = TopLevelEditSubActionDataRef().mChangedRange->CollapseTo(pointToInsert); 1523 if (NS_FAILED(rv)) { 1524 NS_WARNING("nsRange::CollapseTo() failed"); 1525 return Err(rv); 1526 } 1527 return EditActionResult::HandledResult(); 1528 } 1529 1530 HTMLEditor::CharPointData 1531 HTMLEditor::GetPreviousCharPointDataForNormalizingWhiteSpaces( 1532 const EditorDOMPointInText& aPoint) const { 1533 MOZ_ASSERT(aPoint.IsSetAndValid()); 1534 1535 if (!aPoint.IsStartOfContainer()) { 1536 return CharPointData::InSameTextNode( 1537 HTMLEditor::GetPreviousCharPointType(aPoint)); 1538 } 1539 const auto previousCharPoint = 1540 WSRunScanner::GetPreviousCharPoint<EditorRawDOMPointInText>( 1541 {WSRunScanner::Option::OnlyEditableNodes}, aPoint); 1542 if (!previousCharPoint.IsSet()) { 1543 return CharPointData::InDifferentTextNode(CharPointType::TextEnd); 1544 } 1545 return CharPointData::InDifferentTextNode( 1546 HTMLEditor::GetCharPointType(previousCharPoint)); 1547 } 1548 1549 HTMLEditor::CharPointData 1550 HTMLEditor::GetInclusiveNextCharPointDataForNormalizingWhiteSpaces( 1551 const EditorDOMPointInText& aPoint) const { 1552 MOZ_ASSERT(aPoint.IsSetAndValid()); 1553 1554 if (!aPoint.IsEndOfContainer()) { 1555 return CharPointData::InSameTextNode(HTMLEditor::GetCharPointType(aPoint)); 1556 } 1557 const auto nextCharPoint = 1558 WSRunScanner::GetInclusiveNextCharPoint<EditorRawDOMPointInText>( 1559 {WSRunScanner::Option::OnlyEditableNodes}, aPoint); 1560 if (!nextCharPoint.IsSet()) { 1561 return CharPointData::InDifferentTextNode(CharPointType::TextEnd); 1562 } 1563 return CharPointData::InDifferentTextNode( 1564 HTMLEditor::GetCharPointType(nextCharPoint)); 1565 } 1566 1567 // static 1568 void HTMLEditor::NormalizeAllWhiteSpaceSequences( 1569 nsString& aResult, const CharPointData& aPreviousCharPointData, 1570 const CharPointData& aNextCharPointData, Linefeed aLinefeed) { 1571 MOZ_ASSERT(!aResult.IsEmpty()); 1572 1573 const auto IsCollapsibleChar = [&](char16_t aChar) { 1574 if (aChar == HTMLEditUtils::kNewLine) { 1575 return aLinefeed == Linefeed::Preformatted; 1576 } 1577 return nsCRT::IsAsciiSpace(aChar); 1578 }; 1579 const auto IsCollapsibleCharOrNBSP = [&](char16_t aChar) { 1580 return aChar == HTMLEditUtils::kNBSP || IsCollapsibleChar(aChar); 1581 }; 1582 1583 const uint32_t length = aResult.Length(); 1584 for (uint32_t offset = 0; offset < length; offset++) { 1585 const char16_t ch = aResult[offset]; 1586 if (!IsCollapsibleCharOrNBSP(ch)) { 1587 continue; 1588 } 1589 const CharPointData previousCharData = [&]() { 1590 if (offset) { 1591 const char16_t prevChar = aResult[offset - 1u]; 1592 return CharPointData::InSameTextNode( 1593 prevChar == HTMLEditUtils::kNewLine 1594 ? CharPointType::PreformattedLineBreak 1595 : CharPointType::VisibleChar); 1596 } 1597 return aPreviousCharPointData; 1598 }(); 1599 const uint32_t endOffset = [&]() { 1600 for (const uint32_t i : IntegerRange(offset, length)) { 1601 if (IsCollapsibleCharOrNBSP(aResult[i])) { 1602 continue; 1603 } 1604 return i; 1605 } 1606 return length; 1607 }(); 1608 const CharPointData nextCharData = [&]() { 1609 if (endOffset < length) { 1610 const char16_t nextChar = aResult[endOffset]; 1611 return CharPointData::InSameTextNode( 1612 nextChar == HTMLEditUtils::kNewLine 1613 ? CharPointType::PreformattedLineBreak 1614 : CharPointType::VisibleChar); 1615 } 1616 return aNextCharPointData; 1617 }(); 1618 HTMLEditor::ReplaceStringWithNormalizedWhiteSpaceSequence( 1619 aResult, offset, endOffset - offset, previousCharData, nextCharData); 1620 offset = endOffset; 1621 } 1622 } 1623 1624 // static 1625 void HTMLEditor::GenerateWhiteSpaceSequence( 1626 nsString& aResult, uint32_t aLength, 1627 const CharPointData& aPreviousCharPointData, 1628 const CharPointData& aNextCharPointData) { 1629 MOZ_ASSERT(aResult.IsEmpty()); 1630 MOZ_ASSERT(aLength); 1631 1632 aResult.SetLength(aLength); 1633 HTMLEditor::ReplaceStringWithNormalizedWhiteSpaceSequence( 1634 aResult, 0u, aLength, aPreviousCharPointData, aNextCharPointData); 1635 } 1636 1637 // static 1638 void HTMLEditor::ReplaceStringWithNormalizedWhiteSpaceSequence( 1639 nsString& aResult, uint32_t aOffset, uint32_t aLength, 1640 const CharPointData& aPreviousCharPointData, 1641 const CharPointData& aNextCharPointData) { 1642 MOZ_ASSERT(!aResult.IsEmpty()); 1643 MOZ_ASSERT(aLength); 1644 MOZ_ASSERT(aOffset < aResult.Length()); 1645 MOZ_ASSERT(aOffset + aLength <= aResult.Length()); 1646 1647 // For now, this method does not assume that result will be append to 1648 // white-space sequence in the text node. 1649 MOZ_ASSERT(aPreviousCharPointData.AcrossTextNodeBoundary() || 1650 !aPreviousCharPointData.IsCollapsibleWhiteSpace()); 1651 // For now, this method does not assume that the result will be inserted 1652 // into white-space sequence nor start of white-space sequence. 1653 MOZ_ASSERT(aNextCharPointData.AcrossTextNodeBoundary() || 1654 !aNextCharPointData.IsCollapsibleWhiteSpace()); 1655 1656 if (aLength == 1) { 1657 // Even if previous/next char is in different text node, we should put 1658 // an ASCII white-space between visible characters. 1659 // XXX This means that this does not allow to put an NBSP in HTML editor 1660 // without preformatted style. However, Chrome has same issue too. 1661 if (aPreviousCharPointData.Type() == CharPointType::VisibleChar && 1662 aNextCharPointData.Type() == CharPointType::VisibleChar) { 1663 aResult.SetCharAt(HTMLEditUtils::kSpace, aOffset); 1664 return; 1665 } 1666 // If it's start or end of text, put an NBSP. 1667 if (aPreviousCharPointData.Type() == CharPointType::TextEnd || 1668 aNextCharPointData.Type() == CharPointType::TextEnd) { 1669 aResult.SetCharAt(HTMLEditUtils::kNBSP, aOffset); 1670 return; 1671 } 1672 // If the character is next to a preformatted linefeed, we need to put 1673 // an NBSP for avoiding collapsed into the linefeed. 1674 if (aPreviousCharPointData.Type() == CharPointType::PreformattedLineBreak || 1675 aNextCharPointData.Type() == CharPointType::PreformattedLineBreak) { 1676 aResult.SetCharAt(HTMLEditUtils::kNBSP, aOffset); 1677 return; 1678 } 1679 // Now, the white-space will be inserted to a white-space sequence, but not 1680 // end of text. We can put an ASCII white-space only when both sides are 1681 // not ASCII white-spaces. 1682 aResult.SetCharAt( 1683 aPreviousCharPointData.Type() == CharPointType::ASCIIWhiteSpace || 1684 aNextCharPointData.Type() == CharPointType::ASCIIWhiteSpace 1685 ? HTMLEditUtils::kNBSP 1686 : HTMLEditUtils::kSpace, 1687 aOffset); 1688 return; 1689 } 1690 1691 // Generate pairs of NBSP and ASCII white-space. 1692 bool appendNBSP = true; // Basically, starts with an NBSP. 1693 char16_t* const lastChar = aResult.BeginWriting() + aOffset + aLength - 1; 1694 for (char16_t* iter = aResult.BeginWriting() + aOffset; iter != lastChar; 1695 iter++) { 1696 *iter = appendNBSP ? HTMLEditUtils::kNBSP : HTMLEditUtils::kSpace; 1697 appendNBSP = !appendNBSP; 1698 } 1699 1700 // If the final one is expected to an NBSP, we can put an NBSP simply. 1701 if (appendNBSP) { 1702 *lastChar = HTMLEditUtils::kNBSP; 1703 return; 1704 } 1705 1706 // If next char point is end of text node, an ASCII white-space or 1707 // preformatted linefeed, we need to put an NBSP. 1708 *lastChar = 1709 aNextCharPointData.AcrossTextNodeBoundary() || 1710 aNextCharPointData.Type() == CharPointType::ASCIIWhiteSpace || 1711 aNextCharPointData.Type() == CharPointType::PreformattedLineBreak 1712 ? HTMLEditUtils::kNBSP 1713 : HTMLEditUtils::kSpace; 1714 } 1715 1716 HTMLEditor::NormalizedStringToInsertText 1717 HTMLEditor::NormalizeWhiteSpacesToInsertText( 1718 const EditorDOMPoint& aPointToInsert, const nsAString& aStringToInsert, 1719 NormalizeSurroundingWhiteSpaces aNormalizeSurroundingWhiteSpaces) const { 1720 MOZ_ASSERT(aPointToInsert.IsSet()); 1721 1722 // If white-spaces are preformatted, we don't need to normalize white-spaces. 1723 if (EditorUtils::IsWhiteSpacePreformatted( 1724 *aPointToInsert.ContainerAs<nsIContent>())) { 1725 return NormalizedStringToInsertText(aStringToInsert, aPointToInsert); 1726 } 1727 1728 Text* const textNode = aPointToInsert.GetContainerAs<Text>(); 1729 const CharacterDataBuffer* const characterDataBuffer = 1730 textNode ? &textNode->DataBuffer() : nullptr; 1731 const bool isNewLineCollapsible = !EditorUtils::IsNewLinePreformatted( 1732 *aPointToInsert.ContainerAs<nsIContent>()); 1733 1734 // We don't want to make invisible things visible with this normalization. 1735 // Therefore, we need to know whether there are invisible leading and/or 1736 // trailing white-spaces in the `Text`. 1737 1738 // Then, compute visible white-space length before/after the insertion point. 1739 // Note that these lengths may contain invisible white-spaces. 1740 const uint32_t precedingWhiteSpaceLength = [&]() { 1741 if (!textNode || !aNormalizeSurroundingWhiteSpaces || 1742 aPointToInsert.IsStartOfContainer()) { 1743 return 0u; 1744 } 1745 const auto nonWhiteSpaceOffset = 1746 HTMLEditUtils::GetPreviousNonCollapsibleCharOffset( 1747 *textNode, aPointToInsert.Offset(), 1748 {HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible}); 1749 const uint32_t firstWhiteSpaceOffset = 1750 nonWhiteSpaceOffset ? *nonWhiteSpaceOffset + 1u : 0u; 1751 return aPointToInsert.Offset() - firstWhiteSpaceOffset; 1752 }(); 1753 const uint32_t followingWhiteSpaceLength = [&]() { 1754 if (!textNode || !aNormalizeSurroundingWhiteSpaces || 1755 aPointToInsert.IsEndOfContainer()) { 1756 return 0u; 1757 } 1758 MOZ_ASSERT(characterDataBuffer); 1759 const auto nonWhiteSpaceOffset = 1760 HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset( 1761 *textNode, aPointToInsert.Offset(), 1762 {HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible}); 1763 MOZ_ASSERT(nonWhiteSpaceOffset.valueOr(characterDataBuffer->GetLength()) >= 1764 aPointToInsert.Offset()); 1765 return nonWhiteSpaceOffset.valueOr(characterDataBuffer->GetLength()) - 1766 aPointToInsert.Offset(); 1767 }(); 1768 1769 // Now, we can know invisible white-space length in precedingWhiteSpaceLength 1770 // and followingWhiteSpaceLength. 1771 const uint32_t precedingInvisibleWhiteSpaceCount = 1772 textNode 1773 ? HTMLEditUtils::GetInvisibleWhiteSpaceCount( 1774 *textNode, aPointToInsert.Offset() - precedingWhiteSpaceLength, 1775 precedingWhiteSpaceLength) 1776 : 0u; 1777 MOZ_ASSERT(precedingWhiteSpaceLength >= precedingInvisibleWhiteSpaceCount); 1778 const uint32_t newPrecedingWhiteSpaceLength = 1779 precedingWhiteSpaceLength - precedingInvisibleWhiteSpaceCount; 1780 const uint32_t followingInvisibleSpaceCount = 1781 textNode 1782 ? HTMLEditUtils::GetInvisibleWhiteSpaceCount( 1783 *textNode, aPointToInsert.Offset(), followingWhiteSpaceLength) 1784 : 0u; 1785 MOZ_ASSERT(followingWhiteSpaceLength >= followingInvisibleSpaceCount); 1786 const uint32_t newFollowingWhiteSpaceLength = 1787 followingWhiteSpaceLength - followingInvisibleSpaceCount; 1788 1789 const nsAutoString stringToInsertWithSurroundingSpaces = 1790 [&]() -> nsAutoString { 1791 if (!newPrecedingWhiteSpaceLength && !newFollowingWhiteSpaceLength) { 1792 return nsAutoString(aStringToInsert); 1793 } 1794 nsAutoString str; 1795 str.SetCapacity(aStringToInsert.Length() + newPrecedingWhiteSpaceLength + 1796 newFollowingWhiteSpaceLength); 1797 for ([[maybe_unused]] auto unused : 1798 IntegerRange(newPrecedingWhiteSpaceLength)) { 1799 str.Append(' '); 1800 } 1801 str.Append(aStringToInsert); 1802 for ([[maybe_unused]] auto unused : 1803 IntegerRange(newFollowingWhiteSpaceLength)) { 1804 str.Append(' '); 1805 } 1806 return str; 1807 }(); 1808 1809 const uint32_t insertionOffsetInTextNode = 1810 aPointToInsert.IsInTextNode() ? aPointToInsert.Offset() : 0u; 1811 NormalizedStringToInsertText result( 1812 stringToInsertWithSurroundingSpaces, insertionOffsetInTextNode, 1813 insertionOffsetInTextNode - precedingWhiteSpaceLength, // replace start 1814 precedingWhiteSpaceLength + followingWhiteSpaceLength, // replace length 1815 newPrecedingWhiteSpaceLength, newFollowingWhiteSpaceLength); 1816 1817 // Now, normalize the inserting string. 1818 // Note that if the caller does not want to normalize the following 1819 // white-spaces, we always need to guarantee that neither the first character 1820 // nor the last character of the insertion string is not collapsible, i.e., if 1821 // each one is a collapsible white-space, we need to replace them an NBSP to 1822 // keep the visibility of the collapsible white-spaces. Therefore, if 1823 // aNormalizeSurroundingWhiteSpaces is "No", we need to treat the insertion 1824 // string is the only characters in the `Text`. 1825 HTMLEditor::NormalizeAllWhiteSpaceSequences( 1826 result.mNormalizedString, 1827 CharPointData::InSameTextNode( 1828 !characterDataBuffer || !result.mReplaceStartOffset || 1829 !aNormalizeSurroundingWhiteSpaces 1830 ? CharPointType::TextEnd 1831 : (characterDataBuffer->CharAt(result.mReplaceStartOffset - 1u) == 1832 HTMLEditUtils::kNewLine 1833 ? CharPointType::PreformattedLineBreak 1834 : CharPointType::VisibleChar)), 1835 CharPointData::InSameTextNode( 1836 !characterDataBuffer || 1837 result.mReplaceEndOffset >= 1838 characterDataBuffer->GetLength() || 1839 !aNormalizeSurroundingWhiteSpaces 1840 ? CharPointType::TextEnd 1841 : (characterDataBuffer->CharAt(result.mReplaceEndOffset) == 1842 HTMLEditUtils::kNewLine 1843 ? CharPointType::PreformattedLineBreak 1844 : CharPointType::VisibleChar)), 1845 isNewLineCollapsible ? Linefeed::Collapsible : Linefeed::Preformatted); 1846 return result; 1847 } 1848 1849 HTMLEditor::ReplaceWhiteSpacesData HTMLEditor::GetNormalizedStringAt( 1850 const EditorDOMPointInText& aPoint) const { 1851 MOZ_ASSERT(aPoint.IsSet()); 1852 1853 // If white-spaces are preformatted, we don't need to normalize white-spaces. 1854 if (EditorUtils::IsWhiteSpacePreformatted(*aPoint.ContainerAs<Text>())) { 1855 return ReplaceWhiteSpacesData(); 1856 } 1857 1858 const Text& textNode = *aPoint.ContainerAs<Text>(); 1859 const CharacterDataBuffer& characterDataBuffer = textNode.DataBuffer(); 1860 1861 // We don't want to make invisible things visible with this normalization. 1862 // Therefore, we need to know whether there are invisible leading and/or 1863 // trailing white-spaces in the `Text`. 1864 1865 // Then, compute visible white-space length before/after the point. 1866 // Note that these lengths may contain invisible white-spaces. 1867 const uint32_t precedingWhiteSpaceLength = [&]() { 1868 if (aPoint.IsStartOfContainer()) { 1869 return 0u; 1870 } 1871 const auto nonWhiteSpaceOffset = 1872 HTMLEditUtils::GetPreviousNonCollapsibleCharOffset( 1873 textNode, aPoint.Offset(), 1874 {HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible}); 1875 const uint32_t firstWhiteSpaceOffset = 1876 nonWhiteSpaceOffset ? *nonWhiteSpaceOffset + 1u : 0u; 1877 return aPoint.Offset() - firstWhiteSpaceOffset; 1878 }(); 1879 const uint32_t followingWhiteSpaceLength = [&]() { 1880 if (aPoint.IsEndOfContainer()) { 1881 return 0u; 1882 } 1883 const auto nonWhiteSpaceOffset = 1884 HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset( 1885 textNode, aPoint.Offset(), 1886 {HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible}); 1887 MOZ_ASSERT(nonWhiteSpaceOffset.valueOr(characterDataBuffer.GetLength()) >= 1888 aPoint.Offset()); 1889 return nonWhiteSpaceOffset.valueOr(characterDataBuffer.GetLength()) - 1890 aPoint.Offset(); 1891 }(); 1892 if (!precedingWhiteSpaceLength && !followingWhiteSpaceLength) { 1893 return ReplaceWhiteSpacesData(); 1894 } 1895 1896 // Now, we can know invisible white-space length in precedingWhiteSpaceLength 1897 // and followingWhiteSpaceLength. 1898 const uint32_t precedingInvisibleWhiteSpaceCount = 1899 HTMLEditUtils::GetInvisibleWhiteSpaceCount( 1900 textNode, aPoint.Offset() - precedingWhiteSpaceLength, 1901 precedingWhiteSpaceLength); 1902 MOZ_ASSERT(precedingWhiteSpaceLength >= precedingInvisibleWhiteSpaceCount); 1903 const uint32_t newPrecedingWhiteSpaceLength = 1904 precedingWhiteSpaceLength - precedingInvisibleWhiteSpaceCount; 1905 const uint32_t followingInvisibleSpaceCount = 1906 HTMLEditUtils::GetInvisibleWhiteSpaceCount(textNode, aPoint.Offset(), 1907 followingWhiteSpaceLength); 1908 MOZ_ASSERT(followingWhiteSpaceLength >= followingInvisibleSpaceCount); 1909 const uint32_t newFollowingWhiteSpaceLength = 1910 followingWhiteSpaceLength - followingInvisibleSpaceCount; 1911 1912 nsAutoString stringToInsertWithSurroundingSpaces; 1913 if (newPrecedingWhiteSpaceLength || newFollowingWhiteSpaceLength) { 1914 stringToInsertWithSurroundingSpaces.SetLength(newPrecedingWhiteSpaceLength + 1915 newFollowingWhiteSpaceLength); 1916 for (auto index : IntegerRange(newPrecedingWhiteSpaceLength + 1917 newFollowingWhiteSpaceLength)) { 1918 stringToInsertWithSurroundingSpaces.SetCharAt(' ', index); 1919 } 1920 } 1921 1922 ReplaceWhiteSpacesData result( 1923 std::move(stringToInsertWithSurroundingSpaces), 1924 aPoint.Offset() - precedingWhiteSpaceLength, // replace start 1925 precedingWhiteSpaceLength + followingWhiteSpaceLength, // replace length 1926 // aPoint.Offset() after replacing the white-spaces 1927 aPoint.Offset() - precedingWhiteSpaceLength + 1928 newPrecedingWhiteSpaceLength); 1929 if (!result.mNormalizedString.IsEmpty()) { 1930 HTMLEditor::NormalizeAllWhiteSpaceSequences( 1931 result.mNormalizedString, 1932 CharPointData::InSameTextNode( 1933 !result.mReplaceStartOffset 1934 ? CharPointType::TextEnd 1935 : (characterDataBuffer.CharAt(result.mReplaceStartOffset - 1936 1u) == HTMLEditUtils::kNewLine 1937 ? CharPointType::PreformattedLineBreak 1938 : CharPointType::VisibleChar)), 1939 CharPointData::InSameTextNode( 1940 result.mReplaceEndOffset >= characterDataBuffer.GetLength() 1941 ? CharPointType::TextEnd 1942 : (characterDataBuffer.CharAt(result.mReplaceEndOffset) == 1943 HTMLEditUtils::kNewLine 1944 ? CharPointType::PreformattedLineBreak 1945 : CharPointType::VisibleChar)), 1946 EditorUtils::IsNewLinePreformatted(textNode) ? Linefeed::Collapsible 1947 : Linefeed::Preformatted); 1948 } 1949 return result; 1950 } 1951 1952 HTMLEditor::ReplaceWhiteSpacesData 1953 HTMLEditor::GetFollowingNormalizedStringToSplitAt( 1954 const EditorDOMPointInText& aPointToSplit) const { 1955 MOZ_ASSERT(aPointToSplit.IsSet()); 1956 1957 if (EditorUtils::IsWhiteSpacePreformatted( 1958 *aPointToSplit.ContainerAs<Text>()) || 1959 aPointToSplit.IsEndOfContainer()) { 1960 return ReplaceWhiteSpacesData(); 1961 } 1962 const bool isNewLineCollapsible = 1963 !EditorUtils::IsNewLinePreformatted(*aPointToSplit.ContainerAs<Text>()); 1964 const auto IsPreformattedLineBreak = [&](char16_t aChar) { 1965 return !isNewLineCollapsible && aChar == HTMLEditUtils::kNewLine; 1966 }; 1967 const auto IsCollapsibleChar = [&](char16_t aChar) { 1968 return !IsPreformattedLineBreak(aChar) && nsCRT::IsAsciiSpace(aChar); 1969 }; 1970 const auto IsCollapsibleCharOrNBSP = [&](char16_t aChar) { 1971 return aChar == HTMLEditUtils::kNBSP || IsCollapsibleChar(aChar); 1972 }; 1973 const char16_t followingChar = aPointToSplit.Char(); 1974 if (!IsCollapsibleCharOrNBSP(followingChar)) { 1975 return ReplaceWhiteSpacesData(); 1976 } 1977 const uint32_t followingWhiteSpaceLength = [&]() { 1978 const auto nonWhiteSpaceOffset = 1979 HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset( 1980 *aPointToSplit.ContainerAs<Text>(), aPointToSplit.Offset(), 1981 {HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible}); 1982 MOZ_ASSERT(nonWhiteSpaceOffset.valueOr( 1983 aPointToSplit.ContainerAs<Text>()->TextDataLength()) >= 1984 aPointToSplit.Offset()); 1985 return nonWhiteSpaceOffset.valueOr( 1986 aPointToSplit.ContainerAs<Text>()->TextDataLength()) - 1987 aPointToSplit.Offset(); 1988 }(); 1989 MOZ_ASSERT(followingWhiteSpaceLength); 1990 if (NS_WARN_IF(!followingWhiteSpaceLength) || 1991 (followingWhiteSpaceLength == 1u && 1992 followingChar == HTMLEditUtils::kNBSP)) { 1993 return ReplaceWhiteSpacesData(); 1994 } 1995 1996 const uint32_t followingInvisibleSpaceCount = 1997 HTMLEditUtils::GetInvisibleWhiteSpaceCount( 1998 *aPointToSplit.ContainerAs<Text>(), aPointToSplit.Offset(), 1999 followingWhiteSpaceLength); 2000 MOZ_ASSERT(followingWhiteSpaceLength >= followingInvisibleSpaceCount); 2001 const uint32_t newFollowingWhiteSpaceLength = 2002 followingWhiteSpaceLength - followingInvisibleSpaceCount; 2003 nsAutoString followingWhiteSpaces; 2004 if (newFollowingWhiteSpaceLength) { 2005 followingWhiteSpaces.SetLength(newFollowingWhiteSpaceLength); 2006 for (const auto offset : IntegerRange(newFollowingWhiteSpaceLength)) { 2007 followingWhiteSpaces.SetCharAt(' ', offset); 2008 } 2009 } 2010 ReplaceWhiteSpacesData result(std::move(followingWhiteSpaces), 2011 aPointToSplit.Offset(), 2012 followingWhiteSpaceLength); 2013 if (!result.mNormalizedString.IsEmpty()) { 2014 const CharacterDataBuffer& characterDataBuffer = 2015 aPointToSplit.ContainerAs<Text>()->DataBuffer(); 2016 HTMLEditor::NormalizeAllWhiteSpaceSequences( 2017 result.mNormalizedString, 2018 CharPointData::InSameTextNode(CharPointType::TextEnd), 2019 CharPointData::InSameTextNode( 2020 result.mReplaceEndOffset >= characterDataBuffer.GetLength() 2021 ? CharPointType::TextEnd 2022 : (characterDataBuffer.CharAt(result.mReplaceEndOffset) == 2023 HTMLEditUtils::kNewLine 2024 ? CharPointType::PreformattedLineBreak 2025 : CharPointType::VisibleChar)), 2026 isNewLineCollapsible ? Linefeed::Collapsible : Linefeed::Preformatted); 2027 } 2028 return result; 2029 } 2030 2031 HTMLEditor::ReplaceWhiteSpacesData 2032 HTMLEditor::GetPrecedingNormalizedStringToSplitAt( 2033 const EditorDOMPointInText& aPointToSplit) const { 2034 MOZ_ASSERT(aPointToSplit.IsSet()); 2035 2036 if (EditorUtils::IsWhiteSpacePreformatted( 2037 *aPointToSplit.ContainerAs<Text>()) || 2038 aPointToSplit.IsStartOfContainer()) { 2039 return ReplaceWhiteSpacesData(); 2040 } 2041 const bool isNewLineCollapsible = 2042 !EditorUtils::IsNewLinePreformatted(*aPointToSplit.ContainerAs<Text>()); 2043 const auto IsPreformattedLineBreak = [&](char16_t aChar) { 2044 return !isNewLineCollapsible && aChar == HTMLEditUtils::kNewLine; 2045 }; 2046 const auto IsCollapsibleChar = [&](char16_t aChar) { 2047 return !IsPreformattedLineBreak(aChar) && nsCRT::IsAsciiSpace(aChar); 2048 }; 2049 const auto IsCollapsibleCharOrNBSP = [&](char16_t aChar) { 2050 return aChar == HTMLEditUtils::kNBSP || IsCollapsibleChar(aChar); 2051 }; 2052 const char16_t precedingChar = aPointToSplit.PreviousChar(); 2053 if (!IsCollapsibleCharOrNBSP(precedingChar)) { 2054 return ReplaceWhiteSpacesData(); 2055 } 2056 const uint32_t precedingWhiteSpaceLength = [&]() { 2057 const auto nonWhiteSpaceOffset = 2058 HTMLEditUtils::GetPreviousNonCollapsibleCharOffset( 2059 *aPointToSplit.ContainerAs<Text>(), aPointToSplit.Offset(), 2060 {HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible}); 2061 const uint32_t firstWhiteSpaceOffset = 2062 nonWhiteSpaceOffset ? *nonWhiteSpaceOffset + 1u : 0u; 2063 return aPointToSplit.Offset() - firstWhiteSpaceOffset; 2064 }(); 2065 MOZ_ASSERT(precedingWhiteSpaceLength); 2066 if (NS_WARN_IF(!precedingWhiteSpaceLength) || 2067 (precedingWhiteSpaceLength == 1u && 2068 precedingChar == HTMLEditUtils::kNBSP)) { 2069 return ReplaceWhiteSpacesData(); 2070 } 2071 2072 const uint32_t precedingInvisibleWhiteSpaceCount = 2073 HTMLEditUtils::GetInvisibleWhiteSpaceCount( 2074 *aPointToSplit.ContainerAs<Text>(), 2075 aPointToSplit.Offset() - precedingWhiteSpaceLength, 2076 precedingWhiteSpaceLength); 2077 MOZ_ASSERT(precedingWhiteSpaceLength >= precedingInvisibleWhiteSpaceCount); 2078 const uint32_t newPrecedingWhiteSpaceLength = 2079 precedingWhiteSpaceLength - precedingInvisibleWhiteSpaceCount; 2080 nsAutoString precedingWhiteSpaces; 2081 if (newPrecedingWhiteSpaceLength) { 2082 precedingWhiteSpaces.SetLength(newPrecedingWhiteSpaceLength); 2083 for (const auto offset : IntegerRange(newPrecedingWhiteSpaceLength)) { 2084 precedingWhiteSpaces.SetCharAt(' ', offset); 2085 } 2086 } 2087 ReplaceWhiteSpacesData result( 2088 std::move(precedingWhiteSpaces), 2089 aPointToSplit.Offset() - precedingWhiteSpaceLength, 2090 precedingWhiteSpaceLength); 2091 if (!result.mNormalizedString.IsEmpty()) { 2092 const CharacterDataBuffer& characterDataBuffer = 2093 aPointToSplit.ContainerAs<Text>()->DataBuffer(); 2094 HTMLEditor::NormalizeAllWhiteSpaceSequences( 2095 result.mNormalizedString, 2096 CharPointData::InSameTextNode( 2097 !result.mReplaceStartOffset 2098 ? CharPointType::TextEnd 2099 : (characterDataBuffer.CharAt(result.mReplaceStartOffset - 2100 1u) == HTMLEditUtils::kNewLine 2101 ? CharPointType::PreformattedLineBreak 2102 : CharPointType::VisibleChar)), 2103 CharPointData::InSameTextNode(CharPointType::TextEnd), 2104 isNewLineCollapsible ? Linefeed::Collapsible : Linefeed::Preformatted); 2105 } 2106 return result; 2107 } 2108 2109 HTMLEditor::ReplaceWhiteSpacesData 2110 HTMLEditor::GetSurroundingNormalizedStringToDelete(const Text& aTextNode, 2111 uint32_t aOffset, 2112 uint32_t aLength) const { 2113 MOZ_ASSERT(aOffset <= aTextNode.TextDataLength()); 2114 MOZ_ASSERT(aOffset + aLength <= aTextNode.TextDataLength()); 2115 2116 if (EditorUtils::IsWhiteSpacePreformatted(aTextNode) || !aLength || 2117 (!aOffset && aLength >= aTextNode.TextDataLength())) { 2118 return ReplaceWhiteSpacesData(); 2119 } 2120 const bool isNewLineCollapsible = 2121 !EditorUtils::IsNewLinePreformatted(aTextNode); 2122 const auto IsPreformattedLineBreak = [&](char16_t aChar) { 2123 return !isNewLineCollapsible && aChar == HTMLEditUtils::kNewLine; 2124 }; 2125 const auto IsCollapsibleChar = [&](char16_t aChar) { 2126 return !IsPreformattedLineBreak(aChar) && nsCRT::IsAsciiSpace(aChar); 2127 }; 2128 const auto IsCollapsibleCharOrNBSP = [&](char16_t aChar) { 2129 return aChar == HTMLEditUtils::kNBSP || IsCollapsibleChar(aChar); 2130 }; 2131 const CharacterDataBuffer& characterDataBuffer = aTextNode.DataBuffer(); 2132 const char16_t precedingChar = aOffset 2133 ? characterDataBuffer.CharAt(aOffset - 1u) 2134 : static_cast<char16_t>(0); 2135 const char16_t followingChar = 2136 aOffset + aLength < characterDataBuffer.GetLength() 2137 ? characterDataBuffer.CharAt(aOffset + aLength) 2138 : static_cast<char16_t>(0); 2139 // If there is no surrounding white-spaces, we need to do nothing here. 2140 if (!IsCollapsibleCharOrNBSP(precedingChar) && 2141 !IsCollapsibleCharOrNBSP(followingChar)) { 2142 return ReplaceWhiteSpacesData(); 2143 } 2144 const uint32_t precedingWhiteSpaceLength = [&]() { 2145 if (!IsCollapsibleCharOrNBSP(precedingChar)) { 2146 return 0u; 2147 } 2148 const auto nonWhiteSpaceOffset = 2149 HTMLEditUtils::GetPreviousNonCollapsibleCharOffset( 2150 aTextNode, aOffset, 2151 {HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible}); 2152 const uint32_t firstWhiteSpaceOffset = 2153 nonWhiteSpaceOffset ? *nonWhiteSpaceOffset + 1u : 0u; 2154 return aOffset - firstWhiteSpaceOffset; 2155 }(); 2156 const uint32_t followingWhiteSpaceLength = [&]() { 2157 if (!IsCollapsibleCharOrNBSP(followingChar)) { 2158 return 0u; 2159 } 2160 const auto nonWhiteSpaceOffset = 2161 HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset( 2162 aTextNode, aOffset + aLength, 2163 {HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible}); 2164 MOZ_ASSERT(nonWhiteSpaceOffset.valueOr(characterDataBuffer.GetLength()) >= 2165 aOffset + aLength); 2166 return nonWhiteSpaceOffset.valueOr(characterDataBuffer.GetLength()) - 2167 (aOffset + aLength); 2168 }(); 2169 if (NS_WARN_IF(!precedingWhiteSpaceLength && !followingWhiteSpaceLength)) { 2170 return ReplaceWhiteSpacesData(); 2171 } 2172 const uint32_t precedingInvisibleWhiteSpaceCount = 2173 HTMLEditUtils::GetInvisibleWhiteSpaceCount( 2174 aTextNode, aOffset - precedingWhiteSpaceLength, 2175 precedingWhiteSpaceLength); 2176 MOZ_ASSERT(precedingWhiteSpaceLength >= precedingInvisibleWhiteSpaceCount); 2177 const uint32_t followingInvisibleSpaceCount = 2178 HTMLEditUtils::GetInvisibleWhiteSpaceCount(aTextNode, aOffset + aLength, 2179 followingWhiteSpaceLength); 2180 MOZ_ASSERT(followingWhiteSpaceLength >= followingInvisibleSpaceCount); 2181 2182 // Let's try to return early if there is only one white-space around the 2183 // deleting range to avoid to run the expensive path. 2184 if (precedingWhiteSpaceLength == 1u && !precedingInvisibleWhiteSpaceCount && 2185 !followingWhiteSpaceLength) { 2186 // If there is only one ASCII space and it'll be followed by a 2187 // non-collapsible character except preformatted linebreak after deletion, 2188 // we don't need to normalize the preceding white-space. 2189 if (precedingChar == HTMLEditUtils::kSpace && followingChar && 2190 !IsPreformattedLineBreak(followingChar)) { 2191 return ReplaceWhiteSpacesData(); 2192 } 2193 // If there is only one NBSP and it'll be the last character or will be 2194 // followed by a collapsible white-space, we don't need to normalize the 2195 // preceding white-space. 2196 if (precedingChar == HTMLEditUtils::kNBSP && 2197 (!followingChar || IsPreformattedLineBreak(followingChar))) { 2198 return ReplaceWhiteSpacesData(); 2199 } 2200 } 2201 if (followingWhiteSpaceLength == 1u && !followingInvisibleSpaceCount && 2202 !precedingWhiteSpaceLength) { 2203 // If there is only one ASCII space and it'll follow by a non-collapsible 2204 // character after deletion, we don't need to normalize the following 2205 // white-space. 2206 if (followingChar == HTMLEditUtils::kSpace && precedingChar && 2207 !IsPreformattedLineBreak(precedingChar)) { 2208 return ReplaceWhiteSpacesData(); 2209 } 2210 // If there is only one NBSP and it'll be the first character or will 2211 // follow a preformatted line break, we don't need to normalize the 2212 // following white-space. 2213 if (followingChar == HTMLEditUtils::kNBSP && 2214 (!precedingChar || IsPreformattedLineBreak(precedingChar))) { 2215 return ReplaceWhiteSpacesData(); 2216 } 2217 } 2218 2219 const uint32_t newPrecedingWhiteSpaceLength = 2220 precedingWhiteSpaceLength - precedingInvisibleWhiteSpaceCount; 2221 const uint32_t newFollowingWhiteSpaceLength = 2222 followingWhiteSpaceLength - followingInvisibleSpaceCount; 2223 nsAutoString surroundingWhiteSpaces; 2224 if (newPrecedingWhiteSpaceLength || newFollowingWhiteSpaceLength) { 2225 surroundingWhiteSpaces.SetLength(newPrecedingWhiteSpaceLength + 2226 newFollowingWhiteSpaceLength); 2227 for (const auto offset : IntegerRange(newPrecedingWhiteSpaceLength + 2228 newFollowingWhiteSpaceLength)) { 2229 surroundingWhiteSpaces.SetCharAt(' ', offset); 2230 } 2231 } 2232 ReplaceWhiteSpacesData result( 2233 std::move(surroundingWhiteSpaces), aOffset - precedingWhiteSpaceLength, 2234 precedingWhiteSpaceLength + aLength + followingWhiteSpaceLength, 2235 aOffset - precedingInvisibleWhiteSpaceCount); 2236 if (!result.mNormalizedString.IsEmpty()) { 2237 HTMLEditor::NormalizeAllWhiteSpaceSequences( 2238 result.mNormalizedString, 2239 CharPointData::InSameTextNode( 2240 !result.mReplaceStartOffset 2241 ? CharPointType::TextEnd 2242 : (characterDataBuffer.CharAt(result.mReplaceStartOffset - 2243 1u) == HTMLEditUtils::kNewLine 2244 ? CharPointType::PreformattedLineBreak 2245 : CharPointType::VisibleChar)), 2246 CharPointData::InSameTextNode( 2247 result.mReplaceEndOffset >= characterDataBuffer.GetLength() 2248 ? CharPointType::TextEnd 2249 : (characterDataBuffer.CharAt(result.mReplaceEndOffset) == 2250 HTMLEditUtils::kNewLine 2251 ? CharPointType::PreformattedLineBreak 2252 : CharPointType::VisibleChar)), 2253 isNewLineCollapsible ? Linefeed::Collapsible : Linefeed::Preformatted); 2254 } 2255 return result; 2256 } 2257 2258 void HTMLEditor::ExtendRangeToDeleteWithNormalizingWhiteSpaces( 2259 EditorDOMPointInText& aStartToDelete, EditorDOMPointInText& aEndToDelete, 2260 nsString& aNormalizedWhiteSpacesInStartNode, 2261 nsString& aNormalizedWhiteSpacesInEndNode) const { 2262 MOZ_ASSERT(aStartToDelete.IsSetAndValid()); 2263 MOZ_ASSERT(aEndToDelete.IsSetAndValid()); 2264 MOZ_ASSERT(aStartToDelete.EqualsOrIsBefore(aEndToDelete)); 2265 MOZ_ASSERT(aNormalizedWhiteSpacesInStartNode.IsEmpty()); 2266 MOZ_ASSERT(aNormalizedWhiteSpacesInEndNode.IsEmpty()); 2267 2268 // First, check whether there is surrounding white-spaces or not, and if there 2269 // are, check whether they are collapsible or not. Note that we shouldn't 2270 // touch white-spaces in different text nodes for performance, but we need 2271 // adjacent text node's first or last character information in some cases. 2272 const auto precedingCharPoint = 2273 WSRunScanner::GetPreviousCharPoint<EditorDOMPointInText>( 2274 {WSRunScanner::Option::OnlyEditableNodes}, aStartToDelete); 2275 const auto followingCharPoint = 2276 WSRunScanner::GetInclusiveNextCharPoint<EditorDOMPointInText>( 2277 {WSRunScanner::Option::OnlyEditableNodes}, aEndToDelete); 2278 // Blink-compat: Normalize white-spaces in first node only when not removing 2279 // its last character or no text nodes follow the first node. 2280 // If removing last character of first node and there are 2281 // following text nodes, white-spaces in following text node are 2282 // normalized instead. 2283 const bool removingLastCharOfStartNode = 2284 aStartToDelete.ContainerAs<Text>() != aEndToDelete.ContainerAs<Text>() || 2285 (aEndToDelete.IsEndOfContainer() && followingCharPoint.IsSet()); 2286 const bool maybeNormalizePrecedingWhiteSpaces = 2287 !removingLastCharOfStartNode && precedingCharPoint.IsSet() && 2288 !precedingCharPoint.IsEndOfContainer() && 2289 precedingCharPoint.ContainerAs<Text>() == 2290 aStartToDelete.ContainerAs<Text>() && 2291 precedingCharPoint.IsCharCollapsibleASCIISpaceOrNBSP(); 2292 const bool maybeNormalizeFollowingWhiteSpaces = 2293 followingCharPoint.IsSet() && !followingCharPoint.IsEndOfContainer() && 2294 (followingCharPoint.ContainerAs<Text>() == 2295 aEndToDelete.ContainerAs<Text>() || 2296 removingLastCharOfStartNode) && 2297 followingCharPoint.IsCharCollapsibleASCIISpaceOrNBSP(); 2298 2299 if (!maybeNormalizePrecedingWhiteSpaces && 2300 !maybeNormalizeFollowingWhiteSpaces) { 2301 return; // There are no white-spaces. 2302 } 2303 2304 // Next, consider the range to normalize. 2305 EditorDOMPointInText startToNormalize, endToNormalize; 2306 if (maybeNormalizePrecedingWhiteSpaces) { 2307 Maybe<uint32_t> previousCharOffsetOfWhiteSpaces = 2308 HTMLEditUtils::GetPreviousNonCollapsibleCharOffset( 2309 precedingCharPoint, {WalkTextOption::TreatNBSPsCollapsible}); 2310 startToNormalize.Set(precedingCharPoint.ContainerAs<Text>(), 2311 previousCharOffsetOfWhiteSpaces.isSome() 2312 ? previousCharOffsetOfWhiteSpaces.value() + 1 2313 : 0); 2314 MOZ_ASSERT(!startToNormalize.IsEndOfContainer()); 2315 } 2316 if (maybeNormalizeFollowingWhiteSpaces) { 2317 Maybe<uint32_t> nextCharOffsetOfWhiteSpaces = 2318 HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset( 2319 followingCharPoint, {WalkTextOption::TreatNBSPsCollapsible}); 2320 if (nextCharOffsetOfWhiteSpaces.isSome()) { 2321 endToNormalize.Set(followingCharPoint.ContainerAs<Text>(), 2322 nextCharOffsetOfWhiteSpaces.value()); 2323 } else { 2324 endToNormalize.SetToEndOf(followingCharPoint.ContainerAs<Text>()); 2325 } 2326 MOZ_ASSERT(!endToNormalize.IsStartOfContainer()); 2327 } 2328 2329 // Next, retrieve surrounding information of white-space sequence. 2330 // If we're removing first text node's last character, we need to 2331 // normalize white-spaces starts from another text node. In this case, 2332 // we need to lie for avoiding assertion in GenerateWhiteSpaceSequence(). 2333 CharPointData previousCharPointData = 2334 removingLastCharOfStartNode 2335 ? CharPointData::InDifferentTextNode(CharPointType::TextEnd) 2336 : GetPreviousCharPointDataForNormalizingWhiteSpaces( 2337 startToNormalize.IsSet() ? startToNormalize : aStartToDelete); 2338 CharPointData nextCharPointData = 2339 GetInclusiveNextCharPointDataForNormalizingWhiteSpaces( 2340 endToNormalize.IsSet() ? endToNormalize : aEndToDelete); 2341 2342 // Next, compute number of white-spaces in start/end node. 2343 uint32_t lengthInStartNode = 0, lengthInEndNode = 0; 2344 if (startToNormalize.IsSet()) { 2345 MOZ_ASSERT(startToNormalize.ContainerAs<Text>() == 2346 aStartToDelete.ContainerAs<Text>()); 2347 lengthInStartNode = aStartToDelete.Offset() - startToNormalize.Offset(); 2348 MOZ_ASSERT(lengthInStartNode); 2349 } 2350 if (endToNormalize.IsSet()) { 2351 lengthInEndNode = 2352 endToNormalize.ContainerAs<Text>() == aEndToDelete.ContainerAs<Text>() 2353 ? endToNormalize.Offset() - aEndToDelete.Offset() 2354 : endToNormalize.Offset(); 2355 MOZ_ASSERT(lengthInEndNode); 2356 // If we normalize white-spaces in a text node, we can replace all of them 2357 // with one ReplaceTextTransaction. 2358 if (endToNormalize.ContainerAs<Text>() == 2359 aStartToDelete.ContainerAs<Text>()) { 2360 lengthInStartNode += lengthInEndNode; 2361 lengthInEndNode = 0; 2362 } 2363 } 2364 2365 MOZ_ASSERT(lengthInStartNode + lengthInEndNode); 2366 2367 // Next, generate normalized white-spaces. 2368 if (!lengthInEndNode) { 2369 HTMLEditor::GenerateWhiteSpaceSequence( 2370 aNormalizedWhiteSpacesInStartNode, lengthInStartNode, 2371 previousCharPointData, nextCharPointData); 2372 } else if (!lengthInStartNode) { 2373 HTMLEditor::GenerateWhiteSpaceSequence( 2374 aNormalizedWhiteSpacesInEndNode, lengthInEndNode, previousCharPointData, 2375 nextCharPointData); 2376 } else { 2377 // For making `GenerateWhiteSpaceSequence()` simpler, we should create 2378 // whole white-space sequence first, then, copy to the out params. 2379 nsAutoString whiteSpaces; 2380 HTMLEditor::GenerateWhiteSpaceSequence( 2381 whiteSpaces, lengthInStartNode + lengthInEndNode, previousCharPointData, 2382 nextCharPointData); 2383 aNormalizedWhiteSpacesInStartNode = 2384 Substring(whiteSpaces, 0, lengthInStartNode); 2385 aNormalizedWhiteSpacesInEndNode = Substring(whiteSpaces, lengthInStartNode); 2386 MOZ_ASSERT(aNormalizedWhiteSpacesInEndNode.Length() == lengthInEndNode); 2387 } 2388 2389 // TODO: Shrink the replacing range and string as far as possible because 2390 // this may run a lot, i.e., HTMLEditor creates ReplaceTextTransaction 2391 // a lot for normalizing white-spaces. Then, each transaction shouldn't 2392 // have all white-spaces every time because once it's normalized, we 2393 // don't need to normalize all of the sequence again, but currently 2394 // we do. 2395 2396 // Finally, extend the range. 2397 if (startToNormalize.IsSet()) { 2398 aStartToDelete = startToNormalize; 2399 } 2400 if (endToNormalize.IsSet()) { 2401 aEndToDelete = endToNormalize; 2402 } 2403 } 2404 2405 Result<CaretPoint, nsresult> 2406 HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces( 2407 const EditorDOMPointInText& aStartToDelete, 2408 const EditorDOMPointInText& aEndToDelete, 2409 TreatEmptyTextNodes aTreatEmptyTextNodes, DeleteDirection aDeleteDirection, 2410 const Element& aEditingHost) { 2411 MOZ_ASSERT(aStartToDelete.IsSetAndValid()); 2412 MOZ_ASSERT(aEndToDelete.IsSetAndValid()); 2413 MOZ_ASSERT(aStartToDelete.EqualsOrIsBefore(aEndToDelete)); 2414 2415 // Use nsString for these replacing string because we should avoid to copy 2416 // the buffer from auto storange to ReplaceTextTransaction. 2417 nsString normalizedWhiteSpacesInFirstNode, normalizedWhiteSpacesInLastNode; 2418 2419 // First, check whether we need to normalize white-spaces after deleting 2420 // the given range. 2421 EditorDOMPointInText startToDelete(aStartToDelete); 2422 EditorDOMPointInText endToDelete(aEndToDelete); 2423 ExtendRangeToDeleteWithNormalizingWhiteSpaces( 2424 startToDelete, endToDelete, normalizedWhiteSpacesInFirstNode, 2425 normalizedWhiteSpacesInLastNode); 2426 2427 // If extended range is still collapsed, i.e., the caller just wants to 2428 // normalize white-space sequence, but there is no white-spaces which need to 2429 // be replaced, we need to do nothing here. 2430 if (startToDelete == endToDelete) { 2431 return CaretPoint(aStartToDelete.To<EditorDOMPoint>()); 2432 } 2433 2434 // Note that the container text node of startToDelete may be removed from 2435 // the tree if it becomes empty. Therefore, we need to track the point. 2436 EditorDOMPoint newCaretPosition; 2437 if (aStartToDelete.ContainerAs<Text>() == aEndToDelete.ContainerAs<Text>()) { 2438 newCaretPosition = aEndToDelete.To<EditorDOMPoint>(); 2439 } else if (aDeleteDirection == DeleteDirection::Forward) { 2440 newCaretPosition.SetToEndOf(aStartToDelete.ContainerAs<Text>()); 2441 } else { 2442 newCaretPosition.Set(aEndToDelete.ContainerAs<Text>(), 0u); 2443 } 2444 2445 // Then, modify the text nodes in the range. 2446 while (true) { 2447 AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(), 2448 &newCaretPosition); 2449 // Use ReplaceTextTransaction if we need to normalize white-spaces in 2450 // the first text node. 2451 if (!normalizedWhiteSpacesInFirstNode.IsEmpty()) { 2452 EditorDOMPoint trackingEndToDelete(endToDelete.ContainerAs<Text>(), 2453 endToDelete.Offset()); 2454 { 2455 AutoTrackDOMPoint trackEndToDelete(RangeUpdaterRef(), 2456 &trackingEndToDelete); 2457 uint32_t lengthToReplaceInFirstTextNode = 2458 startToDelete.ContainerAs<Text>() == 2459 trackingEndToDelete.ContainerAs<Text>() 2460 ? trackingEndToDelete.Offset() - startToDelete.Offset() 2461 : startToDelete.ContainerAs<Text>()->TextLength() - 2462 startToDelete.Offset(); 2463 Result<InsertTextResult, nsresult> replaceTextResult = 2464 ReplaceTextWithTransaction( 2465 MOZ_KnownLive(*startToDelete.ContainerAs<Text>()), 2466 startToDelete.Offset(), lengthToReplaceInFirstTextNode, 2467 normalizedWhiteSpacesInFirstNode); 2468 if (MOZ_UNLIKELY(replaceTextResult.isErr())) { 2469 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed"); 2470 return replaceTextResult.propagateErr(); 2471 } 2472 // We'll return computed caret point, newCaretPosition, below. 2473 replaceTextResult.unwrap().IgnoreCaretPointSuggestion(); 2474 if (startToDelete.ContainerAs<Text>() == 2475 trackingEndToDelete.ContainerAs<Text>()) { 2476 MOZ_ASSERT(normalizedWhiteSpacesInLastNode.IsEmpty()); 2477 break; // There is no more text which we need to delete. 2478 } 2479 } 2480 MOZ_ASSERT(trackingEndToDelete.IsInTextNode()); 2481 endToDelete.Set(trackingEndToDelete.ContainerAs<Text>(), 2482 trackingEndToDelete.Offset()); 2483 // If the remaining range was modified by mutation event listener, 2484 // we should stop handling the deletion. 2485 startToDelete = 2486 EditorDOMPointInText::AtEndOf(*startToDelete.ContainerAs<Text>()); 2487 } 2488 // Delete ASCII whiteSpaces in the range simpley if there are some text 2489 // nodes which we don't need to replace their text. 2490 if (normalizedWhiteSpacesInLastNode.IsEmpty() || 2491 startToDelete.ContainerAs<Text>() != endToDelete.ContainerAs<Text>()) { 2492 // If we need to replace text in the last text node, we should 2493 // delete text before its previous text node. 2494 EditorDOMPointInText endToDeleteExceptReplaceRange = 2495 normalizedWhiteSpacesInLastNode.IsEmpty() 2496 ? endToDelete 2497 : EditorDOMPointInText(endToDelete.ContainerAs<Text>(), 0); 2498 if (startToDelete != endToDeleteExceptReplaceRange) { 2499 Result<CaretPoint, nsresult> caretPointOrError = 2500 DeleteTextAndTextNodesWithTransaction(startToDelete, 2501 endToDeleteExceptReplaceRange, 2502 aTreatEmptyTextNodes); 2503 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 2504 NS_WARNING( 2505 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed"); 2506 return caretPointOrError.propagateErr(); 2507 } 2508 nsresult rv = caretPointOrError.unwrap().SuggestCaretPointTo( 2509 *this, {SuggestCaret::OnlyIfHasSuggestion, 2510 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 2511 SuggestCaret::AndIgnoreTrivialError}); 2512 if (NS_FAILED(rv)) { 2513 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 2514 return Err(rv); 2515 } 2516 NS_WARNING_ASSERTION( 2517 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 2518 "CaretPoint::SuggestCaretPointTo() failed, but ignored"); 2519 if (normalizedWhiteSpacesInLastNode.IsEmpty()) { 2520 break; // There is no more text which we need to delete. 2521 } 2522 if (MaybeNodeRemovalsObservedByDevTools() && 2523 (NS_WARN_IF(!endToDeleteExceptReplaceRange.IsSetAndValid()) || 2524 NS_WARN_IF(!endToDelete.IsSetAndValid()) || 2525 NS_WARN_IF(endToDelete.IsStartOfContainer()))) { 2526 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2527 } 2528 // Then, replace the text in the last text node. 2529 startToDelete = endToDeleteExceptReplaceRange; 2530 } 2531 } 2532 2533 // Replace ASCII whiteSpaces in the range and following character in the 2534 // last text node. 2535 MOZ_ASSERT(!normalizedWhiteSpacesInLastNode.IsEmpty()); 2536 MOZ_ASSERT(startToDelete.ContainerAs<Text>() == 2537 endToDelete.ContainerAs<Text>()); 2538 Result<InsertTextResult, nsresult> replaceTextResult = 2539 ReplaceTextWithTransaction( 2540 MOZ_KnownLive(*startToDelete.ContainerAs<Text>()), 2541 startToDelete.Offset(), 2542 endToDelete.Offset() - startToDelete.Offset(), 2543 normalizedWhiteSpacesInLastNode); 2544 if (MOZ_UNLIKELY(replaceTextResult.isErr())) { 2545 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed"); 2546 return replaceTextResult.propagateErr(); 2547 } 2548 // We'll return computed caret point, newCaretPosition, below. 2549 replaceTextResult.unwrap().IgnoreCaretPointSuggestion(); 2550 break; 2551 } 2552 2553 if (NS_WARN_IF(!newCaretPosition.IsSetAndValid()) || 2554 NS_WARN_IF(!newCaretPosition.GetContainer()->IsInComposedDoc())) { 2555 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2556 } 2557 2558 // Look for leaf node to put caret if we remove some empty inline ancestors 2559 // at new caret position. 2560 if (!newCaretPosition.IsInTextNode()) { 2561 if (const Element* editableBlockElementOrInlineEditingHost = 2562 HTMLEditUtils::GetInclusiveAncestorElement( 2563 *newCaretPosition.ContainerAs<nsIContent>(), 2564 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost, 2565 BlockInlineCheck::UseComputedDisplayStyle)) { 2566 // Try to put caret next to immediately after previous editable leaf. 2567 nsIContent* previousContent = 2568 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement( 2569 newCaretPosition, {LeafNodeType::LeafNodeOrNonEditableNode}, 2570 BlockInlineCheck::UseComputedDisplayStyle, 2571 editableBlockElementOrInlineEditingHost); 2572 if (previousContent && 2573 HTMLEditUtils::IsSimplyEditableNode(*previousContent) && 2574 !HTMLEditUtils::IsBlockElement( 2575 *previousContent, BlockInlineCheck::UseComputedDisplayStyle)) { 2576 newCaretPosition = 2577 previousContent->IsText() || 2578 HTMLEditUtils::IsContainerNode(*previousContent) 2579 ? EditorDOMPoint::AtEndOf(*previousContent) 2580 : EditorDOMPoint::After(*previousContent); 2581 } 2582 // But if the point is very first of a block element or immediately after 2583 // a child block, look for next editable leaf instead. 2584 else if (nsIContent* nextContent = 2585 HTMLEditUtils::GetNextLeafContentOrNextBlockElement( 2586 newCaretPosition, 2587 {LeafNodeType::LeafNodeOrNonEditableNode}, 2588 BlockInlineCheck::UseComputedDisplayStyle, 2589 editableBlockElementOrInlineEditingHost)) { 2590 if (HTMLEditUtils::IsSimplyEditableNode(*nextContent) && 2591 !HTMLEditUtils::IsBlockElement( 2592 *nextContent, BlockInlineCheck::UseComputedDisplayStyle)) { 2593 newCaretPosition = 2594 nextContent->IsText() || 2595 HTMLEditUtils::IsContainerNode(*nextContent) 2596 ? EditorDOMPoint(nextContent, 0) 2597 : EditorDOMPoint(nextContent); 2598 } 2599 } 2600 } 2601 } 2602 2603 // For compatibility with Blink, we should move caret to end of previous 2604 // text node if it's direct previous sibling of the first text node in the 2605 // range. 2606 if (newCaretPosition.IsStartOfContainer() && 2607 newCaretPosition.IsInTextNode() && 2608 newCaretPosition.GetContainer()->GetPreviousSibling() && 2609 newCaretPosition.GetContainer()->GetPreviousSibling()->IsEditable() && 2610 newCaretPosition.GetContainer()->GetPreviousSibling()->IsText()) { 2611 newCaretPosition.SetToEndOf( 2612 newCaretPosition.GetContainer()->GetPreviousSibling()->AsText()); 2613 } 2614 MOZ_ASSERT(HTMLEditUtils::IsSimplyEditableNode( 2615 *newCaretPosition.ContainerAs<nsIContent>())); 2616 2617 { 2618 AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(), 2619 &newCaretPosition); 2620 nsresult rv = EnsureNoFollowingUnnecessaryLineBreak(newCaretPosition); 2621 if (NS_FAILED(rv)) { 2622 NS_WARNING("HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed"); 2623 return Err(rv); 2624 } 2625 if (NS_WARN_IF(!newCaretPosition.IsSet())) { 2626 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2627 } 2628 } 2629 2630 if (GetTopLevelEditSubAction() == EditSubAction::eDeleteSelectedContent) { 2631 AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(), 2632 &newCaretPosition); 2633 Result<CreateLineBreakResult, nsresult> insertPaddingBRElementOrError = 2634 InsertPaddingBRElementIfNeeded( 2635 newCaretPosition, 2636 aEditingHost.IsContentEditablePlainTextOnly() ? nsIEditor::eNoStrip 2637 : nsIEditor::eStrip, 2638 aEditingHost); 2639 if (MOZ_UNLIKELY(insertPaddingBRElementOrError.isErr())) { 2640 NS_WARNING("HTMLEditor::InsertPaddingBRElementIfNeeded() failed"); 2641 return insertPaddingBRElementOrError.propagateErr(); 2642 } 2643 trackingNewCaretPosition.FlushAndStopTracking(); 2644 if (!newCaretPosition.IsInTextNode()) { 2645 insertPaddingBRElementOrError.unwrap().MoveCaretPointTo( 2646 newCaretPosition, {SuggestCaret::OnlyIfHasSuggestion}); 2647 } else { 2648 insertPaddingBRElementOrError.unwrap().IgnoreCaretPointSuggestion(); 2649 } 2650 if (!newCaretPosition.IsSetAndValid()) { 2651 NS_WARNING("Inserting <br> element caused unexpected DOM tree"); 2652 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2653 } 2654 } 2655 return CaretPoint(std::move(newCaretPosition)); 2656 } 2657 2658 Result<JoinNodesResult, nsresult> 2659 HTMLEditor::JoinTextNodesWithNormalizeWhiteSpaces(Text& aLeftText, 2660 Text& aRightText) { 2661 if (EditorUtils::IsWhiteSpacePreformatted(aLeftText)) { 2662 Result<JoinNodesResult, nsresult> joinResultOrError = 2663 JoinNodesWithTransaction(aLeftText, aRightText); 2664 NS_WARNING_ASSERTION(joinResultOrError.isOk(), 2665 "HTMLEditor::JoinNodesWithTransaction() failed"); 2666 return joinResultOrError; 2667 } 2668 const bool isNewLinePreformatted = 2669 EditorUtils::IsNewLinePreformatted(aLeftText); 2670 const auto IsCollapsibleChar = [&](char16_t aChar) { 2671 return (aChar == HTMLEditUtils::kNewLine && !isNewLinePreformatted) || 2672 nsCRT::IsAsciiSpace(aChar); 2673 }; 2674 const auto IsCollapsibleCharOrNBSP = [&](char16_t aChar) { 2675 return aChar == HTMLEditUtils::kNBSP || IsCollapsibleChar(aChar); 2676 }; 2677 const char16_t lastLeftChar = aLeftText.DataBuffer().SafeLastChar(); 2678 char16_t firstRightChar = aRightText.DataBuffer().SafeFirstChar(); 2679 const char16_t secondRightChar = aRightText.DataBuffer().GetLength() >= 2 2680 ? aRightText.DataBuffer().CharAt(1u) 2681 : static_cast<char16_t>(0); 2682 if (IsCollapsibleCharOrNBSP(firstRightChar)) { 2683 // If the right Text starts only with a collapsible white-space and it'll 2684 // follow a non-collapsible char, we should make it an ASCII white-space. 2685 if (secondRightChar && !IsCollapsibleCharOrNBSP(secondRightChar) && 2686 lastLeftChar && !IsCollapsibleChar(lastLeftChar)) { 2687 if (firstRightChar != HTMLEditUtils::kSpace) { 2688 Result<InsertTextResult, nsresult> replaceWhiteSpaceResultOrError = 2689 ReplaceTextWithTransaction(aRightText, 0u, 1u, u" "_ns); 2690 if (MOZ_UNLIKELY(replaceWhiteSpaceResultOrError.isErr())) { 2691 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed"); 2692 return replaceWhiteSpaceResultOrError.propagateErr(); 2693 } 2694 replaceWhiteSpaceResultOrError.unwrap().IgnoreCaretPointSuggestion(); 2695 if (NS_WARN_IF(aLeftText.GetNextSibling() != &aRightText)) { 2696 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2697 } 2698 firstRightChar = HTMLEditUtils::kSpace; 2699 } 2700 } 2701 // Otherwise, normalize the white-spaces before join, i.e., it will start 2702 // with an NBSP. 2703 else { 2704 Result<EditorDOMPoint, nsresult> atFirstVisibleThingOrError = 2705 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter( 2706 *this, EditorDOMPoint(&aRightText, 0u), {}); 2707 if (MOZ_UNLIKELY(atFirstVisibleThingOrError.isErr())) { 2708 NS_WARNING( 2709 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesAfter() failed"); 2710 return atFirstVisibleThingOrError.propagateErr(); 2711 } 2712 if (!aRightText.GetParentNode()) { 2713 return JoinNodesResult(EditorDOMPoint::AtEndOf(aLeftText), aRightText); 2714 } 2715 } 2716 } else if (IsCollapsibleCharOrNBSP(lastLeftChar) && 2717 lastLeftChar != HTMLEditUtils::kSpace && 2718 aLeftText.DataBuffer().GetLength() >= 2u) { 2719 // If the last char of the left `Text` is a single white-space but not an 2720 // ASCII space, let's replace it with an ASCII space. 2721 const char16_t secondLastChar = 2722 aLeftText.DataBuffer().CharAt(aLeftText.DataBuffer().GetLength() - 2u); 2723 if (!IsCollapsibleCharOrNBSP(secondLastChar) && 2724 !IsCollapsibleCharOrNBSP(firstRightChar)) { 2725 Result<InsertTextResult, nsresult> replaceWhiteSpaceResultOrError = 2726 ReplaceTextWithTransaction( 2727 aLeftText, aLeftText.DataBuffer().GetLength() - 1u, 1u, u" "_ns); 2728 if (MOZ_UNLIKELY(replaceWhiteSpaceResultOrError.isErr())) { 2729 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed"); 2730 return replaceWhiteSpaceResultOrError.propagateErr(); 2731 } 2732 replaceWhiteSpaceResultOrError.unwrap().IgnoreCaretPointSuggestion(); 2733 if (NS_WARN_IF(aLeftText.GetNextSibling() != &aRightText)) { 2734 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2735 } 2736 } 2737 } 2738 Result<JoinNodesResult, nsresult> joinResultOrError = 2739 JoinNodesWithTransaction(aLeftText, aRightText); 2740 if (MOZ_UNLIKELY(joinResultOrError.isErr())) { 2741 NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed"); 2742 return joinResultOrError; 2743 } 2744 JoinNodesResult joinResult = joinResultOrError.unwrap(); 2745 const EditorDOMPointInText startOfRightTextData = 2746 joinResult.AtJoinedPoint<EditorRawDOMPoint>().GetAsInText(); 2747 if (NS_WARN_IF(!startOfRightTextData.IsSet()) || 2748 (firstRightChar && 2749 (NS_WARN_IF(startOfRightTextData.IsEndOfContainer()) || 2750 NS_WARN_IF(firstRightChar != startOfRightTextData.Char())))) { 2751 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2752 } 2753 return std::move(joinResult); 2754 } 2755 2756 // static 2757 bool HTMLEditor::CanInsertLineBreak(LineBreakType aLineBreakType, 2758 const nsIContent& aContent) { 2759 if (MOZ_UNLIKELY(!HTMLEditUtils::IsSimplyEditableNode(aContent))) { 2760 return false; 2761 } 2762 if (aLineBreakType == LineBreakType::BRElement) { 2763 return HTMLEditUtils::CanNodeContain(aContent, *nsGkAtoms::br); 2764 } 2765 MOZ_ASSERT(aLineBreakType == LineBreakType::Linefeed); 2766 const Element* const container = aContent.GetAsElementOrParentElement(); 2767 return container && 2768 HTMLEditUtils::CanNodeContain(*container, *nsGkAtoms::textTagName) && 2769 EditorUtils::IsNewLinePreformatted(*container); 2770 } 2771 2772 Result<CreateLineBreakResult, nsresult> 2773 HTMLEditor::InsertPaddingBRElementToMakeEmptyLineVisibleIfNeeded( 2774 const EditorDOMPoint& aPointToInsert) { 2775 MOZ_ASSERT(IsEditActionDataAvailable()); 2776 MOZ_ASSERT(aPointToInsert.IsSet()); 2777 2778 if (MOZ_UNLIKELY(!aPointToInsert.IsInContentNode())) { 2779 return CreateLineBreakResult::NotHandled(); 2780 } 2781 2782 // If we cannot insert a line break here, do nothing. 2783 if (!HTMLEditor::CanInsertLineBreak( 2784 LineBreakType::BRElement, 2785 *aPointToInsert.ContainerAs<nsIContent>())) { 2786 return CreateLineBreakResult::NotHandled(); 2787 } 2788 2789 // FYI: We don't need to put <br> if it reaches an inline editing host because 2790 // editing host has at least one line height by default even if it's empty and 2791 // it's tested by WPT to no <br> element is inserted in the cases. 2792 2793 // If the point is not start of a line, we don't need to put a line break 2794 // here. 2795 const WSScanResult previousThing = 2796 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 2797 {WSRunScanner::Option::OnlyEditableNodes}, aPointToInsert); 2798 if (!previousThing.ReachedLineBoundary()) { 2799 return CreateLineBreakResult::NotHandled(); 2800 } 2801 2802 // If the point is not followed by a block boundary, we don't need to put a 2803 // line break here. 2804 const WSScanResult nextThing = 2805 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 2806 {WSRunScanner::Option::OnlyEditableNodes}, aPointToInsert); 2807 if (!nextThing.ReachedBlockBoundary()) { 2808 return CreateLineBreakResult::NotHandled(); 2809 } 2810 2811 Result<CreateLineBreakResult, nsresult> insertLineBreakResultOrError = 2812 InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement, 2813 aPointToInsert, nsIEditor::ePrevious); 2814 NS_WARNING_ASSERTION(insertLineBreakResultOrError.isOk(), 2815 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 2816 "LineBreakType::BRElement, ePrevious) failed"); 2817 return insertLineBreakResultOrError; 2818 } 2819 2820 Result<EditActionResult, nsresult> 2821 HTMLEditor::MakeOrChangeListAndListItemAsSubAction( 2822 const nsStaticAtom& aListElementOrListItemElementTagName, 2823 const nsAString& aBulletType, 2824 SelectAllOfCurrentList aSelectAllOfCurrentList, 2825 const Element& aEditingHost) { 2826 MOZ_ASSERT(IsEditActionDataAvailable()); 2827 MOZ_ASSERT(&aListElementOrListItemElementTagName == nsGkAtoms::ul || 2828 &aListElementOrListItemElementTagName == nsGkAtoms::ol || 2829 &aListElementOrListItemElementTagName == nsGkAtoms::dl || 2830 &aListElementOrListItemElementTagName == nsGkAtoms::dd || 2831 &aListElementOrListItemElementTagName == nsGkAtoms::dt); 2832 2833 if (NS_WARN_IF(!mInitSucceeded)) { 2834 return Err(NS_ERROR_NOT_INITIALIZED); 2835 } 2836 2837 { 2838 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 2839 if (MOZ_UNLIKELY(result.isErr())) { 2840 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 2841 return result; 2842 } 2843 if (result.inspect().Canceled()) { 2844 return result; 2845 } 2846 } 2847 2848 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) { 2849 NS_WARNING("Some selection containers are not content node, but ignored"); 2850 return EditActionResult::IgnoredResult(); 2851 } 2852 2853 AutoPlaceholderBatch treatAsOneTransaction( 2854 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 2855 2856 // XXX EditSubAction::eCreateOrChangeDefinitionListItem and 2857 // EditSubAction::eCreateOrChangeList are treated differently in 2858 // HTMLEditor::MaybeSplitElementsAtEveryBRElement(). Only when 2859 // EditSubAction::eCreateOrChangeList, it splits inline nodes. 2860 // Currently, it shouldn't be done when we called for formatting 2861 // `<dd>` or `<dt>` by 2862 // HTMLEditor::MakeDefinitionListItemWithTransaction(). But this 2863 // difference may be a bug. We should investigate this later. 2864 IgnoredErrorResult error; 2865 AutoEditSubActionNotifier startToHandleEditSubAction( 2866 *this, 2867 &aListElementOrListItemElementTagName == nsGkAtoms::dd || 2868 &aListElementOrListItemElementTagName == nsGkAtoms::dt 2869 ? EditSubAction::eCreateOrChangeDefinitionListItem 2870 : EditSubAction::eCreateOrChangeList, 2871 nsIEditor::eNext, error); 2872 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 2873 return Err(error.StealNSResult()); 2874 } 2875 NS_WARNING_ASSERTION( 2876 !error.Failed(), 2877 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 2878 2879 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor(); 2880 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2881 return Err(NS_ERROR_EDITOR_DESTROYED); 2882 } 2883 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2884 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " 2885 "failed, but ignored"); 2886 2887 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) { 2888 nsresult rv = EnsureCaretNotAfterInvisibleBRElement(aEditingHost); 2889 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2890 return Err(NS_ERROR_EDITOR_DESTROYED); 2891 } 2892 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2893 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " 2894 "failed, but ignored"); 2895 if (NS_SUCCEEDED(rv)) { 2896 nsresult rv = PrepareInlineStylesForCaret(); 2897 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2898 return Err(NS_ERROR_EDITOR_DESTROYED); 2899 } 2900 NS_WARNING_ASSERTION( 2901 NS_SUCCEEDED(rv), 2902 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); 2903 } 2904 } 2905 2906 const nsStaticAtom* listTagName = nullptr; 2907 const nsStaticAtom* listItemTagName = nullptr; 2908 if (&aListElementOrListItemElementTagName == nsGkAtoms::ul || 2909 &aListElementOrListItemElementTagName == nsGkAtoms::ol) { 2910 listTagName = &aListElementOrListItemElementTagName; 2911 listItemTagName = nsGkAtoms::li; 2912 } else if (&aListElementOrListItemElementTagName == nsGkAtoms::dl) { 2913 listTagName = &aListElementOrListItemElementTagName; 2914 listItemTagName = nsGkAtoms::dd; 2915 } else if (&aListElementOrListItemElementTagName == nsGkAtoms::dd || 2916 &aListElementOrListItemElementTagName == nsGkAtoms::dt) { 2917 listTagName = nsGkAtoms::dl; 2918 listItemTagName = &aListElementOrListItemElementTagName; 2919 } else { 2920 NS_WARNING( 2921 "aListElementOrListItemElementTagName was neither list element name " 2922 "nor " 2923 "definition listitem element name"); 2924 return Err(NS_ERROR_INVALID_ARG); 2925 } 2926 2927 // Expands selection range to include the immediate block parent, and then 2928 // further expands to include any ancestors whose children are all in the 2929 // range. 2930 // XXX Why do we do this only when there is only one selection range? 2931 if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) { 2932 Result<EditorRawDOMRange, nsresult> extendedRange = 2933 GetRangeExtendedToHardLineEdgesForBlockEditAction( 2934 SelectionRef().GetRangeAt(0u), aEditingHost); 2935 if (MOZ_UNLIKELY(extendedRange.isErr())) { 2936 NS_WARNING( 2937 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() " 2938 "failed"); 2939 return extendedRange.propagateErr(); 2940 } 2941 // Note that end point may be prior to start point. So, we 2942 // cannot use Selection::SetStartAndEndInLimit() here. 2943 error.SuppressException(); 2944 SelectionRef().SetBaseAndExtentInLimiter( 2945 extendedRange.inspect().StartRef().ToRawRangeBoundary(), 2946 extendedRange.inspect().EndRef().ToRawRangeBoundary(), error); 2947 if (NS_WARN_IF(Destroyed())) { 2948 return Err(NS_ERROR_EDITOR_DESTROYED); 2949 } 2950 if (MOZ_UNLIKELY(error.Failed())) { 2951 NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed"); 2952 return Err(error.StealNSResult()); 2953 } 2954 } 2955 2956 AutoListElementCreator listCreator(*listTagName, *listItemTagName, 2957 aBulletType); 2958 AutoClonedSelectionRangeArray selectionRanges(SelectionRef()); 2959 Result<EditActionResult, nsresult> result = listCreator.Run( 2960 *this, selectionRanges, aSelectAllOfCurrentList, aEditingHost); 2961 if (MOZ_UNLIKELY(result.isErr())) { 2962 NS_WARNING("HTMLEditor::ConvertContentAroundRangesToList() failed"); 2963 // XXX Should we try to restore selection ranges in this case? 2964 return result; 2965 } 2966 2967 rv = selectionRanges.ApplyTo(SelectionRef()); 2968 if (NS_WARN_IF(Destroyed())) { 2969 return Err(NS_ERROR_EDITOR_DESTROYED); 2970 } 2971 if (NS_FAILED(rv)) { 2972 NS_WARNING("AutoClonedSelectionRangeArray::ApplyTo() failed"); 2973 return Err(rv); 2974 } 2975 return result.inspect().Ignored() ? EditActionResult::CanceledResult() 2976 : EditActionResult::HandledResult(); 2977 } 2978 2979 Result<EditActionResult, nsresult> HTMLEditor::AutoListElementCreator::Run( 2980 HTMLEditor& aHTMLEditor, AutoClonedSelectionRangeArray& aRanges, 2981 SelectAllOfCurrentList aSelectAllOfCurrentList, 2982 const Element& aEditingHost) const { 2983 MOZ_ASSERT(aHTMLEditor.IsTopLevelEditSubActionDataAvailable()); 2984 MOZ_ASSERT(!aHTMLEditor.IsSelectionRangeContainerNotContent()); 2985 2986 if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(aHTMLEditor))) { 2987 return Err(NS_ERROR_FAILURE); 2988 } 2989 2990 AutoContentNodeArray arrayOfContents; 2991 nsresult rv = SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList( 2992 aHTMLEditor, aRanges, aSelectAllOfCurrentList, aEditingHost, 2993 arrayOfContents); 2994 if (NS_FAILED(rv)) { 2995 NS_WARNING( 2996 "AutoListElementCreator::" 2997 "SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList() failed"); 2998 return Err(rv); 2999 } 3000 3001 // check if all our nodes are <br>s, or empty inlines 3002 // if no nodes, we make empty list. Ditto if the user tried to make a list 3003 // of some # of breaks. 3004 if (AutoListElementCreator:: 3005 IsEmptyOrContainsOnlyBRElementsOrEmptyInlineElements( 3006 arrayOfContents)) { 3007 Result<RefPtr<Element>, nsresult> newListItemElementOrError = 3008 ReplaceContentNodesWithEmptyNewList(aHTMLEditor, aRanges, 3009 arrayOfContents, aEditingHost); 3010 if (MOZ_UNLIKELY(newListItemElementOrError.isErr())) { 3011 NS_WARNING( 3012 "AutoListElementCreator::ReplaceContentNodesWithEmptyNewList() " 3013 "failed"); 3014 return newListItemElementOrError.propagateErr(); 3015 } 3016 if (MOZ_UNLIKELY(!newListItemElementOrError.inspect())) { 3017 aRanges.RestoreFromSavedRanges(); 3018 return EditActionResult::CanceledResult(); 3019 } 3020 aRanges.ClearSavedRanges(); 3021 nsresult rv = aRanges.Collapse( 3022 EditorRawDOMPoint(newListItemElementOrError.inspect(), 0u)); 3023 if (NS_FAILED(rv)) { 3024 NS_WARNING("AutoClonedRangeArray::Collapse() failed"); 3025 return Err(rv); 3026 } 3027 return EditActionResult::IgnoredResult(); 3028 } 3029 3030 Result<RefPtr<Element>, nsresult> listItemOrListToPutCaretOrError = 3031 WrapContentNodesIntoNewListElements(aHTMLEditor, aRanges, arrayOfContents, 3032 aEditingHost); 3033 if (MOZ_UNLIKELY(listItemOrListToPutCaretOrError.isErr())) { 3034 NS_WARNING( 3035 "AutoListElementCreator::WrapContentNodesIntoNewListElements() failed"); 3036 return listItemOrListToPutCaretOrError.propagateErr(); 3037 } 3038 3039 MOZ_ASSERT(aRanges.HasSavedRanges()); 3040 aRanges.RestoreFromSavedRanges(); 3041 3042 // If selection will be collapsed but not in listItemOrListToPutCaret, we need 3043 // to adjust the caret position into it. 3044 if (listItemOrListToPutCaretOrError.inspect()) { 3045 DebugOnly<nsresult> rvIgnored = 3046 EnsureCollapsedRangeIsInListItemOrListElement( 3047 *listItemOrListToPutCaretOrError.inspect(), aRanges); 3048 NS_WARNING_ASSERTION( 3049 NS_SUCCEEDED(rvIgnored), 3050 "AutoListElementCreator::" 3051 "EnsureCollapsedRangeIsInListItemOrListElement() failed, but ignored"); 3052 } 3053 3054 return EditActionResult::HandledResult(); 3055 } 3056 3057 nsresult HTMLEditor::AutoListElementCreator:: 3058 SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList( 3059 HTMLEditor& aHTMLEditor, AutoClonedRangeArray& aRanges, 3060 SelectAllOfCurrentList aSelectAllOfCurrentList, 3061 const Element& aEditingHost, 3062 ContentNodeArray& aOutArrayOfContents) const { 3063 MOZ_ASSERT(aOutArrayOfContents.IsEmpty()); 3064 3065 if (aSelectAllOfCurrentList == SelectAllOfCurrentList::Yes) { 3066 if (Element* parentListElementOfRanges = 3067 aRanges.GetClosestAncestorAnyListElementOfRange()) { 3068 aOutArrayOfContents.AppendElement( 3069 OwningNonNull<nsIContent>(*parentListElementOfRanges)); 3070 return NS_OK; 3071 } 3072 } 3073 3074 AutoClonedRangeArray extendedRanges(aRanges); 3075 3076 // TODO: We don't need AutoTransactionsConserveSelection here in the 3077 // normal cases, but removing this may cause the behavior with the 3078 // legacy mutation event listeners. We should try to delete this in 3079 // a bug. 3080 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor); 3081 3082 extendedRanges.ExtendRangesToWrapLines(EditSubAction::eCreateOrChangeList, 3083 BlockInlineCheck::UseHTMLDefaultStyle, 3084 aEditingHost); 3085 Result<EditorDOMPoint, nsresult> splitResult = 3086 extendedRanges.SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries( 3087 aHTMLEditor, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost); 3088 if (MOZ_UNLIKELY(splitResult.isErr())) { 3089 NS_WARNING( 3090 "AutoClonedRangeArray::" 3091 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() failed"); 3092 return splitResult.unwrapErr(); 3093 } 3094 nsresult rv = extendedRanges.CollectEditTargetNodes( 3095 aHTMLEditor, aOutArrayOfContents, EditSubAction::eCreateOrChangeList, 3096 AutoClonedRangeArray::CollectNonEditableNodes::No); 3097 if (NS_FAILED(rv)) { 3098 NS_WARNING( 3099 "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::" 3100 "eCreateOrChangeList, CollectNonEditableNodes::No) failed"); 3101 return rv; 3102 } 3103 3104 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult = 3105 aHTMLEditor.MaybeSplitElementsAtEveryBRElement( 3106 aOutArrayOfContents, EditSubAction::eCreateOrChangeList); 3107 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) { 3108 NS_WARNING( 3109 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::" 3110 "eCreateOrChangeList) failed"); 3111 return splitAtBRElementsResult.unwrapErr(); 3112 } 3113 return NS_OK; 3114 } 3115 3116 // static 3117 bool HTMLEditor::AutoListElementCreator:: 3118 IsEmptyOrContainsOnlyBRElementsOrEmptyInlineElements( 3119 const ContentNodeArray& aArrayOfContents) { 3120 for (const OwningNonNull<nsIContent>& content : aArrayOfContents) { 3121 // if content is not a <br> or empty inline, we're done 3122 // XXX Should we handle line breaks in preformatted text node? 3123 if (!content->IsHTMLElement(nsGkAtoms::br) && 3124 !HTMLEditUtils::IsEmptyInlineContainer( 3125 content, 3126 {EmptyCheckOption::TreatSingleBRElementAsVisible, 3127 EmptyCheckOption::TreatNonEditableContentAsInvisible}, 3128 BlockInlineCheck::UseComputedDisplayStyle)) { 3129 return false; 3130 } 3131 } 3132 return true; 3133 } 3134 3135 Result<RefPtr<Element>, nsresult> 3136 HTMLEditor::AutoListElementCreator::ReplaceContentNodesWithEmptyNewList( 3137 HTMLEditor& aHTMLEditor, const AutoClonedRangeArray& aRanges, 3138 const AutoContentNodeArray& aArrayOfContents, 3139 const Element& aEditingHost) const { 3140 // if only breaks, delete them 3141 for (const OwningNonNull<nsIContent>& content : aArrayOfContents) { 3142 // MOZ_KnownLive because of bug 1620312 3143 nsresult rv = 3144 aHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(*content)); 3145 if (NS_FAILED(rv)) { 3146 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 3147 return Err(rv); 3148 } 3149 } 3150 3151 const auto firstRangeStartPoint = 3152 aRanges.GetFirstRangeStartPoint<EditorDOMPoint>(); 3153 if (NS_WARN_IF(!firstRangeStartPoint.IsSet())) { 3154 return Err(NS_ERROR_FAILURE); 3155 } 3156 3157 // Make sure we can put a list here. 3158 if (!HTMLEditUtils::CanNodeContain(*firstRangeStartPoint.GetContainer(), 3159 mListTagName)) { 3160 return RefPtr<Element>(); 3161 } 3162 3163 RefPtr<Element> newListItemElement; 3164 Result<CreateElementResult, nsresult> createNewListElementResult = 3165 aHTMLEditor.InsertElementWithSplittingAncestorsWithTransaction( 3166 mListTagName, firstRangeStartPoint, BRElementNextToSplitPoint::Keep, 3167 aEditingHost, 3168 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868 3169 [&](HTMLEditor& aHTMLEditor, Element& aListElement, 3170 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY { 3171 AutoHandlingState dummyState; 3172 Result<CreateElementResult, nsresult> createListItemElementResult = 3173 AppendListItemElement(aHTMLEditor, aListElement, dummyState); 3174 if (MOZ_UNLIKELY(createListItemElementResult.isErr())) { 3175 NS_WARNING( 3176 "AutoListElementCreator::AppendListItemElement() failed"); 3177 return createListItemElementResult.unwrapErr(); 3178 } 3179 CreateElementResult unwrappedResult = 3180 createListItemElementResult.unwrap(); 3181 // There is AutoSelectionRestorer in this method so that it'll 3182 // be restored or updated with making it abort. Therefore, 3183 // we don't need to update selection here. 3184 // XXX I'd like to check aRanges.HasSavedRanges() here, but it 3185 // requires ifdefs to avoid bustage of opt builds caused 3186 // by unused warning... 3187 unwrappedResult.IgnoreCaretPointSuggestion(); 3188 newListItemElement = unwrappedResult.UnwrapNewNode(); 3189 MOZ_ASSERT(newListItemElement); 3190 return NS_OK; 3191 }); 3192 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) { 3193 NS_WARNING( 3194 nsPrintfCString( 3195 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(" 3196 "%s) failed", 3197 nsAtomCString(&mListTagName).get()) 3198 .get()); 3199 return createNewListElementResult.propagateErr(); 3200 } 3201 MOZ_ASSERT(createNewListElementResult.inspect().GetNewNode()); 3202 3203 // Put selection in new list item and don't restore the Selection. 3204 createNewListElementResult.inspect().IgnoreCaretPointSuggestion(); 3205 return newListItemElement; 3206 } 3207 3208 Result<RefPtr<Element>, nsresult> 3209 HTMLEditor::AutoListElementCreator::WrapContentNodesIntoNewListElements( 3210 HTMLEditor& aHTMLEditor, AutoClonedRangeArray& aRanges, 3211 AutoContentNodeArray& aArrayOfContents, const Element& aEditingHost) const { 3212 // if there is only one node in the array, and it is a list, div, or 3213 // blockquote, then look inside of it until we find inner list or content. 3214 if (aArrayOfContents.Length() == 1) { 3215 if (Element* deepestDivBlockquoteOrListElement = 3216 HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild( 3217 aArrayOfContents[0], {WalkTreeOption::IgnoreNonEditableNode}, 3218 BlockInlineCheck::UseHTMLDefaultStyle, nsGkAtoms::div, 3219 nsGkAtoms::blockquote, nsGkAtoms::ul, nsGkAtoms::ol, 3220 nsGkAtoms::dl)) { 3221 if (deepestDivBlockquoteOrListElement->IsAnyOfHTMLElements( 3222 nsGkAtoms::div, nsGkAtoms::blockquote)) { 3223 aArrayOfContents.Clear(); 3224 HTMLEditUtils::CollectChildren(*deepestDivBlockquoteOrListElement, 3225 aArrayOfContents, 0, {}); 3226 } else { 3227 aArrayOfContents.ReplaceElementAt( 3228 0, OwningNonNull<nsIContent>(*deepestDivBlockquoteOrListElement)); 3229 } 3230 } 3231 } 3232 3233 // Ok, now go through all the nodes and put then in the list, 3234 // or whatever is appropriate. Wohoo! 3235 AutoHandlingState handlingState; 3236 for (const OwningNonNull<nsIContent>& content : aArrayOfContents) { 3237 // MOZ_KnownLive because of bug 1620312 3238 nsresult rv = HandleChildContent(aHTMLEditor, MOZ_KnownLive(content), 3239 handlingState, aEditingHost); 3240 if (NS_FAILED(rv)) { 3241 NS_WARNING("AutoListElementCreator::HandleChildContent() failed"); 3242 return Err(rv); 3243 } 3244 } 3245 3246 return std::move(handlingState.mListOrListItemElementToPutCaret); 3247 } 3248 3249 nsresult HTMLEditor::AutoListElementCreator::HandleChildContent( 3250 HTMLEditor& aHTMLEditor, nsIContent& aHandlingContent, 3251 AutoHandlingState& aState, const Element& aEditingHost) const { 3252 // make sure we don't assemble content that is in different table cells 3253 // into the same list. respect table cell boundaries when listifying. 3254 if (aState.mCurrentListElement && 3255 HTMLEditUtils::GetInclusiveAncestorAnyTableElement( 3256 *aState.mCurrentListElement) != 3257 HTMLEditUtils::GetInclusiveAncestorAnyTableElement( 3258 aHandlingContent)) { 3259 aState.mCurrentListElement = nullptr; 3260 } 3261 3262 // If current node is a `<br>` element, delete it and forget previous 3263 // list item element. 3264 // If current node is an empty inline node, just delete it. 3265 if (EditorUtils::IsEditableContent(aHandlingContent, EditorType::HTML) && 3266 (aHandlingContent.IsHTMLElement(nsGkAtoms::br) || 3267 HTMLEditUtils::IsEmptyInlineContainer( 3268 aHandlingContent, 3269 {EmptyCheckOption::TreatSingleBRElementAsVisible, 3270 EmptyCheckOption::TreatNonEditableContentAsInvisible}, 3271 BlockInlineCheck::UseHTMLDefaultStyle))) { 3272 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aHandlingContent); 3273 if (NS_FAILED(rv)) { 3274 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 3275 return rv; 3276 } 3277 if (aHandlingContent.IsHTMLElement(nsGkAtoms::br)) { 3278 aState.mPreviousListItemElement = nullptr; 3279 } 3280 return NS_OK; 3281 } 3282 3283 // If we meet a list, we can reuse it or convert it to the expected type list. 3284 if (HTMLEditUtils::IsListElement(aHandlingContent)) { 3285 nsresult rv = HandleChildListElement( 3286 aHTMLEditor, MOZ_KnownLive(*aHandlingContent.AsElement()), aState); 3287 NS_WARNING_ASSERTION( 3288 NS_SUCCEEDED(rv), 3289 "AutoListElementCreator::HandleChildListElement() failed"); 3290 return rv; 3291 } 3292 3293 // We cannot handle nodes if not in element node. 3294 if (NS_WARN_IF(!aHandlingContent.GetParentElement())) { 3295 return NS_ERROR_FAILURE; 3296 } 3297 3298 // If we meet a list item, we can just move it to current list element or new 3299 // list element. 3300 if (HTMLEditUtils::IsListItemElement(aHandlingContent)) { 3301 nsresult rv = HandleChildListItemElement( 3302 aHTMLEditor, MOZ_KnownLive(*aHandlingContent.AsElement()), aState); 3303 NS_WARNING_ASSERTION( 3304 NS_SUCCEEDED(rv), 3305 "AutoListElementCreator::HandleChildListItemElement() failed"); 3306 return rv; 3307 } 3308 3309 // If we meet a <div> or a <p>, we want only its children to wrapping into 3310 // list element. Therefore, this call will call this recursively. 3311 if (aHandlingContent.IsAnyOfHTMLElements(nsGkAtoms::div, nsGkAtoms::p)) { 3312 nsresult rv = HandleChildDivOrParagraphElement( 3313 aHTMLEditor, MOZ_KnownLive(*aHandlingContent.AsElement()), aState, 3314 aEditingHost); 3315 NS_WARNING_ASSERTION( 3316 NS_SUCCEEDED(rv), 3317 "AutoListElementCreator::HandleChildDivOrParagraphElement() failed"); 3318 return rv; 3319 } 3320 3321 // If we've not met a list element, create a list element and make it 3322 // current list element. 3323 if (!aState.mCurrentListElement) { 3324 nsresult rv = CreateAndUpdateCurrentListElement( 3325 aHTMLEditor, EditorDOMPoint(&aHandlingContent), 3326 EmptyListItem::NotCreate, aState, aEditingHost); 3327 if (NS_FAILED(rv)) { 3328 NS_WARNING("AutoListElementCreator::HandleChildInlineElement() failed"); 3329 return rv; 3330 } 3331 } 3332 3333 // If we meet an inline content, we want to move it to previously used list 3334 // item element or new list item element. 3335 if (HTMLEditUtils::IsInlineContent(aHandlingContent, 3336 BlockInlineCheck::UseHTMLDefaultStyle)) { 3337 nsresult rv = 3338 HandleChildInlineContent(aHTMLEditor, aHandlingContent, aState); 3339 NS_WARNING_ASSERTION( 3340 NS_SUCCEEDED(rv), 3341 "AutoListElementCreator::HandleChildInlineElement() failed"); 3342 return rv; 3343 } 3344 3345 // Otherwise, we should wrap it into new list item element. 3346 nsresult rv = 3347 WrapContentIntoNewListItemElement(aHTMLEditor, aHandlingContent, aState); 3348 NS_WARNING_ASSERTION( 3349 NS_SUCCEEDED(rv), 3350 "AutoListElementCreator::WrapContentIntoNewListItemElement() failed"); 3351 return rv; 3352 } 3353 3354 nsresult HTMLEditor::AutoListElementCreator::HandleChildListElement( 3355 HTMLEditor& aHTMLEditor, Element& aHandlingListElement, 3356 AutoHandlingState& aState) const { 3357 MOZ_ASSERT(HTMLEditUtils::IsListElement(aHandlingListElement)); 3358 3359 // If we met a list element and current list element is not a descendant 3360 // of the list, append current node to end of the current list element. 3361 // Then, wrap it with list item element and delete the old container. 3362 if (aState.mCurrentListElement && 3363 !EditorUtils::IsDescendantOf(aHandlingListElement, 3364 *aState.mCurrentListElement)) { 3365 Result<MoveNodeResult, nsresult> moveNodeResult = 3366 aHTMLEditor.MoveNodeToEndWithTransaction( 3367 aHandlingListElement, MOZ_KnownLive(*aState.mCurrentListElement)); 3368 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 3369 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); 3370 return moveNodeResult.propagateErr(); 3371 } 3372 moveNodeResult.inspect().IgnoreCaretPointSuggestion(); 3373 3374 Result<CreateElementResult, nsresult> convertListTypeResult = 3375 aHTMLEditor.ChangeListElementType(aHandlingListElement, mListTagName, 3376 mListItemTagName); 3377 if (MOZ_UNLIKELY(convertListTypeResult.isErr())) { 3378 NS_WARNING("HTMLEditor::ChangeListElementType() failed"); 3379 return convertListTypeResult.propagateErr(); 3380 } 3381 convertListTypeResult.inspect().IgnoreCaretPointSuggestion(); 3382 3383 Result<EditorDOMPoint, nsresult> unwrapNewListElementResult = 3384 aHTMLEditor.RemoveBlockContainerWithTransaction( 3385 MOZ_KnownLive(*convertListTypeResult.inspect().GetNewNode())); 3386 if (MOZ_UNLIKELY(unwrapNewListElementResult.isErr())) { 3387 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed"); 3388 return unwrapNewListElementResult.propagateErr(); 3389 } 3390 aState.mPreviousListItemElement = nullptr; 3391 return NS_OK; 3392 } 3393 3394 // If current list element is in found list element or we've not met a 3395 // list element, convert current list element to proper type. 3396 Result<CreateElementResult, nsresult> convertListTypeResult = 3397 aHTMLEditor.ChangeListElementType(aHandlingListElement, mListTagName, 3398 mListItemTagName); 3399 if (MOZ_UNLIKELY(convertListTypeResult.isErr())) { 3400 NS_WARNING("HTMLEditor::ChangeListElementType() failed"); 3401 return convertListTypeResult.propagateErr(); 3402 } 3403 CreateElementResult unwrappedConvertListTypeResult = 3404 convertListTypeResult.unwrap(); 3405 unwrappedConvertListTypeResult.IgnoreCaretPointSuggestion(); 3406 MOZ_ASSERT(unwrappedConvertListTypeResult.GetNewNode()); 3407 aState.mCurrentListElement = unwrappedConvertListTypeResult.UnwrapNewNode(); 3408 aState.mPreviousListItemElement = nullptr; 3409 return NS_OK; 3410 } 3411 3412 nsresult 3413 HTMLEditor::AutoListElementCreator::HandleChildListItemInDifferentTypeList( 3414 HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement, 3415 AutoHandlingState& aState) const { 3416 MOZ_ASSERT(HTMLEditUtils::IsListItemElement(aHandlingListItemElement)); 3417 MOZ_ASSERT( 3418 !aHandlingListItemElement.GetParent()->IsHTMLElement(&mListTagName)); 3419 3420 // If we've not met a list element or current node is not in current list 3421 // element, insert a list element at current node and set current list element 3422 // to the new one. 3423 if (!aState.mCurrentListElement || 3424 aHandlingListItemElement.IsInclusiveDescendantOf( 3425 aState.mCurrentListElement)) { 3426 EditorDOMPoint atListItem(&aHandlingListItemElement); 3427 MOZ_ASSERT(atListItem.IsInContentNode()); 3428 3429 Result<SplitNodeResult, nsresult> splitListItemParentResult = 3430 aHTMLEditor.SplitNodeWithTransaction(atListItem); 3431 if (MOZ_UNLIKELY(splitListItemParentResult.isErr())) { 3432 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed"); 3433 return splitListItemParentResult.propagateErr(); 3434 } 3435 SplitNodeResult unwrappedSplitListItemParentResult = 3436 splitListItemParentResult.unwrap(); 3437 MOZ_ASSERT(unwrappedSplitListItemParentResult.DidSplit()); 3438 unwrappedSplitListItemParentResult.IgnoreCaretPointSuggestion(); 3439 3440 Result<CreateElementResult, nsresult> createNewListElementResult = 3441 aHTMLEditor.CreateAndInsertElement( 3442 WithTransaction::Yes, mListTagName, 3443 unwrappedSplitListItemParentResult.AtNextContent<EditorDOMPoint>()); 3444 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) { 3445 NS_WARNING( 3446 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) " 3447 "failed"); 3448 return createNewListElementResult.propagateErr(); 3449 } 3450 CreateElementResult unwrapCreateNewListElementResult = 3451 createNewListElementResult.unwrap(); 3452 unwrapCreateNewListElementResult.IgnoreCaretPointSuggestion(); 3453 MOZ_ASSERT(unwrapCreateNewListElementResult.GetNewNode()); 3454 aState.mCurrentListElement = 3455 unwrapCreateNewListElementResult.UnwrapNewNode(); 3456 } 3457 3458 // Then, move current node into current list element. 3459 Result<MoveNodeResult, nsresult> moveNodeResult = 3460 aHTMLEditor.MoveNodeToEndWithTransaction( 3461 aHandlingListItemElement, MOZ_KnownLive(*aState.mCurrentListElement)); 3462 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 3463 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); 3464 return moveNodeResult.propagateErr(); 3465 } 3466 moveNodeResult.inspect().IgnoreCaretPointSuggestion(); 3467 3468 // Convert list item type if current node is different list item type. 3469 if (aHandlingListItemElement.IsHTMLElement(&mListItemTagName)) { 3470 return NS_OK; 3471 } 3472 Result<CreateElementResult, nsresult> newListItemElementOrError = 3473 aHTMLEditor.ReplaceContainerAndCloneAttributesWithTransaction( 3474 aHandlingListItemElement, mListItemTagName); 3475 if (MOZ_UNLIKELY(newListItemElementOrError.isErr())) { 3476 NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed"); 3477 return newListItemElementOrError.propagateErr(); 3478 } 3479 newListItemElementOrError.inspect().IgnoreCaretPointSuggestion(); 3480 return NS_OK; 3481 } 3482 3483 nsresult HTMLEditor::AutoListElementCreator::HandleChildListItemElement( 3484 HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement, 3485 AutoHandlingState& aState) const { 3486 MOZ_ASSERT(aHandlingListItemElement.GetParentNode()); 3487 MOZ_ASSERT(HTMLEditUtils::IsListItemElement(aHandlingListItemElement)); 3488 3489 // If current list item element is not in proper list element, we need 3490 // to convert the list element. 3491 // XXX This check is not enough, 3492 if (!aHandlingListItemElement.GetParentNode()->IsHTMLElement(&mListTagName)) { 3493 nsresult rv = HandleChildListItemInDifferentTypeList( 3494 aHTMLEditor, aHandlingListItemElement, aState); 3495 if (NS_FAILED(rv)) { 3496 NS_WARNING( 3497 "AutoListElementCreator::HandleChildListItemInDifferentTypeList() " 3498 "failed"); 3499 return rv; 3500 } 3501 } else { 3502 nsresult rv = HandleChildListItemInSameTypeList( 3503 aHTMLEditor, aHandlingListItemElement, aState); 3504 if (NS_FAILED(rv)) { 3505 NS_WARNING( 3506 "AutoListElementCreator::HandleChildListItemInSameTypeList() failed"); 3507 return rv; 3508 } 3509 } 3510 3511 // If bullet type is specified, set list type attribute. 3512 // XXX Cannot we set type attribute before inserting the list item 3513 // element into the DOM tree? 3514 if (!mBulletType.IsEmpty()) { 3515 nsresult rv = aHTMLEditor.SetAttributeWithTransaction( 3516 aHandlingListItemElement, *nsGkAtoms::type, mBulletType); 3517 if (NS_WARN_IF(aHTMLEditor.Destroyed())) { 3518 return NS_ERROR_EDITOR_DESTROYED; 3519 } 3520 NS_WARNING_ASSERTION( 3521 NS_SUCCEEDED(rv), 3522 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::type) failed"); 3523 return rv; 3524 } 3525 3526 // Otherwise, remove list type attribute if there is. 3527 if (!aHandlingListItemElement.HasAttr(nsGkAtoms::type)) { 3528 return NS_OK; 3529 } 3530 nsresult rv = aHTMLEditor.RemoveAttributeWithTransaction( 3531 aHandlingListItemElement, *nsGkAtoms::type); 3532 NS_WARNING_ASSERTION( 3533 NS_SUCCEEDED(rv), 3534 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::type) failed"); 3535 return rv; 3536 } 3537 3538 nsresult HTMLEditor::AutoListElementCreator::HandleChildListItemInSameTypeList( 3539 HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement, 3540 AutoHandlingState& aState) const { 3541 MOZ_ASSERT(HTMLEditUtils::IsListItemElement(aHandlingListItemElement)); 3542 MOZ_ASSERT( 3543 aHandlingListItemElement.GetParent()->IsHTMLElement(&mListTagName)); 3544 3545 EditorDOMPoint atListItem(&aHandlingListItemElement); 3546 MOZ_ASSERT(atListItem.IsInContentNode()); 3547 3548 // If we've not met a list element, set current list element to the 3549 // parent of current list item element. 3550 if (!aState.mCurrentListElement) { 3551 aState.mCurrentListElement = atListItem.GetContainerAs<Element>(); 3552 NS_WARNING_ASSERTION( 3553 HTMLEditUtils::IsListElement(*aState.mCurrentListElement), 3554 "Current list item parent is not a list element"); 3555 } 3556 // If current list item element is not a child of current list element, 3557 // move it into current list item. 3558 else if (atListItem.GetContainer() != aState.mCurrentListElement) { 3559 Result<MoveNodeResult, nsresult> moveNodeResult = 3560 aHTMLEditor.MoveNodeToEndWithTransaction( 3561 aHandlingListItemElement, 3562 MOZ_KnownLive(*aState.mCurrentListElement)); 3563 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 3564 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); 3565 return moveNodeResult.propagateErr(); 3566 } 3567 moveNodeResult.inspect().IgnoreCaretPointSuggestion(); 3568 } 3569 3570 // Then, if current list item element is not proper type for current 3571 // list element, convert list item element to proper element. 3572 if (aHandlingListItemElement.IsHTMLElement(&mListItemTagName)) { 3573 return NS_OK; 3574 } 3575 // FIXME: Manage attribute cloning 3576 Result<CreateElementResult, nsresult> newListItemElementOrError = 3577 aHTMLEditor.ReplaceContainerAndCloneAttributesWithTransaction( 3578 aHandlingListItemElement, mListItemTagName); 3579 if (MOZ_UNLIKELY(newListItemElementOrError.isErr())) { 3580 NS_WARNING( 3581 "HTMLEditor::ReplaceContainerAndCloneAttributesWithTransaction() " 3582 "failed"); 3583 return newListItemElementOrError.propagateErr(); 3584 } 3585 newListItemElementOrError.inspect().IgnoreCaretPointSuggestion(); 3586 return NS_OK; 3587 } 3588 3589 nsresult HTMLEditor::AutoListElementCreator::HandleChildDivOrParagraphElement( 3590 HTMLEditor& aHTMLEditor, Element& aHandlingDivOrParagraphElement, 3591 AutoHandlingState& aState, const Element& aEditingHost) const { 3592 MOZ_ASSERT(aHandlingDivOrParagraphElement.IsAnyOfHTMLElements(nsGkAtoms::div, 3593 nsGkAtoms::p)); 3594 3595 AutoRestore<RefPtr<Element>> previouslyReplacingBlockElement( 3596 aState.mReplacingBlockElement); 3597 aState.mReplacingBlockElement = &aHandlingDivOrParagraphElement; 3598 AutoRestore<bool> previouslyReplacingBlockElementIdCopied( 3599 aState.mMaybeCopiedReplacingBlockElementId); 3600 aState.mMaybeCopiedReplacingBlockElementId = false; 3601 3602 // If the <div> or <p> is empty, we should replace it with a list element 3603 // and/or a list item element. 3604 if (HTMLEditUtils::IsEmptyNode(aHandlingDivOrParagraphElement, 3605 {EmptyCheckOption::TreatListItemAsVisible, 3606 EmptyCheckOption::TreatTableCellAsVisible})) { 3607 if (!aState.mCurrentListElement) { 3608 nsresult rv = CreateAndUpdateCurrentListElement( 3609 aHTMLEditor, EditorDOMPoint(&aHandlingDivOrParagraphElement), 3610 EmptyListItem::Create, aState, aEditingHost); 3611 if (NS_FAILED(rv)) { 3612 NS_WARNING( 3613 "AutoListElementCreator::CreateAndUpdateCurrentListElement(" 3614 "EmptyListItem::Create) failed"); 3615 return rv; 3616 } 3617 } else { 3618 Result<CreateElementResult, nsresult> createListItemElementResult = 3619 AppendListItemElement( 3620 aHTMLEditor, MOZ_KnownLive(*aState.mCurrentListElement), aState); 3621 if (MOZ_UNLIKELY(createListItemElementResult.isErr())) { 3622 NS_WARNING("AutoListElementCreator::AppendListItemElement() failed"); 3623 return createListItemElementResult.unwrapErr(); 3624 } 3625 CreateElementResult unwrappedResult = 3626 createListItemElementResult.unwrap(); 3627 unwrappedResult.IgnoreCaretPointSuggestion(); 3628 aState.mListOrListItemElementToPutCaret = unwrappedResult.UnwrapNewNode(); 3629 } 3630 nsresult rv = 3631 aHTMLEditor.DeleteNodeWithTransaction(aHandlingDivOrParagraphElement); 3632 if (NS_FAILED(rv)) { 3633 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed"); 3634 return rv; 3635 } 3636 3637 // We don't want new inline contents inserted into the new list item element 3638 // because we want to keep the line break at end of 3639 // aHandlingDivOrParagraphElement. 3640 aState.mPreviousListItemElement = nullptr; 3641 3642 return NS_OK; 3643 } 3644 3645 // If current node is a <div> element, replace it with its children and handle 3646 // them as same as topmost children in the range. 3647 AutoContentNodeArray arrayOfContentsInDiv; 3648 HTMLEditUtils::CollectChildren(aHandlingDivOrParagraphElement, 3649 arrayOfContentsInDiv, 0, 3650 {CollectChildrenOption::CollectListChildren, 3651 CollectChildrenOption::CollectTableChildren}); 3652 3653 Result<EditorDOMPoint, nsresult> unwrapDivElementResult = 3654 aHTMLEditor.RemoveContainerWithTransaction( 3655 aHandlingDivOrParagraphElement); 3656 if (MOZ_UNLIKELY(unwrapDivElementResult.isErr())) { 3657 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed"); 3658 return unwrapDivElementResult.unwrapErr(); 3659 } 3660 3661 for (const OwningNonNull<nsIContent>& content : arrayOfContentsInDiv) { 3662 // MOZ_KnownLive because of bug 1620312 3663 nsresult rv = HandleChildContent(aHTMLEditor, MOZ_KnownLive(content), 3664 aState, aEditingHost); 3665 if (NS_FAILED(rv)) { 3666 NS_WARNING("AutoListElementCreator::HandleChildContent() failed"); 3667 return rv; 3668 } 3669 } 3670 3671 // We don't want new inline contents inserted into the new list item element 3672 // because we want to keep the line break at end of 3673 // aHandlingDivOrParagraphElement. 3674 aState.mPreviousListItemElement = nullptr; 3675 3676 return NS_OK; 3677 } 3678 3679 nsresult HTMLEditor::AutoListElementCreator::CreateAndUpdateCurrentListElement( 3680 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToInsert, 3681 EmptyListItem aEmptyListItem, AutoHandlingState& aState, 3682 const Element& aEditingHost) const { 3683 MOZ_ASSERT(aPointToInsert.IsSetAndValid()); 3684 3685 aState.mPreviousListItemElement = nullptr; 3686 RefPtr<Element> newListItemElement; 3687 auto initializer = 3688 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868 3689 [&](HTMLEditor&, Element& aListElement, const EditorDOMPoint&) 3690 MOZ_CAN_RUN_SCRIPT_BOUNDARY { 3691 // If the replacing element has `dir` attribute, the new list 3692 // element should take it to correct its list marker position. 3693 if (aState.mReplacingBlockElement) { 3694 nsString dirValue; 3695 if (aState.mReplacingBlockElement->GetAttr(nsGkAtoms::dir, 3696 dirValue) && 3697 !dirValue.IsEmpty()) { 3698 // We don't need to use transaction to set `dir` attribute here 3699 // because the element will be stored with the `dir` attribute 3700 // in InsertNodeTransaction. Therefore, undo should work. 3701 IgnoredErrorResult ignoredError; 3702 aListElement.SetAttr(nsGkAtoms::dir, dirValue, ignoredError); 3703 NS_WARNING_ASSERTION( 3704 !ignoredError.Failed(), 3705 "Element::SetAttr(nsGkAtoms::dir) failed, but ignored"); 3706 } 3707 } 3708 if (aEmptyListItem == EmptyListItem::Create) { 3709 Result<CreateElementResult, nsresult> createNewListItemResult = 3710 AppendListItemElement(aHTMLEditor, aListElement, aState); 3711 if (MOZ_UNLIKELY(createNewListItemResult.isErr())) { 3712 NS_WARNING( 3713 "HTMLEditor::AppendNewElementToInsertingElement()" 3714 " failed"); 3715 return createNewListItemResult.unwrapErr(); 3716 } 3717 CreateElementResult unwrappedResult = 3718 createNewListItemResult.unwrap(); 3719 unwrappedResult.IgnoreCaretPointSuggestion(); 3720 newListItemElement = unwrappedResult.UnwrapNewNode(); 3721 } 3722 return NS_OK; 3723 }; 3724 Result<CreateElementResult, nsresult> createNewListElementResult = 3725 aHTMLEditor.InsertElementWithSplittingAncestorsWithTransaction( 3726 mListTagName, aPointToInsert, BRElementNextToSplitPoint::Keep, 3727 aEditingHost, initializer); 3728 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) { 3729 NS_WARNING( 3730 nsPrintfCString( 3731 "HTMLEditor::" 3732 "InsertElementWithSplittingAncestorsWithTransaction(%s) failed", 3733 nsAtomCString(&mListTagName).get()) 3734 .get()); 3735 return createNewListElementResult.propagateErr(); 3736 } 3737 CreateElementResult unwrappedCreateNewListElementResult = 3738 createNewListElementResult.unwrap(); 3739 unwrappedCreateNewListElementResult.IgnoreCaretPointSuggestion(); 3740 3741 MOZ_ASSERT(unwrappedCreateNewListElementResult.GetNewNode()); 3742 aState.mListOrListItemElementToPutCaret = 3743 newListItemElement ? newListItemElement.get() 3744 : unwrappedCreateNewListElementResult.GetNewNode(); 3745 aState.mCurrentListElement = 3746 unwrappedCreateNewListElementResult.UnwrapNewNode(); 3747 aState.mPreviousListItemElement = std::move(newListItemElement); 3748 return NS_OK; 3749 } 3750 3751 // static 3752 nsresult HTMLEditor::AutoListElementCreator::MaybeCloneAttributesToNewListItem( 3753 HTMLEditor& aHTMLEditor, Element& aListItemElement, 3754 AutoHandlingState& aState) { 3755 if (!aState.mReplacingBlockElement) { 3756 return NS_OK; 3757 } 3758 // If we're replacing a block element, the list items should have attributes 3759 // of the replacing element. However, we don't want to copy `dir` attribute 3760 // because it does not affect content in list item element and setting 3761 // opposite direction from the parent list causes the marker invisible. 3762 // Therefore, we don't want to take it. Finally, we don't need to use 3763 // transaction to copy the attributes here because the element will be stored 3764 // with the attributes in InsertNodeTransaction. Therefore, undo should work. 3765 nsresult rv = aHTMLEditor.CopyAttributes( 3766 WithTransaction::No, aListItemElement, 3767 MOZ_KnownLive(*aState.mReplacingBlockElement), 3768 aState.mMaybeCopiedReplacingBlockElementId 3769 ? HTMLEditor::CopyAllAttributesExceptIdAndDir 3770 : HTMLEditor::CopyAllAttributesExceptDir); 3771 aState.mMaybeCopiedReplacingBlockElementId = true; 3772 if (NS_WARN_IF(aHTMLEditor.Destroyed())) { 3773 return NS_ERROR_EDITOR_DESTROYED; 3774 } 3775 NS_WARNING_ASSERTION( 3776 NS_SUCCEEDED(rv), 3777 "HTMLEditor::CopyAttributes(WithTransaction::No) failed"); 3778 return rv; 3779 } 3780 3781 Result<CreateElementResult, nsresult> 3782 HTMLEditor::AutoListElementCreator::AppendListItemElement( 3783 HTMLEditor& aHTMLEditor, const Element& aListElement, 3784 AutoHandlingState& aState) const { 3785 const WithTransaction withTransaction = aListElement.IsInComposedDoc() 3786 ? WithTransaction::Yes 3787 : WithTransaction::No; 3788 Result<CreateElementResult, nsresult> createNewListItemResult = 3789 aHTMLEditor.CreateAndInsertElement( 3790 withTransaction, mListItemTagName, 3791 EditorDOMPoint::AtEndOf(aListElement), 3792 !aState.mReplacingBlockElement 3793 ? HTMLEditor::DoNothingForNewElement 3794 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868 3795 : [&aState](HTMLEditor& aHTMLEditor, Element& aListItemElement, 3796 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY { 3797 nsresult rv = 3798 AutoListElementCreator::MaybeCloneAttributesToNewListItem( 3799 aHTMLEditor, aListItemElement, aState); 3800 NS_WARNING_ASSERTION( 3801 NS_SUCCEEDED(rv), 3802 "AutoListElementCreator::" 3803 "MaybeCloneAttributesToNewListItem() failed"); 3804 return rv; 3805 }); 3806 NS_WARNING_ASSERTION(createNewListItemResult.isOk(), 3807 "HTMLEditor::CreateAndInsertElement() failed"); 3808 return createNewListItemResult; 3809 } 3810 3811 nsresult HTMLEditor::AutoListElementCreator::HandleChildInlineContent( 3812 HTMLEditor& aHTMLEditor, nsIContent& aHandlingInlineContent, 3813 AutoHandlingState& aState) const { 3814 MOZ_ASSERT(HTMLEditUtils::IsInlineContent( 3815 aHandlingInlineContent, BlockInlineCheck::UseHTMLDefaultStyle)); 3816 3817 // If we're currently handling contents of a list item and current node 3818 // is not a block element, move current node into the list item. 3819 if (!aState.mPreviousListItemElement) { 3820 nsresult rv = WrapContentIntoNewListItemElement( 3821 aHTMLEditor, aHandlingInlineContent, aState); 3822 NS_WARNING_ASSERTION( 3823 NS_SUCCEEDED(rv), 3824 "AutoListElementCreator::WrapContentIntoNewListItemElement() failed"); 3825 return rv; 3826 } 3827 3828 Result<MoveNodeResult, nsresult> moveInlineElementResult = 3829 aHTMLEditor.MoveNodeToEndWithTransaction( 3830 aHandlingInlineContent, 3831 MOZ_KnownLive(*aState.mPreviousListItemElement)); 3832 if (MOZ_UNLIKELY(moveInlineElementResult.isErr())) { 3833 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); 3834 return moveInlineElementResult.propagateErr(); 3835 } 3836 moveInlineElementResult.inspect().IgnoreCaretPointSuggestion(); 3837 return NS_OK; 3838 } 3839 3840 nsresult HTMLEditor::AutoListElementCreator::WrapContentIntoNewListItemElement( 3841 HTMLEditor& aHTMLEditor, nsIContent& aHandlingContent, 3842 AutoHandlingState& aState) const { 3843 // If current node is not a paragraph, wrap current node with new list 3844 // item element and move it into current list element. 3845 Result<CreateElementResult, nsresult> wrapContentInListItemElementResult = 3846 aHTMLEditor.InsertContainerWithTransaction( 3847 aHandlingContent, mListItemTagName, 3848 !aState.mReplacingBlockElement 3849 ? HTMLEditor::DoNothingForNewElement 3850 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868 3851 : [&aState](HTMLEditor& aHTMLEditor, Element& aListItemElement, 3852 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY { 3853 nsresult rv = 3854 AutoListElementCreator::MaybeCloneAttributesToNewListItem( 3855 aHTMLEditor, aListItemElement, aState); 3856 NS_WARNING_ASSERTION( 3857 NS_SUCCEEDED(rv), 3858 "AutoListElementCreator::" 3859 "MaybeCloneAttributesToNewListItem() failed"); 3860 return rv; 3861 }); 3862 if (MOZ_UNLIKELY(wrapContentInListItemElementResult.isErr())) { 3863 NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed"); 3864 return wrapContentInListItemElementResult.unwrapErr(); 3865 } 3866 CreateElementResult unwrappedWrapContentInListItemElementResult = 3867 wrapContentInListItemElementResult.unwrap(); 3868 unwrappedWrapContentInListItemElementResult.IgnoreCaretPointSuggestion(); 3869 MOZ_ASSERT(unwrappedWrapContentInListItemElementResult.GetNewNode()); 3870 3871 // MOZ_KnownLive(unwrappedWrapContentInListItemElementResult.GetNewNode()): 3872 // The result is grabbed by unwrappedWrapContentInListItemElementResult. 3873 Result<MoveNodeResult, nsresult> moveListItemElementResult = 3874 aHTMLEditor.MoveNodeToEndWithTransaction( 3875 MOZ_KnownLive( 3876 *unwrappedWrapContentInListItemElementResult.GetNewNode()), 3877 MOZ_KnownLive(*aState.mCurrentListElement)); 3878 if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) { 3879 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); 3880 return moveListItemElementResult.unwrapErr(); 3881 } 3882 moveListItemElementResult.inspect().IgnoreCaretPointSuggestion(); 3883 3884 // If current node is not a block element, new list item should have 3885 // following inline nodes too. 3886 if (HTMLEditUtils::IsInlineContent(aHandlingContent, 3887 BlockInlineCheck::UseHTMLDefaultStyle)) { 3888 aState.mPreviousListItemElement = 3889 unwrappedWrapContentInListItemElementResult.UnwrapNewNode(); 3890 } else { 3891 aState.mPreviousListItemElement = nullptr; 3892 } 3893 3894 // XXX Why don't we set `type` attribute here?? 3895 return NS_OK; 3896 } 3897 3898 nsresult HTMLEditor::AutoListElementCreator:: 3899 EnsureCollapsedRangeIsInListItemOrListElement( 3900 Element& aListItemOrListToPutCaret, 3901 AutoClonedRangeArray& aRanges) const { 3902 if (!aRanges.IsCollapsed() || aRanges.Ranges().IsEmpty()) { 3903 return NS_OK; 3904 } 3905 3906 const auto firstRangeStartPoint = 3907 aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>(); 3908 if (MOZ_UNLIKELY(!firstRangeStartPoint.IsSet())) { 3909 return NS_OK; 3910 } 3911 Result<EditorRawDOMPoint, nsresult> pointToPutCaretOrError = 3912 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside< 3913 EditorRawDOMPoint>(aListItemOrListToPutCaret, firstRangeStartPoint); 3914 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 3915 NS_WARNING("HTMLEditUtils::ComputePointToPutCaretInElementIfOutside()"); 3916 return pointToPutCaretOrError.unwrapErr(); 3917 } 3918 if (pointToPutCaretOrError.inspect().IsSet()) { 3919 nsresult rv = aRanges.Collapse(pointToPutCaretOrError.inspect()); 3920 if (NS_FAILED(rv)) { 3921 NS_WARNING("AutoClonedRangeArray::Collapse() failed"); 3922 return rv; 3923 } 3924 } 3925 return NS_OK; 3926 } 3927 3928 nsresult HTMLEditor::RemoveListAtSelectionAsSubAction( 3929 const Element& aEditingHost) { 3930 MOZ_ASSERT(IsEditActionDataAvailable()); 3931 3932 { 3933 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 3934 if (MOZ_UNLIKELY(result.isErr())) { 3935 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 3936 return result.unwrapErr(); 3937 } 3938 if (result.inspect().Canceled()) { 3939 return NS_OK; 3940 } 3941 } 3942 3943 AutoPlaceholderBatch treatAsOneTransaction( 3944 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 3945 IgnoredErrorResult error; 3946 AutoEditSubActionNotifier startToHandleEditSubAction( 3947 *this, EditSubAction::eRemoveList, nsIEditor::eNext, error); 3948 if (NS_WARN_IF(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 3949 return error.StealNSResult(); 3950 } 3951 NS_WARNING_ASSERTION( 3952 !error.Failed(), 3953 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 3954 3955 // XXX Why do we do this only when there is only one selection range? 3956 if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) { 3957 Result<EditorRawDOMRange, nsresult> extendedRange = 3958 GetRangeExtendedToHardLineEdgesForBlockEditAction( 3959 SelectionRef().GetRangeAt(0u), aEditingHost); 3960 if (MOZ_UNLIKELY(extendedRange.isErr())) { 3961 NS_WARNING( 3962 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() " 3963 "failed"); 3964 return extendedRange.unwrapErr(); 3965 } 3966 // Note that end point may be prior to start point. So, we 3967 // cannot use Selection::SetStartAndEndInLimit() here. 3968 error.SuppressException(); 3969 SelectionRef().SetBaseAndExtentInLimiter( 3970 extendedRange.inspect().StartRef().ToRawRangeBoundary(), 3971 extendedRange.inspect().EndRef().ToRawRangeBoundary(), error); 3972 if (NS_WARN_IF(Destroyed())) { 3973 return NS_ERROR_EDITOR_DESTROYED; 3974 } 3975 if (error.Failed()) { 3976 NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed"); 3977 return error.StealNSResult(); 3978 } 3979 } 3980 3981 AutoSelectionRestorer restoreSelectionLater(this); 3982 3983 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents; 3984 { 3985 // TODO: We don't need AutoTransactionsConserveSelection here in the normal 3986 // cases, but removing this may cause the behavior with the legacy 3987 // mutation event listeners. We should try to delete this in a bug. 3988 AutoTransactionsConserveSelection dontChangeMySelection(*this); 3989 3990 { 3991 AutoClonedSelectionRangeArray extendedSelectionRanges(SelectionRef()); 3992 extendedSelectionRanges.ExtendRangesToWrapLines( 3993 EditSubAction::eCreateOrChangeList, 3994 BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost); 3995 Result<EditorDOMPoint, nsresult> splitResult = 3996 extendedSelectionRanges 3997 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries( 3998 *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost); 3999 if (MOZ_UNLIKELY(splitResult.isErr())) { 4000 NS_WARNING( 4001 "AutoClonedRangeArray::" 4002 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() " 4003 "failed"); 4004 return splitResult.unwrapErr(); 4005 } 4006 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes( 4007 *this, arrayOfContents, EditSubAction::eCreateOrChangeList, 4008 AutoClonedRangeArray::CollectNonEditableNodes::No); 4009 if (NS_FAILED(rv)) { 4010 NS_WARNING( 4011 "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::" 4012 "eCreateOrChangeList, CollectNonEditableNodes::No) failed"); 4013 return rv; 4014 } 4015 } 4016 4017 const Result<EditorDOMPoint, nsresult> splitAtBRElementsResult = 4018 MaybeSplitElementsAtEveryBRElement(arrayOfContents, 4019 EditSubAction::eCreateOrChangeList); 4020 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) { 4021 NS_WARNING( 4022 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::" 4023 "eCreateOrChangeList) failed"); 4024 return splitAtBRElementsResult.inspectErr(); 4025 } 4026 } 4027 4028 // Remove all non-editable nodes. Leave them be. 4029 // XXX CollectEditTargetNodes() should return only editable contents when it's 4030 // called with CollectNonEditableNodes::No, but checking it here, looks 4031 // like just wasting the runtime cost. 4032 for (int32_t i = arrayOfContents.Length() - 1; i >= 0; i--) { 4033 const OwningNonNull<nsIContent>& content = arrayOfContents[i]; 4034 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) { 4035 arrayOfContents.RemoveElementAt(i); 4036 } 4037 } 4038 4039 // Only act on lists or list items in the array 4040 for (const OwningNonNull<nsIContent>& content : arrayOfContents) { 4041 // here's where we actually figure out what to do 4042 if (HTMLEditUtils::IsListItemElement(*content)) { 4043 // unlist this listitem 4044 nsresult rv = LiftUpListItemElement(MOZ_KnownLive(*content->AsElement()), 4045 LiftUpFromAllParentListElements::Yes); 4046 if (NS_FAILED(rv)) { 4047 NS_WARNING( 4048 "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:" 4049 ":Yes) failed"); 4050 return rv; 4051 } 4052 continue; 4053 } 4054 if (HTMLEditUtils::IsListElement(*content)) { 4055 // node is a list, move list items out 4056 nsresult rv = 4057 DestroyListStructureRecursively(MOZ_KnownLive(*content->AsElement())); 4058 if (NS_FAILED(rv)) { 4059 NS_WARNING("HTMLEditor::DestroyListStructureRecursively() failed"); 4060 return rv; 4061 } 4062 continue; 4063 } 4064 } 4065 return NS_OK; 4066 } 4067 4068 Result<RefPtr<Element>, nsresult> 4069 HTMLEditor::FormatBlockContainerWithTransaction( 4070 AutoClonedSelectionRangeArray& aSelectionRanges, 4071 const nsStaticAtom& aNewFormatTagName, FormatBlockMode aFormatBlockMode, 4072 const Element& aEditingHost) { 4073 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); 4074 4075 // XXX Why do we do this only when there is only one selection range? 4076 if (!aSelectionRanges.IsCollapsed() && 4077 aSelectionRanges.Ranges().Length() == 1u) { 4078 Result<EditorRawDOMRange, nsresult> extendedRange = 4079 GetRangeExtendedToHardLineEdgesForBlockEditAction( 4080 aSelectionRanges.FirstRangeRef(), aEditingHost); 4081 if (MOZ_UNLIKELY(extendedRange.isErr())) { 4082 NS_WARNING( 4083 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() " 4084 "failed"); 4085 return extendedRange.propagateErr(); 4086 } 4087 // Note that end point may be prior to start point. So, we 4088 // cannot use AutoClonedRangeArray::SetStartAndEnd() here. 4089 if (NS_FAILED(aSelectionRanges.SetBaseAndExtent( 4090 extendedRange.inspect().StartRef(), 4091 extendedRange.inspect().EndRef()))) { 4092 NS_WARNING("AutoClonedRangeArray::SetBaseAndExtent() failed"); 4093 return Err(NS_ERROR_FAILURE); 4094 } 4095 } 4096 4097 MOZ_ALWAYS_TRUE(aSelectionRanges.SaveAndTrackRanges(*this)); 4098 4099 // TODO: We don't need AutoTransactionsConserveSelection here in the normal 4100 // cases, but removing this may cause the behavior with the legacy 4101 // mutation event listeners. We should try to delete this in a bug. 4102 AutoTransactionsConserveSelection dontChangeMySelection(*this); 4103 4104 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents; 4105 aSelectionRanges.ExtendRangesToWrapLines( 4106 aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand 4107 ? EditSubAction::eFormatBlockForHTMLCommand 4108 : EditSubAction::eCreateOrRemoveBlock, 4109 BlockInlineCheck::UseComputedDisplayOutsideStyle, aEditingHost); 4110 Result<EditorDOMPoint, nsresult> splitResult = 4111 aSelectionRanges 4112 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries( 4113 *this, BlockInlineCheck::UseComputedDisplayOutsideStyle, 4114 aEditingHost); 4115 if (MOZ_UNLIKELY(splitResult.isErr())) { 4116 NS_WARNING( 4117 "AutoClonedRangeArray::" 4118 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() failed"); 4119 return splitResult.propagateErr(); 4120 } 4121 nsresult rv = aSelectionRanges.CollectEditTargetNodes( 4122 *this, arrayOfContents, 4123 aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand 4124 ? EditSubAction::eFormatBlockForHTMLCommand 4125 : EditSubAction::eCreateOrRemoveBlock, 4126 AutoClonedRangeArray::CollectNonEditableNodes::Yes); 4127 if (NS_FAILED(rv)) { 4128 NS_WARNING( 4129 "AutoClonedRangeArray::CollectEditTargetNodes(CollectNonEditableNodes::" 4130 "No) failed"); 4131 return Err(rv); 4132 } 4133 4134 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult = 4135 MaybeSplitElementsAtEveryBRElement( 4136 arrayOfContents, 4137 aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand 4138 ? EditSubAction::eFormatBlockForHTMLCommand 4139 : EditSubAction::eCreateOrRemoveBlock); 4140 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) { 4141 NS_WARNING("HTMLEditor::MaybeSplitElementsAtEveryBRElement() failed"); 4142 return splitAtBRElementsResult.propagateErr(); 4143 } 4144 4145 // If there is no visible and editable nodes in the edit targets, make an 4146 // empty block. 4147 // XXX Isn't this odd if there are only non-editable visible nodes? 4148 if (HTMLEditUtils::IsEmptyOneHardLine( 4149 arrayOfContents, BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 4150 if (NS_WARN_IF(aSelectionRanges.Ranges().IsEmpty())) { 4151 return Err(NS_ERROR_FAILURE); 4152 } 4153 4154 auto pointToInsertBlock = 4155 aSelectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>(); 4156 if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand && 4157 (&aNewFormatTagName == nsGkAtoms::normal || 4158 &aNewFormatTagName == nsGkAtoms::_empty)) { 4159 if (!pointToInsertBlock.IsInContentNode()) { 4160 NS_WARNING( 4161 "HTMLEditor::FormatBlockContainerWithTransaction() couldn't find " 4162 "block parent because container of the point is not content"); 4163 return Err(NS_ERROR_FAILURE); 4164 } 4165 // We are removing blocks (going to "body text") 4166 const RefPtr<Element> editableBlockElement = 4167 HTMLEditUtils::GetInclusiveAncestorElement( 4168 *pointToInsertBlock.ContainerAs<nsIContent>(), 4169 HTMLEditUtils::ClosestEditableBlockElement, 4170 BlockInlineCheck::UseComputedDisplayOutsideStyle); 4171 if (!editableBlockElement) { 4172 NS_WARNING( 4173 "HTMLEditor::FormatBlockContainerWithTransaction() couldn't find " 4174 "block parent"); 4175 return Err(NS_ERROR_FAILURE); 4176 } 4177 if (editableBlockElement->IsAnyOfHTMLElements( 4178 nsGkAtoms::dd, nsGkAtoms::dl, nsGkAtoms::dt) || 4179 !HTMLEditUtils::IsFormatElementForParagraphStateCommand( 4180 *editableBlockElement)) { 4181 return RefPtr<Element>(); 4182 } 4183 4184 // If the first editable node after selection is a br, consume it. 4185 // Otherwise it gets pushed into a following block after the split, 4186 // which is visually bad. 4187 if (nsCOMPtr<nsIContent> brContent = HTMLEditUtils::GetNextContent( 4188 pointToInsertBlock, {WalkTreeOption::IgnoreNonEditableNode}, 4189 BlockInlineCheck::UseComputedDisplayOutsideStyle, 4190 &aEditingHost)) { 4191 if (brContent && brContent->IsHTMLElement(nsGkAtoms::br)) { 4192 AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock); 4193 nsresult rv = DeleteNodeWithTransaction(*brContent); 4194 if (NS_FAILED(rv)) { 4195 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 4196 return Err(rv); 4197 } 4198 } 4199 } 4200 // Do the splits! 4201 Result<SplitNodeResult, nsresult> splitNodeResult = 4202 SplitNodeDeepWithTransaction( 4203 *editableBlockElement, pointToInsertBlock, 4204 SplitAtEdges::eDoNotCreateEmptyContainer); 4205 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 4206 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed"); 4207 return splitNodeResult.propagateErr(); 4208 } 4209 SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap(); 4210 unwrappedSplitNodeResult.IgnoreCaretPointSuggestion(); 4211 // Put a <br> element at the split point 4212 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 4213 InsertLineBreak( 4214 WithTransaction::Yes, LineBreakType::BRElement, 4215 unwrappedSplitNodeResult.AtSplitPoint<EditorDOMPoint>()); 4216 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 4217 NS_WARNING( 4218 "HTMLEditor::InsertLineBreak(WithTransaction::Yes " 4219 "LineBreakType::BRElement) failed"); 4220 return insertBRElementResultOrError.propagateErr(); 4221 } 4222 CreateLineBreakResult insertBRElementResult = 4223 insertBRElementResultOrError.unwrap(); 4224 MOZ_ASSERT(insertBRElementResult.Handled()); 4225 aSelectionRanges.ClearSavedRanges(); 4226 nsresult rv = 4227 aSelectionRanges.Collapse(insertBRElementResult.UnwrapCaretPoint()); 4228 if (NS_FAILED(rv)) { 4229 NS_WARNING("AutoClonedRangeArray::Collapse() failed"); 4230 return Err(rv); 4231 } 4232 return RefPtr<Element>(); 4233 } 4234 4235 // We are making a block. Consume a br, if needed. 4236 if (nsCOMPtr<nsIContent> maybeBRContent = HTMLEditUtils::GetNextContent( 4237 pointToInsertBlock, 4238 {WalkTreeOption::IgnoreNonEditableNode, 4239 WalkTreeOption::StopAtBlockBoundary}, 4240 BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)) { 4241 if (maybeBRContent->IsHTMLElement(nsGkAtoms::br)) { 4242 AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock); 4243 nsresult rv = DeleteNodeWithTransaction(*maybeBRContent); 4244 if (NS_FAILED(rv)) { 4245 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 4246 return Err(rv); 4247 } 4248 // We don't need to act on this node any more 4249 arrayOfContents.RemoveElement(maybeBRContent); 4250 } 4251 } 4252 // Make sure we can put a block here. 4253 Result<CreateElementResult, nsresult> createNewBlockElementResult = 4254 InsertElementWithSplittingAncestorsWithTransaction( 4255 aNewFormatTagName, pointToInsertBlock, 4256 BRElementNextToSplitPoint::Keep, aEditingHost); 4257 if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) { 4258 NS_WARNING( 4259 nsPrintfCString( 4260 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(" 4261 "%s) failed", 4262 nsAtomCString(&aNewFormatTagName).get()) 4263 .get()); 4264 return createNewBlockElementResult.propagateErr(); 4265 } 4266 CreateElementResult unwrappedCreateNewBlockElementResult = 4267 createNewBlockElementResult.unwrap(); 4268 unwrappedCreateNewBlockElementResult.IgnoreCaretPointSuggestion(); 4269 MOZ_ASSERT(unwrappedCreateNewBlockElementResult.GetNewNode()); 4270 4271 // Delete anything that was in the list of nodes 4272 while (!arrayOfContents.IsEmpty()) { 4273 OwningNonNull<nsIContent>& content = arrayOfContents[0]; 4274 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to 4275 // keep it alive. 4276 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content)); 4277 if (NS_FAILED(rv)) { 4278 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 4279 return Err(rv); 4280 } 4281 arrayOfContents.RemoveElementAt(0); 4282 } 4283 // Put selection in new block 4284 aSelectionRanges.ClearSavedRanges(); 4285 nsresult rv = aSelectionRanges.Collapse(EditorRawDOMPoint( 4286 unwrappedCreateNewBlockElementResult.GetNewNode(), 0u)); 4287 if (NS_FAILED(rv)) { 4288 NS_WARNING("AutoClonedRangeArray::Collapse() failed"); 4289 return Err(rv); 4290 } 4291 return unwrappedCreateNewBlockElementResult.UnwrapNewNode(); 4292 } 4293 4294 if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand) { 4295 // Okay, now go through all the nodes and make the right kind of blocks, or 4296 // whatever is appropriate. 4297 // Note: blockquote is handled a little differently. 4298 if (&aNewFormatTagName == nsGkAtoms::blockquote) { 4299 Result<CreateElementResult, nsresult> 4300 wrapContentsInBlockquoteElementsResult = 4301 WrapContentsInBlockquoteElementsWithTransaction(arrayOfContents, 4302 aEditingHost); 4303 if (MOZ_UNLIKELY(wrapContentsInBlockquoteElementsResult.isErr())) { 4304 NS_WARNING( 4305 "HTMLEditor::WrapContentsInBlockquoteElementsWithTransaction() " 4306 "failed"); 4307 return wrapContentsInBlockquoteElementsResult.propagateErr(); 4308 } 4309 wrapContentsInBlockquoteElementsResult.inspect() 4310 .IgnoreCaretPointSuggestion(); 4311 return wrapContentsInBlockquoteElementsResult.unwrap().UnwrapNewNode(); 4312 } 4313 if (&aNewFormatTagName == nsGkAtoms::normal || 4314 &aNewFormatTagName == nsGkAtoms::_empty) { 4315 Result<EditorDOMPoint, nsresult> removeBlockContainerElementsResult = 4316 RemoveBlockContainerElementsWithTransaction( 4317 arrayOfContents, FormatBlockMode::XULParagraphStateCommand, 4318 BlockInlineCheck::UseComputedDisplayOutsideStyle); 4319 if (MOZ_UNLIKELY(removeBlockContainerElementsResult.isErr())) { 4320 NS_WARNING( 4321 "HTMLEditor::RemoveBlockContainerElementsWithTransaction() failed"); 4322 return removeBlockContainerElementsResult.propagateErr(); 4323 } 4324 return RefPtr<Element>(); 4325 } 4326 } 4327 4328 Result<CreateElementResult, nsresult> wrapContentsInBlockElementResult = 4329 CreateOrChangeFormatContainerElement(arrayOfContents, aNewFormatTagName, 4330 aFormatBlockMode, aEditingHost); 4331 if (MOZ_UNLIKELY(wrapContentsInBlockElementResult.isErr())) { 4332 NS_WARNING("HTMLEditor::CreateOrChangeFormatContainerElement() failed"); 4333 return wrapContentsInBlockElementResult.propagateErr(); 4334 } 4335 wrapContentsInBlockElementResult.inspect().IgnoreCaretPointSuggestion(); 4336 return wrapContentsInBlockElementResult.unwrap().UnwrapNewNode(); 4337 } 4338 4339 Result<EditActionResult, nsresult> HTMLEditor::IndentAsSubAction( 4340 const Element& aEditingHost) { 4341 MOZ_ASSERT(IsEditActionDataAvailable()); 4342 4343 AutoPlaceholderBatch treatAsOneTransaction( 4344 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 4345 IgnoredErrorResult ignoredError; 4346 AutoEditSubActionNotifier startToHandleEditSubAction( 4347 *this, EditSubAction::eIndent, nsIEditor::eNext, ignoredError); 4348 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 4349 return Err(ignoredError.StealNSResult()); 4350 } 4351 NS_WARNING_ASSERTION( 4352 !ignoredError.Failed(), 4353 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 4354 4355 { 4356 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 4357 if (MOZ_UNLIKELY(result.isErr())) { 4358 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 4359 return result; 4360 } 4361 if (result.inspect().Canceled()) { 4362 return result; 4363 } 4364 } 4365 4366 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) { 4367 NS_WARNING("Some selection containers are not content node, but ignored"); 4368 return EditActionResult::IgnoredResult(); 4369 } 4370 4371 Result<EditActionResult, nsresult> result = 4372 HandleIndentAtSelection(aEditingHost); 4373 if (MOZ_UNLIKELY(result.isErr())) { 4374 NS_WARNING("HTMLEditor::HandleIndentAtSelection() failed"); 4375 return result; 4376 } 4377 if (result.inspect().Canceled()) { 4378 return result; 4379 } 4380 4381 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) { 4382 NS_WARNING("Mutation event listener might have changed selection"); 4383 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 4384 } 4385 4386 if (!SelectionRef().IsCollapsed()) { 4387 return result; 4388 } 4389 4390 // TODO: Investigate when we need to put a `<br>` element after indenting 4391 // ranges. Then, we could stop calling this here, or maybe we need to 4392 // do it while moving content nodes. 4393 const auto caretPosition = 4394 EditorBase::GetFirstSelectionStartPoint<EditorDOMPoint>(); 4395 Result<CreateLineBreakResult, nsresult> insertPaddingBRElementResultOrError = 4396 InsertPaddingBRElementIfInEmptyBlock(caretPosition, eNoStrip); 4397 if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { 4398 NS_WARNING( 4399 "HTMLEditor::InsertPaddingBRElementIfInEmptyBlock(eNoStrip) failed"); 4400 return insertPaddingBRElementResultOrError.propagateErr(); 4401 } 4402 nsresult rv = 4403 insertPaddingBRElementResultOrError.unwrap().SuggestCaretPointTo( 4404 *this, {SuggestCaret::OnlyIfHasSuggestion, 4405 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 4406 SuggestCaret::AndIgnoreTrivialError}); 4407 if (NS_FAILED(rv)) { 4408 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 4409 return Err(rv); 4410 } 4411 NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 4412 "CaretPoint::SuggestCaretPointTo() failed, but ignored"); 4413 return result; 4414 } 4415 4416 Result<EditorDOMPoint, nsresult> HTMLEditor::IndentListChildWithTransaction( 4417 RefPtr<Element>* aSubListElement, const EditorDOMPoint& aPointInListElement, 4418 nsIContent& aContentMovingToSubList, const Element& aEditingHost) { 4419 MOZ_ASSERT(aPointInListElement.IsInContentNode()); 4420 MOZ_ASSERT(HTMLEditUtils::IsListElement( 4421 *aPointInListElement.ContainerAs<nsIContent>())); 4422 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); 4423 4424 // some logic for putting list items into nested lists... 4425 4426 // If aContentMovingToSubList is followed by a sub-list element whose tag is 4427 // same as the parent list element's tag, we can move it to start of the 4428 // sub-list. 4429 if (nsIContent* const nextEditableSibling = HTMLEditUtils::GetNextSibling( 4430 aContentMovingToSubList, {WalkTreeOption::IgnoreWhiteSpaceOnlyText, 4431 WalkTreeOption::IgnoreNonEditableNode})) { 4432 if (HTMLEditUtils::IsListElement(*nextEditableSibling) && 4433 aPointInListElement.GetContainer()->NodeInfo()->NameAtom() == 4434 nextEditableSibling->NodeInfo()->NameAtom() && 4435 aPointInListElement.GetContainer()->NodeInfo()->NamespaceID() == 4436 nextEditableSibling->NodeInfo()->NamespaceID()) { 4437 Result<MoveNodeResult, nsresult> moveListElementResult = 4438 MoveNodeWithTransaction(aContentMovingToSubList, 4439 EditorDOMPoint(nextEditableSibling, 0u)); 4440 if (MOZ_UNLIKELY(moveListElementResult.isErr())) { 4441 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed"); 4442 return moveListElementResult.propagateErr(); 4443 } 4444 return moveListElementResult.unwrap().UnwrapCaretPoint(); 4445 } 4446 } 4447 4448 // If aContentMovingToSubList follows a sub-list element whose tag is same 4449 // as the parent list element's tag, we can move it to end of the sub-list. 4450 if (const nsCOMPtr<nsIContent> previousEditableSibling = 4451 HTMLEditUtils::GetPreviousSibling( 4452 aContentMovingToSubList, 4453 {WalkTreeOption::IgnoreWhiteSpaceOnlyText, 4454 WalkTreeOption::IgnoreNonEditableNode})) { 4455 if (HTMLEditUtils::IsListElement(*previousEditableSibling) && 4456 aPointInListElement.GetContainer()->NodeInfo()->NameAtom() == 4457 previousEditableSibling->NodeInfo()->NameAtom() && 4458 aPointInListElement.GetContainer()->NodeInfo()->NamespaceID() == 4459 previousEditableSibling->NodeInfo()->NamespaceID()) { 4460 Result<MoveNodeResult, nsresult> moveListElementResult = 4461 MoveNodeToEndWithTransaction(aContentMovingToSubList, 4462 *previousEditableSibling); 4463 if (MOZ_UNLIKELY(moveListElementResult.isErr())) { 4464 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); 4465 return moveListElementResult.propagateErr(); 4466 } 4467 return moveListElementResult.unwrap().UnwrapCaretPoint(); 4468 } 4469 } 4470 4471 // If aContentMovingToSubList does not follow aSubListElement, we need 4472 // to create new sub-list element. 4473 EditorDOMPoint pointToPutCaret; 4474 nsIContent* previousEditableSibling = 4475 *aSubListElement ? HTMLEditUtils::GetPreviousSibling( 4476 aContentMovingToSubList, 4477 {WalkTreeOption::IgnoreWhiteSpaceOnlyText, 4478 WalkTreeOption::IgnoreNonEditableNode}) 4479 : nullptr; 4480 if (!*aSubListElement || (previousEditableSibling && 4481 previousEditableSibling != *aSubListElement)) { 4482 nsAtom* containerName = 4483 aPointInListElement.GetContainer()->NodeInfo()->NameAtom(); 4484 // Create a new nested list of correct type. 4485 Result<CreateElementResult, nsresult> createNewListElementResult = 4486 InsertElementWithSplittingAncestorsWithTransaction( 4487 MOZ_KnownLive(*containerName), aPointInListElement, 4488 BRElementNextToSplitPoint::Keep, aEditingHost); 4489 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) { 4490 NS_WARNING( 4491 nsPrintfCString( 4492 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(" 4493 "%s) failed", 4494 nsAtomCString(containerName).get()) 4495 .get()); 4496 return createNewListElementResult.propagateErr(); 4497 } 4498 CreateElementResult unwrappedCreateNewListElementResult = 4499 createNewListElementResult.unwrap(); 4500 MOZ_ASSERT(unwrappedCreateNewListElementResult.GetNewNode()); 4501 pointToPutCaret = unwrappedCreateNewListElementResult.UnwrapCaretPoint(); 4502 *aSubListElement = unwrappedCreateNewListElementResult.UnwrapNewNode(); 4503 } 4504 4505 // Finally, we should move aContentMovingToSubList into aSubListElement. 4506 const RefPtr<Element> subListElement = *aSubListElement; 4507 Result<MoveNodeResult, nsresult> moveNodeResult = 4508 MoveNodeToEndWithTransaction(aContentMovingToSubList, *subListElement); 4509 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 4510 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); 4511 return moveNodeResult.propagateErr(); 4512 } 4513 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap(); 4514 if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) { 4515 pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint(); 4516 } 4517 return pointToPutCaret; 4518 } 4519 4520 Result<EditActionResult, nsresult> HTMLEditor::HandleIndentAtSelection( 4521 const Element& aEditingHost) { 4522 MOZ_ASSERT(IsEditActionDataAvailable()); 4523 MOZ_ASSERT(!IsSelectionRangeContainerNotContent()); 4524 4525 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor(); 4526 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 4527 return Err(NS_ERROR_EDITOR_DESTROYED); 4528 } 4529 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 4530 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " 4531 "failed, but ignored"); 4532 4533 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) { 4534 nsresult rv = EnsureCaretNotAfterInvisibleBRElement(aEditingHost); 4535 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 4536 return Err(NS_ERROR_EDITOR_DESTROYED); 4537 } 4538 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 4539 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " 4540 "failed, but ignored"); 4541 if (NS_SUCCEEDED(rv)) { 4542 nsresult rv = PrepareInlineStylesForCaret(); 4543 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 4544 return Err(NS_ERROR_EDITOR_DESTROYED); 4545 } 4546 NS_WARNING_ASSERTION( 4547 NS_SUCCEEDED(rv), 4548 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); 4549 } 4550 } 4551 4552 AutoClonedSelectionRangeArray selectionRanges(SelectionRef()); 4553 4554 if (MOZ_UNLIKELY(!selectionRanges.IsInContent())) { 4555 NS_WARNING("Mutation event listener might have changed the selection"); 4556 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 4557 } 4558 4559 if (IsCSSEnabled()) { 4560 nsresult rv = HandleCSSIndentAroundRanges(selectionRanges, aEditingHost); 4561 if (NS_FAILED(rv)) { 4562 NS_WARNING("HTMLEditor::HandleCSSIndentAroundRanges() failed"); 4563 return Err(rv); 4564 } 4565 } else { 4566 nsresult rv = HandleHTMLIndentAroundRanges(selectionRanges, aEditingHost); 4567 if (NS_FAILED(rv)) { 4568 NS_WARNING("HTMLEditor::HandleHTMLIndentAroundRanges() failed"); 4569 return Err(rv); 4570 } 4571 } 4572 rv = selectionRanges.ApplyTo(SelectionRef()); 4573 if (MOZ_UNLIKELY(Destroyed())) { 4574 NS_WARNING( 4575 "AutoClonedSelectionRangeArray::ApplyTo() caused destroying the " 4576 "editor"); 4577 return Err(NS_ERROR_EDITOR_DESTROYED); 4578 } 4579 if (NS_FAILED(rv)) { 4580 NS_WARNING("AutoClonedSelectionRangeArray::ApplyTo() failed"); 4581 return Err(rv); 4582 } 4583 return EditActionResult::HandledResult(); 4584 } 4585 4586 nsresult HTMLEditor::HandleCSSIndentAroundRanges( 4587 AutoClonedSelectionRangeArray& aRanges, const Element& aEditingHost) { 4588 MOZ_ASSERT(IsEditActionDataAvailable()); 4589 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); 4590 MOZ_ASSERT(!aRanges.Ranges().IsEmpty()); 4591 MOZ_ASSERT(aRanges.IsInContent()); 4592 4593 if (aRanges.Ranges().IsEmpty()) { 4594 NS_WARNING("There is no selection range"); 4595 return NS_ERROR_FAILURE; 4596 } 4597 4598 // XXX Why do we do this only when there is only one selection range? 4599 if (!aRanges.IsCollapsed() && aRanges.Ranges().Length() == 1u) { 4600 Result<EditorRawDOMRange, nsresult> extendedRange = 4601 GetRangeExtendedToHardLineEdgesForBlockEditAction( 4602 aRanges.FirstRangeRef(), aEditingHost); 4603 if (MOZ_UNLIKELY(extendedRange.isErr())) { 4604 NS_WARNING( 4605 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() " 4606 "failed"); 4607 return extendedRange.unwrapErr(); 4608 } 4609 // Note that end point may be prior to start point. So, we 4610 // cannot use SetStartAndEnd() here. 4611 nsresult rv = aRanges.SetBaseAndExtent(extendedRange.inspect().StartRef(), 4612 extendedRange.inspect().EndRef()); 4613 if (NS_FAILED(rv)) { 4614 NS_WARNING("AutoClonedRangeArray::SetBaseAndExtent() failed"); 4615 return rv; 4616 } 4617 } 4618 4619 if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(*this))) { 4620 return NS_ERROR_FAILURE; 4621 } 4622 4623 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents; 4624 4625 // short circuit: detect case of collapsed selection inside an <li>. 4626 // just sublist that <li>. This prevents bug 97797. 4627 4628 if (aRanges.IsCollapsed()) { 4629 const auto atCaret = aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>(); 4630 if (NS_WARN_IF(!atCaret.IsSet())) { 4631 return NS_ERROR_FAILURE; 4632 } 4633 MOZ_ASSERT(atCaret.IsInContentNode()); 4634 Element* const editableBlockElement = 4635 HTMLEditUtils::GetInclusiveAncestorElement( 4636 *atCaret.ContainerAs<nsIContent>(), 4637 HTMLEditUtils::ClosestEditableBlockElement, 4638 BlockInlineCheck::UseHTMLDefaultStyle); 4639 if (editableBlockElement && 4640 HTMLEditUtils::IsListItemElement(*editableBlockElement)) { 4641 arrayOfContents.AppendElement(*editableBlockElement); 4642 } 4643 } 4644 4645 EditorDOMPoint pointToPutCaret; 4646 if (arrayOfContents.IsEmpty()) { 4647 { 4648 AutoClonedSelectionRangeArray extendedRanges(aRanges); 4649 extendedRanges.ExtendRangesToWrapLines( 4650 EditSubAction::eIndent, BlockInlineCheck::UseHTMLDefaultStyle, 4651 aEditingHost); 4652 Result<EditorDOMPoint, nsresult> splitResult = 4653 extendedRanges 4654 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries( 4655 *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost); 4656 if (MOZ_UNLIKELY(splitResult.isErr())) { 4657 NS_WARNING( 4658 "AutoClonedRangeArray::" 4659 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() " 4660 "failed"); 4661 return splitResult.unwrapErr(); 4662 } 4663 if (splitResult.inspect().IsSet()) { 4664 pointToPutCaret = splitResult.unwrap(); 4665 } 4666 nsresult rv = extendedRanges.CollectEditTargetNodes( 4667 *this, arrayOfContents, EditSubAction::eIndent, 4668 AutoClonedRangeArray::CollectNonEditableNodes::Yes); 4669 if (NS_FAILED(rv)) { 4670 NS_WARNING( 4671 "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::" 4672 "eIndent, CollectNonEditableNodes::Yes) failed"); 4673 return rv; 4674 } 4675 } 4676 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult = 4677 MaybeSplitElementsAtEveryBRElement(arrayOfContents, 4678 EditSubAction::eIndent); 4679 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) { 4680 NS_WARNING( 4681 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::" 4682 "eIndent) failed"); 4683 return splitAtBRElementsResult.inspectErr(); 4684 } 4685 if (splitAtBRElementsResult.inspect().IsSet()) { 4686 pointToPutCaret = splitAtBRElementsResult.unwrap(); 4687 } 4688 } 4689 4690 // If there is no visible and editable nodes in the edit targets, make an 4691 // empty block. 4692 // XXX Isn't this odd if there are only non-editable visible nodes? 4693 if (HTMLEditUtils::IsEmptyOneHardLine( 4694 arrayOfContents, BlockInlineCheck::UseHTMLDefaultStyle)) { 4695 const EditorDOMPoint pointToInsertDivElement = 4696 pointToPutCaret.IsSet() 4697 ? std::move(pointToPutCaret) 4698 : aRanges.GetFirstRangeStartPoint<EditorDOMPoint>(); 4699 if (NS_WARN_IF(!pointToInsertDivElement.IsSet())) { 4700 return NS_ERROR_FAILURE; 4701 } 4702 4703 // make sure we can put a block here 4704 Result<CreateElementResult, nsresult> createNewDivElementResult = 4705 InsertElementWithSplittingAncestorsWithTransaction( 4706 *nsGkAtoms::div, pointToInsertDivElement, 4707 BRElementNextToSplitPoint::Keep, aEditingHost); 4708 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) { 4709 NS_WARNING( 4710 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(" 4711 "nsGkAtoms::div) failed"); 4712 return createNewDivElementResult.unwrapErr(); 4713 } 4714 CreateElementResult unwrappedCreateNewDivElementResult = 4715 createNewDivElementResult.unwrap(); 4716 // We'll collapse ranges below, so we don't need to touch the ranges here. 4717 unwrappedCreateNewDivElementResult.IgnoreCaretPointSuggestion(); 4718 const RefPtr<Element> newDivElement = 4719 unwrappedCreateNewDivElementResult.UnwrapNewNode(); 4720 MOZ_ASSERT(newDivElement); 4721 const Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 4722 ChangeMarginStart(*newDivElement, ChangeMargin::Increase, aEditingHost); 4723 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 4724 if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() == 4725 NS_ERROR_EDITOR_DESTROYED)) { 4726 return NS_ERROR_EDITOR_DESTROYED; 4727 } 4728 NS_WARNING( 4729 "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed, but " 4730 "ignored"); 4731 } 4732 // delete anything that was in the list of nodes 4733 // XXX We don't need to remove the nodes from the array for performance. 4734 for (const OwningNonNull<nsIContent>& content : arrayOfContents) { 4735 // MOZ_KnownLive(content) due to bug 1622253 4736 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(content)); 4737 if (NS_FAILED(rv)) { 4738 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 4739 return rv; 4740 } 4741 } 4742 aRanges.ClearSavedRanges(); 4743 nsresult rv = aRanges.Collapse(EditorDOMPoint(newDivElement, 0u)); 4744 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 4745 "AutoClonedRangeArray::Collapse() failed"); 4746 return rv; 4747 } 4748 4749 RefPtr<Element> latestNewBlockElement; 4750 auto RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside = 4751 [&]() -> nsresult { 4752 MOZ_ASSERT(aRanges.HasSavedRanges()); 4753 aRanges.RestoreFromSavedRanges(); 4754 4755 if (!latestNewBlockElement || !aRanges.IsCollapsed() || 4756 aRanges.Ranges().IsEmpty()) { 4757 return NS_OK; 4758 } 4759 4760 const auto firstRangeStartRawPoint = 4761 aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>(); 4762 if (MOZ_UNLIKELY(!firstRangeStartRawPoint.IsSet())) { 4763 return NS_OK; 4764 } 4765 Result<EditorRawDOMPoint, nsresult> pointInNewBlockElementOrError = 4766 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside< 4767 EditorRawDOMPoint>(*latestNewBlockElement, firstRangeStartRawPoint); 4768 if (MOZ_UNLIKELY(pointInNewBlockElementOrError.isErr())) { 4769 NS_WARNING( 4770 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, " 4771 "but ignored"); 4772 return NS_OK; 4773 } 4774 if (!pointInNewBlockElementOrError.inspect().IsSet()) { 4775 return NS_OK; 4776 } 4777 return aRanges.Collapse(pointInNewBlockElementOrError.unwrap()); 4778 }; 4779 4780 // Ok, now go through all the nodes and put them into sub-list element 4781 // elements and new <div> elements which have start margin. 4782 RefPtr<Element> subListElement, divElement; 4783 for (size_t i = 0; i < arrayOfContents.Length(); i++) { 4784 const OwningNonNull<nsIContent>& content = arrayOfContents[i]; 4785 4786 // Here's where we actually figure out what to do. 4787 EditorDOMPoint atContent(content); 4788 if (NS_WARN_IF(!atContent.IsInContentNode())) { 4789 continue; 4790 } 4791 4792 // Ignore all non-editable nodes. Leave them be. 4793 // XXX We ignore non-editable nodes here, but not so in the above block. 4794 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) { 4795 continue; 4796 } 4797 4798 if (HTMLEditUtils::IsListElement(*atContent.ContainerAs<nsIContent>())) { 4799 const RefPtr<Element> oldSubListElement = subListElement; 4800 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to 4801 // keep it alive. 4802 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 4803 IndentListChildWithTransaction(&subListElement, atContent, 4804 MOZ_KnownLive(content), aEditingHost); 4805 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 4806 NS_WARNING("HTMLEditor::IndentListChildWithTransaction() failed"); 4807 return pointToPutCaretOrError.unwrapErr(); 4808 } 4809 if (subListElement != oldSubListElement) { 4810 // New list element is created, so we should put caret into the new list 4811 // element. 4812 latestNewBlockElement = subListElement; 4813 } 4814 if (pointToPutCaretOrError.inspect().IsSet()) { 4815 pointToPutCaret = pointToPutCaretOrError.unwrap(); 4816 } 4817 continue; 4818 } 4819 4820 // Not a list item. 4821 4822 if (HTMLEditUtils::IsBlockElement(content, 4823 BlockInlineCheck::UseHTMLDefaultStyle)) { 4824 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 4825 ChangeMarginStart(MOZ_KnownLive(*content->AsElement()), 4826 ChangeMargin::Increase, aEditingHost); 4827 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 4828 if (MOZ_UNLIKELY(pointToPutCaretOrError.inspectErr() == 4829 NS_ERROR_EDITOR_DESTROYED)) { 4830 NS_WARNING( 4831 "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed"); 4832 return NS_ERROR_EDITOR_DESTROYED; 4833 } 4834 NS_WARNING( 4835 "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed, but " 4836 "ignored"); 4837 } else if (pointToPutCaretOrError.inspect().IsSet()) { 4838 pointToPutCaret = pointToPutCaretOrError.unwrap(); 4839 } 4840 divElement = nullptr; 4841 continue; 4842 } 4843 4844 if (!divElement) { 4845 // First, check that our element can contain a div. 4846 if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(), 4847 *nsGkAtoms::div)) { 4848 // XXX This is odd, why do we stop indenting remaining content nodes? 4849 // Perhaps, `continue` is better. 4850 nsresult rv = 4851 RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside(); 4852 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 4853 "RestoreSavedRangesAndCollapseInLatestBlockElement" 4854 "IfOutside() failed"); 4855 return rv; 4856 } 4857 4858 Result<CreateElementResult, nsresult> createNewDivElementResult = 4859 InsertElementWithSplittingAncestorsWithTransaction( 4860 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep, 4861 aEditingHost); 4862 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) { 4863 NS_WARNING( 4864 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(" 4865 "nsGkAtoms::div) failed"); 4866 return createNewDivElementResult.unwrapErr(); 4867 } 4868 CreateElementResult unwrappedCreateNewDivElementResult = 4869 createNewDivElementResult.unwrap(); 4870 pointToPutCaret = unwrappedCreateNewDivElementResult.UnwrapCaretPoint(); 4871 4872 MOZ_ASSERT(unwrappedCreateNewDivElementResult.GetNewNode()); 4873 divElement = unwrappedCreateNewDivElementResult.UnwrapNewNode(); 4874 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 4875 ChangeMarginStart(*divElement, ChangeMargin::Increase, aEditingHost); 4876 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 4877 if (MOZ_UNLIKELY(pointToPutCaretOrError.inspectErr() == 4878 NS_ERROR_EDITOR_DESTROYED)) { 4879 NS_WARNING( 4880 "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed"); 4881 return NS_ERROR_EDITOR_DESTROYED; 4882 } 4883 NS_WARNING( 4884 "HTMLEditor::ChangeMarginStart(ChangeMargin::Increase) failed, but " 4885 "ignored"); 4886 } else if (AllowsTransactionsToChangeSelection() && 4887 pointToPutCaretOrError.inspect().IsSet()) { 4888 pointToPutCaret = pointToPutCaretOrError.unwrap(); 4889 } 4890 4891 latestNewBlockElement = divElement; 4892 } 4893 4894 const auto IsMovableContentSibling = [&](const nsIContent& aContent) { 4895 return HTMLEditUtils::IsSimplyEditableNode(aContent) && 4896 !HTMLEditUtils::IsBlockElement( 4897 aContent, BlockInlineCheck::UseHTMLDefaultStyle); 4898 }; 4899 MOZ_ASSERT(IsMovableContentSibling(content)); 4900 const OwningNonNull<nsIContent> lastContent = [&]() { 4901 nsIContent* lastContent = content; 4902 for (; i + 1 < arrayOfContents.Length(); i++) { 4903 nsIContent* const nextContent = arrayOfContents[i + 1]; 4904 if (lastContent->GetNextSibling() != nextContent || 4905 !IsMovableContentSibling(*nextContent)) { 4906 break; 4907 } 4908 lastContent = nextContent; 4909 } 4910 return OwningNonNull<nsIContent>(*lastContent); 4911 }(); 4912 // Move the content into the <div> which has start margin. 4913 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to 4914 // keep it alive. 4915 Result<MoveNodeResult, nsresult> moveNodeResult = 4916 MoveSiblingsToEndWithTransaction(MOZ_KnownLive(content), lastContent, 4917 *divElement); 4918 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 4919 NS_WARNING("HTMLEditor::MoveSiblingsToEndWithTransaction() failed"); 4920 return moveNodeResult.unwrapErr(); 4921 } 4922 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap(); 4923 if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) { 4924 pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint(); 4925 } 4926 } 4927 4928 nsresult rv = RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside(); 4929 NS_WARNING_ASSERTION( 4930 NS_SUCCEEDED(rv), 4931 "RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside() failed"); 4932 return rv; 4933 } 4934 4935 nsresult HTMLEditor::HandleHTMLIndentAroundRanges( 4936 AutoClonedSelectionRangeArray& aRanges, const Element& aEditingHost) { 4937 MOZ_ASSERT(IsEditActionDataAvailable()); 4938 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); 4939 MOZ_ASSERT(!aRanges.Ranges().IsEmpty()); 4940 MOZ_ASSERT(aRanges.IsInContent()); 4941 4942 // XXX Why do we do this only when there is only one range? 4943 if (!aRanges.IsCollapsed() && aRanges.Ranges().Length() == 1u) { 4944 Result<EditorRawDOMRange, nsresult> extendedRange = 4945 GetRangeExtendedToHardLineEdgesForBlockEditAction( 4946 aRanges.FirstRangeRef(), aEditingHost); 4947 if (MOZ_UNLIKELY(extendedRange.isErr())) { 4948 NS_WARNING( 4949 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() " 4950 "failed"); 4951 return extendedRange.unwrapErr(); 4952 } 4953 // Note that end point may be prior to start point. So, we cannot use 4954 // SetStartAndEnd() here. 4955 nsresult rv = aRanges.SetBaseAndExtent(extendedRange.inspect().StartRef(), 4956 extendedRange.inspect().EndRef()); 4957 if (NS_FAILED(rv)) { 4958 NS_WARNING("AutoClonedRangeArray::SetBaseAndExtent() failed"); 4959 return rv; 4960 } 4961 } 4962 4963 if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(*this))) { 4964 return NS_ERROR_FAILURE; 4965 } 4966 4967 EditorDOMPoint pointToPutCaret; 4968 4969 // convert the selection ranges into "promoted" selection ranges: 4970 // this basically just expands the range to include the immediate 4971 // block parent, and then further expands to include any ancestors 4972 // whose children are all in the range 4973 4974 // use these ranges to construct a list of nodes to act on. 4975 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents; 4976 { 4977 AutoClonedSelectionRangeArray extendedRanges(aRanges); 4978 extendedRanges.ExtendRangesToWrapLines( 4979 EditSubAction::eIndent, BlockInlineCheck::UseHTMLDefaultStyle, 4980 aEditingHost); 4981 Result<EditorDOMPoint, nsresult> splitResult = 4982 extendedRanges 4983 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries( 4984 *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost); 4985 if (MOZ_UNLIKELY(splitResult.isErr())) { 4986 NS_WARNING( 4987 "AutoClonedRangeArray::" 4988 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() " 4989 "failed"); 4990 return splitResult.unwrapErr(); 4991 } 4992 if (splitResult.inspect().IsSet()) { 4993 pointToPutCaret = splitResult.unwrap(); 4994 } 4995 nsresult rv = extendedRanges.CollectEditTargetNodes( 4996 *this, arrayOfContents, EditSubAction::eIndent, 4997 AutoClonedRangeArray::CollectNonEditableNodes::Yes); 4998 if (NS_FAILED(rv)) { 4999 NS_WARNING( 5000 "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::eIndent," 5001 " CollectNonEditableNodes::Yes) failed"); 5002 return rv; 5003 } 5004 } 5005 5006 // FIXME: Split ancestors when we consider to indent the range. 5007 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult = 5008 MaybeSplitElementsAtEveryBRElement(arrayOfContents, 5009 EditSubAction::eIndent); 5010 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) { 5011 NS_WARNING( 5012 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::eIndent)" 5013 " failed"); 5014 return splitAtBRElementsResult.inspectErr(); 5015 } 5016 if (splitAtBRElementsResult.inspect().IsSet()) { 5017 pointToPutCaret = splitAtBRElementsResult.unwrap(); 5018 } 5019 5020 // If there is no visible and editable nodes in the edit targets, make an 5021 // empty block. 5022 // XXX Isn't this odd if there are only non-editable visible nodes? 5023 if (HTMLEditUtils::IsEmptyOneHardLine( 5024 arrayOfContents, BlockInlineCheck::UseHTMLDefaultStyle)) { 5025 const EditorDOMPoint pointToInsertBlockquoteElement = 5026 pointToPutCaret.IsSet() 5027 ? std::move(pointToPutCaret) 5028 : EditorBase::GetFirstSelectionStartPoint<EditorDOMPoint>(); 5029 if (NS_WARN_IF(!pointToInsertBlockquoteElement.IsSet())) { 5030 return NS_ERROR_FAILURE; 5031 } 5032 5033 // If there is no element which can have <blockquote>, abort. 5034 if (NS_WARN_IF(!HTMLEditUtils::GetInsertionPointInInclusiveAncestor( 5035 *nsGkAtoms::blockquote, pointToInsertBlockquoteElement, 5036 &aEditingHost) 5037 .IsSet())) { 5038 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE; 5039 } 5040 5041 // Make sure we can put a block here. 5042 // XXX Unfortunately, this calls 5043 // MaybeSplitAncestorsForInsertWithTransaction() then, 5044 // HTMLEditUtils::GetInsertionPointInInclusiveAncestor() is called again. 5045 Result<CreateElementResult, nsresult> createNewBlockquoteElementResult = 5046 InsertElementWithSplittingAncestorsWithTransaction( 5047 *nsGkAtoms::blockquote, pointToInsertBlockquoteElement, 5048 BRElementNextToSplitPoint::Keep, aEditingHost); 5049 if (MOZ_UNLIKELY(createNewBlockquoteElementResult.isErr())) { 5050 NS_WARNING( 5051 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(" 5052 "nsGkAtoms::blockquote) failed"); 5053 return createNewBlockquoteElementResult.unwrapErr(); 5054 } 5055 CreateElementResult unwrappedCreateNewBlockquoteElementResult = 5056 createNewBlockquoteElementResult.unwrap(); 5057 unwrappedCreateNewBlockquoteElementResult.IgnoreCaretPointSuggestion(); 5058 RefPtr<Element> newBlockquoteElement = 5059 unwrappedCreateNewBlockquoteElementResult.UnwrapNewNode(); 5060 MOZ_ASSERT(newBlockquoteElement); 5061 // delete anything that was in the list of nodes 5062 // XXX We don't need to remove the nodes from the array for performance. 5063 for (const OwningNonNull<nsIContent>& content : arrayOfContents) { 5064 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to 5065 // keep it alive. 5066 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content)); 5067 if (NS_FAILED(rv)) { 5068 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 5069 return rv; 5070 } 5071 } 5072 aRanges.ClearSavedRanges(); 5073 nsresult rv = aRanges.Collapse(EditorRawDOMPoint(newBlockquoteElement, 0u)); 5074 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 5075 "EditorBase::CollapseSelectionToStartOf() failed"); 5076 return rv; 5077 } 5078 5079 RefPtr<Element> latestNewBlockElement; 5080 auto RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside = 5081 [&]() -> nsresult { 5082 MOZ_ASSERT(aRanges.HasSavedRanges()); 5083 aRanges.RestoreFromSavedRanges(); 5084 5085 if (!latestNewBlockElement || !aRanges.IsCollapsed() || 5086 aRanges.Ranges().IsEmpty()) { 5087 return NS_OK; 5088 } 5089 5090 const auto firstRangeStartRawPoint = 5091 aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>(); 5092 if (MOZ_UNLIKELY(!firstRangeStartRawPoint.IsSet())) { 5093 return NS_OK; 5094 } 5095 Result<EditorRawDOMPoint, nsresult> pointInNewBlockElementOrError = 5096 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside< 5097 EditorRawDOMPoint>(*latestNewBlockElement, firstRangeStartRawPoint); 5098 if (MOZ_UNLIKELY(pointInNewBlockElementOrError.isErr())) { 5099 NS_WARNING( 5100 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, " 5101 "but ignored"); 5102 return NS_OK; 5103 } 5104 if (!pointInNewBlockElementOrError.inspect().IsSet()) { 5105 return NS_OK; 5106 } 5107 return aRanges.Collapse(pointInNewBlockElementOrError.unwrap()); 5108 }; 5109 5110 // Ok, now go through all the nodes and put them in a blockquote, 5111 // or whatever is appropriate. Wohoo! 5112 RefPtr<Element> subListElement, blockquoteElement, indentedListItemElement; 5113 for (size_t i = 0; i < arrayOfContents.Length(); i++) { 5114 const OwningNonNull<nsIContent>& content = arrayOfContents[i]; 5115 5116 // Here's where we actually figure out what to do. 5117 EditorDOMPoint atContent(content); 5118 if (NS_WARN_IF(!atContent.IsInContentNode())) { 5119 continue; 5120 } 5121 5122 const auto IsNotHandlableContent = [](const nsIContent& aContent) { 5123 // Ignore all non-editable nodes. Leave them be. 5124 // XXX We ignore non-editable nodes here, but not so in the above 5125 // block. 5126 return !EditorUtils::IsEditableContent(aContent, EditorType::HTML) || 5127 !HTMLEditUtils::IsRemovableNode(aContent); 5128 }; 5129 5130 const auto IsMovableContentSibling = [&](const nsIContent& aContent) { 5131 return !IsNotHandlableContent(aContent) && 5132 !HTMLEditUtils::IsListItemElement(aContent); 5133 }; 5134 5135 if (IsNotHandlableContent(content)) { 5136 continue; 5137 } 5138 5139 // If the content has been moved to different place, ignore it. 5140 if (!content->IsInclusiveDescendantOf(&aEditingHost)) { 5141 continue; 5142 } 5143 5144 if (HTMLEditUtils::IsListElement(*atContent.ContainerAs<nsIContent>())) { 5145 const RefPtr<Element> oldSubListElement = subListElement; 5146 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to 5147 // keep it alive. 5148 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 5149 IndentListChildWithTransaction(&subListElement, atContent, 5150 MOZ_KnownLive(content), aEditingHost); 5151 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 5152 NS_WARNING("HTMLEditor::IndentListChildWithTransaction() failed"); 5153 return pointToPutCaretOrError.unwrapErr(); 5154 } 5155 if (oldSubListElement != subListElement) { 5156 // New list element is created, so we should put caret into the new list 5157 // element. 5158 latestNewBlockElement = subListElement; 5159 } 5160 if (pointToPutCaretOrError.inspect().IsSet()) { 5161 pointToPutCaret = pointToPutCaretOrError.unwrap(); 5162 } 5163 blockquoteElement = nullptr; 5164 continue; 5165 } 5166 5167 // Not a list item, use blockquote? 5168 5169 // if we are inside a list item, we don't want to blockquote, we want 5170 // to sublist the list item. We may have several nodes listed in the 5171 // array of nodes to act on, that are in the same list item. Since 5172 // we only want to indent that li once, we must keep track of the most 5173 // recent indented list item, and not indent it if we find another node 5174 // to act on that is still inside the same li. 5175 if (RefPtr<Element> listItem = 5176 HTMLEditUtils::GetClosestInclusiveAncestorListItemElement( 5177 content, &aEditingHost)) { 5178 if (indentedListItemElement == listItem) { 5179 // already indented this list item 5180 continue; 5181 } 5182 // check to see if subListElement is still appropriate. Which it is if 5183 // content is still right after it in the same list. 5184 nsIContent* previousEditableSibling = 5185 subListElement 5186 ? HTMLEditUtils::GetPreviousSibling( 5187 *listItem, {WalkTreeOption::IgnoreNonEditableNode}) 5188 : nullptr; 5189 if (!subListElement || (previousEditableSibling && 5190 previousEditableSibling != subListElement)) { 5191 EditorDOMPoint atListItem(listItem); 5192 if (NS_WARN_IF(!listItem)) { 5193 return NS_ERROR_FAILURE; 5194 } 5195 nsAtom* containerName = 5196 atListItem.GetContainer()->NodeInfo()->NameAtom(); 5197 // Create a new nested list of correct type. 5198 Result<CreateElementResult, nsresult> createNewListElementResult = 5199 InsertElementWithSplittingAncestorsWithTransaction( 5200 MOZ_KnownLive(*containerName), atListItem, 5201 BRElementNextToSplitPoint::Keep, aEditingHost); 5202 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) { 5203 NS_WARNING(nsPrintfCString("HTMLEditor::" 5204 "InsertElementWithSplittingAncestorsWithTr" 5205 "ansaction(%s) failed", 5206 nsAtomCString(containerName).get()) 5207 .get()); 5208 return createNewListElementResult.unwrapErr(); 5209 } 5210 CreateElementResult unwrappedCreateNewListElementResult = 5211 createNewListElementResult.unwrap(); 5212 if (unwrappedCreateNewListElementResult.HasCaretPointSuggestion()) { 5213 pointToPutCaret = 5214 unwrappedCreateNewListElementResult.UnwrapCaretPoint(); 5215 } 5216 MOZ_ASSERT(unwrappedCreateNewListElementResult.GetNewNode()); 5217 subListElement = unwrappedCreateNewListElementResult.UnwrapNewNode(); 5218 } 5219 5220 Result<MoveNodeResult, nsresult> moveListItemElementResult = 5221 MoveNodeToEndWithTransaction(*listItem, *subListElement); 5222 if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) { 5223 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); 5224 return moveListItemElementResult.unwrapErr(); 5225 } 5226 MoveNodeResult unwrappedMoveListItemElementResult = 5227 moveListItemElementResult.unwrap(); 5228 if (unwrappedMoveListItemElementResult.HasCaretPointSuggestion()) { 5229 pointToPutCaret = unwrappedMoveListItemElementResult.UnwrapCaretPoint(); 5230 } 5231 5232 // Remember the list item element which we indented now for ignoring its 5233 // children to avoid using <blockquote> in it. 5234 indentedListItemElement = std::move(listItem); 5235 5236 continue; 5237 } 5238 5239 // need to make a blockquote to put things in if we haven't already, 5240 // or if this node doesn't go in blockquote we used earlier. 5241 // One reason it might not go in prio blockquote is if we are now 5242 // in a different table cell. 5243 if (blockquoteElement && 5244 HTMLEditUtils::GetInclusiveAncestorAnyTableElement( 5245 *blockquoteElement) != 5246 HTMLEditUtils::GetInclusiveAncestorAnyTableElement(content)) { 5247 blockquoteElement = nullptr; 5248 } 5249 5250 if (!blockquoteElement) { 5251 // First, check that our element can contain a blockquote. 5252 if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(), 5253 *nsGkAtoms::blockquote)) { 5254 // XXX This is odd, why do we stop indenting remaining content nodes? 5255 // Perhaps, `continue` is better. 5256 nsresult rv = 5257 RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside(); 5258 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 5259 "RestoreSavedRangesAndCollapseInLatestBlockElement" 5260 "IfOutside() failed"); 5261 return rv; 5262 } 5263 5264 Result<CreateElementResult, nsresult> createNewBlockquoteElementResult = 5265 InsertElementWithSplittingAncestorsWithTransaction( 5266 *nsGkAtoms::blockquote, atContent, 5267 BRElementNextToSplitPoint::Keep, aEditingHost); 5268 if (MOZ_UNLIKELY(createNewBlockquoteElementResult.isErr())) { 5269 NS_WARNING( 5270 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(" 5271 "nsGkAtoms::blockquote) failed"); 5272 return createNewBlockquoteElementResult.unwrapErr(); 5273 } 5274 CreateElementResult unwrappedCreateNewBlockquoteElementResult = 5275 createNewBlockquoteElementResult.unwrap(); 5276 if (unwrappedCreateNewBlockquoteElementResult.HasCaretPointSuggestion()) { 5277 pointToPutCaret = 5278 unwrappedCreateNewBlockquoteElementResult.UnwrapCaretPoint(); 5279 } 5280 5281 MOZ_ASSERT(unwrappedCreateNewBlockquoteElementResult.GetNewNode()); 5282 blockquoteElement = 5283 unwrappedCreateNewBlockquoteElementResult.UnwrapNewNode(); 5284 latestNewBlockElement = blockquoteElement; 5285 } 5286 5287 MOZ_ASSERT(IsMovableContentSibling(content)); 5288 const OwningNonNull<nsIContent> lastContent = [&]() { 5289 nsIContent* lastContent = content; 5290 for (; i + 1 < arrayOfContents.Length(); i++) { 5291 const OwningNonNull<nsIContent>& nextContent = arrayOfContents[i + 1]; 5292 if (lastContent->GetNextSibling() != nextContent || 5293 !IsMovableContentSibling(nextContent)) { 5294 break; 5295 } 5296 lastContent = nextContent; 5297 } 5298 return OwningNonNull<nsIContent>(*lastContent); 5299 }(); 5300 // tuck the node into the end of the active blockquote 5301 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to 5302 // keep it alive. 5303 Result<MoveNodeResult, nsresult> moveNodeResult = 5304 MoveSiblingsToEndWithTransaction(MOZ_KnownLive(content), lastContent, 5305 *blockquoteElement); 5306 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 5307 NS_WARNING("HTMLEditor::MoveSiblingsToEndWithTransaction() failed"); 5308 return moveNodeResult.unwrapErr(); 5309 } 5310 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap(); 5311 if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) { 5312 pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint(); 5313 } 5314 subListElement = nullptr; 5315 } 5316 5317 nsresult rv = RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside(); 5318 NS_WARNING_ASSERTION( 5319 NS_SUCCEEDED(rv), 5320 "RestoreSavedRangesAndCollapseInLatestBlockElementIfOutside() failed"); 5321 return rv; 5322 } 5323 5324 Result<EditActionResult, nsresult> HTMLEditor::OutdentAsSubAction( 5325 const Element& aEditingHost) { 5326 MOZ_ASSERT(IsEditActionDataAvailable()); 5327 5328 AutoPlaceholderBatch treatAsOneTransaction( 5329 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 5330 IgnoredErrorResult ignoredError; 5331 AutoEditSubActionNotifier startToHandleEditSubAction( 5332 *this, EditSubAction::eOutdent, nsIEditor::eNext, ignoredError); 5333 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 5334 return Err(ignoredError.StealNSResult()); 5335 } 5336 NS_WARNING_ASSERTION( 5337 !ignoredError.Failed(), 5338 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 5339 5340 { 5341 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 5342 if (MOZ_UNLIKELY(result.isErr())) { 5343 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 5344 return result; 5345 } 5346 if (result.inspect().Canceled()) { 5347 return result; 5348 } 5349 } 5350 5351 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) { 5352 NS_WARNING("Some selection containers are not content node, but ignored"); 5353 return EditActionResult::IgnoredResult(); 5354 } 5355 5356 Result<EditActionResult, nsresult> result = 5357 HandleOutdentAtSelection(aEditingHost); 5358 if (MOZ_UNLIKELY(result.isErr())) { 5359 NS_WARNING("HTMLEditor::HandleOutdentAtSelection() failed"); 5360 return result; 5361 } 5362 if (result.inspect().Canceled()) { 5363 return result; 5364 } 5365 5366 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) { 5367 NS_WARNING("Mutation event listener might have changed the selection"); 5368 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 5369 } 5370 5371 if (!SelectionRef().IsCollapsed()) { 5372 return result; 5373 } 5374 5375 const auto caretPosition = 5376 EditorBase::GetFirstSelectionStartPoint<EditorDOMPoint>(); 5377 Result<CreateLineBreakResult, nsresult> insertPaddingBRElementResultOrError = 5378 InsertPaddingBRElementIfInEmptyBlock(caretPosition, eNoStrip); 5379 if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { 5380 NS_WARNING( 5381 "HTMLEditor::InsertPaddingBRElementIfInEmptyBlock(eNoStrip) failed"); 5382 return insertPaddingBRElementResultOrError.propagateErr(); 5383 } 5384 nsresult rv = 5385 insertPaddingBRElementResultOrError.unwrap().SuggestCaretPointTo( 5386 *this, {SuggestCaret::OnlyIfHasSuggestion, 5387 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 5388 SuggestCaret::AndIgnoreTrivialError}); 5389 if (NS_FAILED(rv)) { 5390 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 5391 return Err(rv); 5392 } 5393 NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 5394 "CaretPoint::SuggestCaretPointTo() failed, but ignored"); 5395 return result; 5396 } 5397 5398 Result<EditActionResult, nsresult> HTMLEditor::HandleOutdentAtSelection( 5399 const Element& aEditingHost) { 5400 MOZ_ASSERT(IsEditActionDataAvailable()); 5401 MOZ_ASSERT(!IsSelectionRangeContainerNotContent()); 5402 5403 // XXX Why do we do this only when there is only one selection range? 5404 if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) { 5405 Result<EditorRawDOMRange, nsresult> extendedRange = 5406 GetRangeExtendedToHardLineEdgesForBlockEditAction( 5407 SelectionRef().GetRangeAt(0u), aEditingHost); 5408 if (MOZ_UNLIKELY(extendedRange.isErr())) { 5409 NS_WARNING( 5410 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() " 5411 "failed"); 5412 return extendedRange.propagateErr(); 5413 } 5414 // Note that end point may be prior to start point. So, we 5415 // cannot use Selection::SetStartAndEndInLimit() here. 5416 IgnoredErrorResult error; 5417 SelectionRef().SetBaseAndExtentInLimiter( 5418 extendedRange.inspect().StartRef().ToRawRangeBoundary(), 5419 extendedRange.inspect().EndRef().ToRawRangeBoundary(), error); 5420 if (NS_WARN_IF(Destroyed())) { 5421 return Err(NS_ERROR_EDITOR_DESTROYED); 5422 } 5423 if (MOZ_UNLIKELY(error.Failed())) { 5424 NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed"); 5425 return Err(error.StealNSResult()); 5426 } 5427 } 5428 5429 // HandleOutdentAtSelectionInternal() creates AutoSelectionRestorer. 5430 // Therefore, even if it returns NS_OK, the editor might have been destroyed 5431 // at restoring Selection. 5432 Result<SplitRangeOffFromNodeResult, nsresult> outdentResult = 5433 HandleOutdentAtSelectionInternal(aEditingHost); 5434 MOZ_ASSERT_IF(outdentResult.isOk(), 5435 !outdentResult.inspect().HasCaretPointSuggestion()); 5436 if (NS_WARN_IF(Destroyed())) { 5437 return Err(NS_ERROR_EDITOR_DESTROYED); 5438 } 5439 if (MOZ_UNLIKELY(outdentResult.isErr())) { 5440 NS_WARNING("HTMLEditor::HandleOutdentAtSelectionInternal() failed"); 5441 return outdentResult.propagateErr(); 5442 } 5443 SplitRangeOffFromNodeResult unwrappedOutdentResult = outdentResult.unwrap(); 5444 5445 // Make sure selection didn't stick to last piece of content in old bq (only 5446 // a problem for collapsed selections) 5447 if (!unwrappedOutdentResult.GetLeftContent() && 5448 !unwrappedOutdentResult.GetRightContent()) { 5449 return EditActionResult::HandledResult(); 5450 } 5451 5452 if (!SelectionRef().IsCollapsed()) { 5453 return EditActionResult::HandledResult(); 5454 } 5455 5456 // Push selection past end of left element of last split indented element. 5457 if (unwrappedOutdentResult.GetLeftContent()) { 5458 const nsRange* firstRange = SelectionRef().GetRangeAt(0); 5459 if (NS_WARN_IF(!firstRange)) { 5460 return EditActionResult::HandledResult(); 5461 } 5462 const RangeBoundary& atStartOfSelection = firstRange->StartRef(); 5463 if (NS_WARN_IF(!atStartOfSelection.IsSet())) { 5464 return Err(NS_ERROR_FAILURE); 5465 } 5466 if (atStartOfSelection.GetContainer() == 5467 unwrappedOutdentResult.GetLeftContent() || 5468 EditorUtils::IsDescendantOf(*atStartOfSelection.GetContainer(), 5469 *unwrappedOutdentResult.GetLeftContent())) { 5470 // Selection is inside the left node - push it past it. 5471 EditorRawDOMPoint afterRememberedLeftBQ( 5472 EditorRawDOMPoint::After(*unwrappedOutdentResult.GetLeftContent())); 5473 NS_WARNING_ASSERTION( 5474 afterRememberedLeftBQ.IsSet(), 5475 "Failed to set after remembered left blockquote element"); 5476 nsresult rv = CollapseSelectionTo(afterRememberedLeftBQ); 5477 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 5478 return Err(NS_ERROR_EDITOR_DESTROYED); 5479 } 5480 NS_WARNING_ASSERTION( 5481 NS_SUCCEEDED(rv), 5482 "EditorBase::CollapseSelectionTo() failed, but ignored"); 5483 } 5484 } 5485 // And pull selection before beginning of right element of last split 5486 // indented element. 5487 if (unwrappedOutdentResult.GetRightContent()) { 5488 const nsRange* firstRange = SelectionRef().GetRangeAt(0); 5489 if (NS_WARN_IF(!firstRange)) { 5490 return EditActionResult::HandledResult(); 5491 } 5492 const RangeBoundary& atStartOfSelection = firstRange->StartRef(); 5493 if (NS_WARN_IF(!atStartOfSelection.IsSet())) { 5494 return Err(NS_ERROR_FAILURE); 5495 } 5496 if (atStartOfSelection.GetContainer() == 5497 unwrappedOutdentResult.GetRightContent() || 5498 EditorUtils::IsDescendantOf( 5499 *atStartOfSelection.GetContainer(), 5500 *unwrappedOutdentResult.GetRightContent())) { 5501 // Selection is inside the right element - push it before it. 5502 EditorRawDOMPoint atRememberedRightBQ( 5503 unwrappedOutdentResult.GetRightContent()); 5504 nsresult rv = CollapseSelectionTo(atRememberedRightBQ); 5505 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 5506 return Err(NS_ERROR_EDITOR_DESTROYED); 5507 } 5508 NS_WARNING_ASSERTION( 5509 NS_SUCCEEDED(rv), 5510 "EditorBase::CollapseSelectionTo() failed, but ignored"); 5511 } 5512 } 5513 return EditActionResult::HandledResult(); 5514 } 5515 5516 Result<SplitRangeOffFromNodeResult, nsresult> 5517 HTMLEditor::HandleOutdentAtSelectionInternal(const Element& aEditingHost) { 5518 MOZ_ASSERT(IsEditActionDataAvailable()); 5519 5520 AutoSelectionRestorer restoreSelectionLater(this); 5521 5522 bool useCSS = IsCSSEnabled(); 5523 5524 // Convert the selection ranges into "promoted" selection ranges: this 5525 // basically just expands the range to include the immediate block parent, 5526 // and then further expands to include any ancestors whose children are all 5527 // in the range 5528 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents; 5529 { 5530 AutoClonedSelectionRangeArray extendedSelectionRanges(SelectionRef()); 5531 extendedSelectionRanges.ExtendRangesToWrapLines( 5532 EditSubAction::eOutdent, BlockInlineCheck::UseHTMLDefaultStyle, 5533 aEditingHost); 5534 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes( 5535 *this, arrayOfContents, EditSubAction::eOutdent, 5536 AutoClonedRangeArray::CollectNonEditableNodes::Yes); 5537 if (NS_FAILED(rv)) { 5538 NS_WARNING( 5539 "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::" 5540 "eOutdent, CollectNonEditableNodes::Yes) failed"); 5541 return Err(rv); 5542 } 5543 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult = 5544 MaybeSplitElementsAtEveryBRElement(arrayOfContents, 5545 EditSubAction::eOutdent); 5546 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) { 5547 NS_WARNING( 5548 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::" 5549 "eOutdent) failed"); 5550 return splitAtBRElementsResult.propagateErr(); 5551 } 5552 if (AllowsTransactionsToChangeSelection() && 5553 splitAtBRElementsResult.inspect().IsSet()) { 5554 nsresult rv = CollapseSelectionTo(splitAtBRElementsResult.inspect()); 5555 if (NS_FAILED(rv)) { 5556 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 5557 return Err(rv); 5558 } 5559 } 5560 } 5561 5562 nsCOMPtr<nsIContent> leftContentOfLastOutdented; 5563 nsCOMPtr<nsIContent> middleContentOfLastOutdented; 5564 nsCOMPtr<nsIContent> rightContentOfLastOutdented; 5565 RefPtr<Element> indentedParentElement; 5566 nsCOMPtr<nsIContent> firstContentToBeOutdented, lastContentToBeOutdented; 5567 BlockIndentedWith indentedParentIndentedWith = BlockIndentedWith::HTML; 5568 for (const OwningNonNull<nsIContent>& content : arrayOfContents) { 5569 // Here's where we actually figure out what to do 5570 EditorDOMPoint atContent(content); 5571 if (NS_WARN_IF(!atContent.IsInContentNode())) { 5572 continue; 5573 } 5574 5575 // If it's a `<blockquote>`, remove it to outdent its children. 5576 if (content->IsHTMLElement(nsGkAtoms::blockquote)) { 5577 // If we've already found an ancestor block element indented, we need to 5578 // split it and remove the block element first. 5579 if (indentedParentElement) { 5580 NS_WARNING_ASSERTION(indentedParentElement == content, 5581 "Indented parent element is not the <blockquote>"); 5582 Result<SplitRangeOffFromNodeResult, nsresult> outdentResult = 5583 OutdentPartOfBlock(*indentedParentElement, 5584 *firstContentToBeOutdented, 5585 *lastContentToBeOutdented, 5586 indentedParentIndentedWith, aEditingHost); 5587 if (MOZ_UNLIKELY(outdentResult.isErr())) { 5588 NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed"); 5589 return outdentResult; 5590 } 5591 SplitRangeOffFromNodeResult unwrappedOutdentResult = 5592 outdentResult.unwrap(); 5593 unwrappedOutdentResult.IgnoreCaretPointSuggestion(); 5594 leftContentOfLastOutdented = unwrappedOutdentResult.UnwrapLeftContent(); 5595 middleContentOfLastOutdented = 5596 unwrappedOutdentResult.UnwrapMiddleContent(); 5597 rightContentOfLastOutdented = 5598 unwrappedOutdentResult.UnwrapRightContent(); 5599 indentedParentElement = nullptr; 5600 firstContentToBeOutdented = nullptr; 5601 lastContentToBeOutdented = nullptr; 5602 indentedParentIndentedWith = BlockIndentedWith::HTML; 5603 } 5604 Result<EditorDOMPoint, nsresult> unwrapBlockquoteElementResult = 5605 RemoveBlockContainerWithTransaction( 5606 MOZ_KnownLive(*content->AsElement())); 5607 if (MOZ_UNLIKELY(unwrapBlockquoteElementResult.isErr())) { 5608 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed"); 5609 return unwrapBlockquoteElementResult.propagateErr(); 5610 } 5611 const EditorDOMPoint& pointToPutCaret = 5612 unwrapBlockquoteElementResult.inspect(); 5613 if (AllowsTransactionsToChangeSelection() && pointToPutCaret.IsSet()) { 5614 nsresult rv = CollapseSelectionTo(pointToPutCaret); 5615 if (NS_FAILED(rv)) { 5616 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 5617 return Err(rv); 5618 } 5619 } 5620 continue; 5621 } 5622 5623 // If we're using CSS and the node is a block element, check its start 5624 // margin whether it's indented with CSS. 5625 if (useCSS && HTMLEditUtils::IsBlockElement( 5626 content, BlockInlineCheck::UseHTMLDefaultStyle)) { 5627 nsStaticAtom& marginProperty = 5628 MarginPropertyAtomForIndent(MOZ_KnownLive(content)); 5629 if (NS_WARN_IF(Destroyed())) { 5630 return Err(NS_ERROR_EDITOR_DESTROYED); 5631 } 5632 nsAutoString value; 5633 DebugOnly<nsresult> rvIgnored = 5634 CSSEditUtils::GetSpecifiedProperty(content, marginProperty, value); 5635 if (NS_WARN_IF(Destroyed())) { 5636 return Err(NS_ERROR_EDITOR_DESTROYED); 5637 } 5638 NS_WARNING_ASSERTION( 5639 NS_SUCCEEDED(rvIgnored), 5640 "CSSEditUtils::GetSpecifiedProperty() failed, but ignored"); 5641 float startMargin = 0; 5642 RefPtr<nsAtom> unit; 5643 CSSEditUtils::ParseLength(value, &startMargin, getter_AddRefs(unit)); 5644 // If indented with CSS, we should decrease the start margin. 5645 if (startMargin > 0) { 5646 const Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 5647 ChangeMarginStart(MOZ_KnownLive(*content->AsElement()), 5648 ChangeMargin::Decrease, aEditingHost); 5649 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 5650 if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() == 5651 NS_ERROR_EDITOR_DESTROYED)) { 5652 return Err(NS_ERROR_EDITOR_DESTROYED); 5653 } 5654 NS_WARNING( 5655 "HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed, " 5656 "but ignored"); 5657 } else if (AllowsTransactionsToChangeSelection() && 5658 pointToPutCaretOrError.inspect().IsSet()) { 5659 nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect()); 5660 if (NS_FAILED(rv)) { 5661 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 5662 return Err(rv); 5663 } 5664 } 5665 continue; 5666 } 5667 } 5668 5669 // If it's a list item, we should treat as that it "indents" its children. 5670 if (HTMLEditUtils::IsListItemElement(*content)) { 5671 // If it is a list item, that means we are not outdenting whole list. 5672 // XXX I don't understand this sentence... We may meet parent list 5673 // element, no? 5674 if (indentedParentElement) { 5675 Result<SplitRangeOffFromNodeResult, nsresult> outdentResult = 5676 OutdentPartOfBlock(*indentedParentElement, 5677 *firstContentToBeOutdented, 5678 *lastContentToBeOutdented, 5679 indentedParentIndentedWith, aEditingHost); 5680 if (MOZ_UNLIKELY(outdentResult.isErr())) { 5681 NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed"); 5682 return outdentResult; 5683 } 5684 SplitRangeOffFromNodeResult unwrappedOutdentResult = 5685 outdentResult.unwrap(); 5686 unwrappedOutdentResult.IgnoreCaretPointSuggestion(); 5687 leftContentOfLastOutdented = unwrappedOutdentResult.UnwrapLeftContent(); 5688 middleContentOfLastOutdented = 5689 unwrappedOutdentResult.UnwrapMiddleContent(); 5690 rightContentOfLastOutdented = 5691 unwrappedOutdentResult.UnwrapRightContent(); 5692 indentedParentElement = nullptr; 5693 firstContentToBeOutdented = nullptr; 5694 lastContentToBeOutdented = nullptr; 5695 indentedParentIndentedWith = BlockIndentedWith::HTML; 5696 } 5697 // XXX `content` could become different element since 5698 // `OutdentPartOfBlock()` may run mutation event listeners. 5699 nsresult rv = LiftUpListItemElement(MOZ_KnownLive(*content->AsElement()), 5700 LiftUpFromAllParentListElements::No); 5701 if (NS_FAILED(rv)) { 5702 NS_WARNING( 5703 "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:" 5704 ":No) failed"); 5705 return Err(rv); 5706 } 5707 continue; 5708 } 5709 5710 // If we've found an ancestor block element which indents its children 5711 // and the current node is NOT a descendant of it, we should remove it to 5712 // outdent its children. Otherwise, i.e., current node is a descendant of 5713 // it, we meet new node which should be outdented when the indented parent 5714 // is removed. 5715 if (indentedParentElement) { 5716 if (EditorUtils::IsDescendantOf(*content, *indentedParentElement)) { 5717 // Extend the range to be outdented at removing the 5718 // indentedParentElement. 5719 lastContentToBeOutdented = content; 5720 continue; 5721 } 5722 Result<SplitRangeOffFromNodeResult, nsresult> outdentResult = 5723 OutdentPartOfBlock(*indentedParentElement, *firstContentToBeOutdented, 5724 *lastContentToBeOutdented, 5725 indentedParentIndentedWith, aEditingHost); 5726 if (MOZ_UNLIKELY(outdentResult.isErr())) { 5727 NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed"); 5728 return outdentResult; 5729 } 5730 SplitRangeOffFromNodeResult unwrappedOutdentResult = 5731 outdentResult.unwrap(); 5732 unwrappedOutdentResult.IgnoreCaretPointSuggestion(); 5733 leftContentOfLastOutdented = unwrappedOutdentResult.UnwrapLeftContent(); 5734 middleContentOfLastOutdented = 5735 unwrappedOutdentResult.UnwrapMiddleContent(); 5736 rightContentOfLastOutdented = unwrappedOutdentResult.UnwrapRightContent(); 5737 indentedParentElement = nullptr; 5738 firstContentToBeOutdented = nullptr; 5739 lastContentToBeOutdented = nullptr; 5740 // curBlockIndentedWith = HTMLEditor::BlockIndentedWith::HTML; 5741 5742 // Then, we need to look for next indentedParentElement. 5743 } 5744 5745 indentedParentIndentedWith = BlockIndentedWith::HTML; 5746 for (nsCOMPtr<nsIContent> parentContent = content->GetParent(); 5747 parentContent && !parentContent->IsHTMLElement(nsGkAtoms::body) && 5748 parentContent != &aEditingHost && 5749 (parentContent->IsHTMLElement(nsGkAtoms::table) || 5750 !HTMLEditUtils::IsAnyTableElementExceptColumnElement(*parentContent)); 5751 parentContent = parentContent->GetParent()) { 5752 if (MOZ_UNLIKELY(!HTMLEditUtils::IsRemovableNode(*parentContent))) { 5753 continue; 5754 } 5755 // If we reach a `<blockquote>` ancestor, it should be split at next 5756 // time at least for outdenting current node. 5757 if (parentContent->IsHTMLElement(nsGkAtoms::blockquote)) { 5758 indentedParentElement = parentContent->AsElement(); 5759 firstContentToBeOutdented = content; 5760 lastContentToBeOutdented = content; 5761 break; 5762 } 5763 5764 if (!useCSS) { 5765 continue; 5766 } 5767 5768 nsCOMPtr<nsINode> grandParentNode = parentContent->GetParentNode(); 5769 nsStaticAtom& marginProperty = 5770 MarginPropertyAtomForIndent(MOZ_KnownLive(content)); 5771 if (NS_WARN_IF(Destroyed())) { 5772 return Err(NS_ERROR_EDITOR_DESTROYED); 5773 } 5774 if (NS_WARN_IF(grandParentNode != parentContent->GetParentNode())) { 5775 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 5776 } 5777 nsAutoString value; 5778 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetSpecifiedProperty( 5779 *parentContent, marginProperty, value); 5780 if (NS_WARN_IF(Destroyed())) { 5781 return Err(NS_ERROR_EDITOR_DESTROYED); 5782 } 5783 NS_WARNING_ASSERTION( 5784 NS_SUCCEEDED(rvIgnored), 5785 "CSSEditUtils::GetSpecifiedProperty() failed, but ignored"); 5786 // XXX Now, editing host may become different element. If so, shouldn't 5787 // we stop this handling? 5788 float startMargin; 5789 RefPtr<nsAtom> unit; 5790 CSSEditUtils::ParseLength(value, &startMargin, getter_AddRefs(unit)); 5791 // If we reach a block element which indents its children with start 5792 // margin, we should remove it at next time. 5793 if (startMargin > 0 && !(HTMLEditUtils::IsListElement( 5794 *atContent.ContainerAs<nsIContent>()) && 5795 HTMLEditUtils::IsListElement(*content))) { 5796 indentedParentElement = parentContent->AsElement(); 5797 firstContentToBeOutdented = content; 5798 lastContentToBeOutdented = content; 5799 indentedParentIndentedWith = BlockIndentedWith::CSS; 5800 break; 5801 } 5802 } 5803 5804 if (indentedParentElement) { 5805 continue; 5806 } 5807 5808 // If we don't have any block elements which indents current node and 5809 // both current node and its parent are list element, remove current 5810 // node to move all its children to the parent list. 5811 // XXX This is buggy. When both lists' item types are different, 5812 // we create invalid tree. E.g., `<ul>` may have `<dd>` as its 5813 // list item element. 5814 if (HTMLEditUtils::IsListElement(*atContent.ContainerAs<nsIContent>())) { 5815 if (!HTMLEditUtils::IsListElement(*content)) { 5816 continue; 5817 } 5818 // Just unwrap this sublist 5819 Result<EditorDOMPoint, nsresult> unwrapSubListElementResult = 5820 RemoveBlockContainerWithTransaction( 5821 MOZ_KnownLive(*content->AsElement())); 5822 if (MOZ_UNLIKELY(unwrapSubListElementResult.isErr())) { 5823 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed"); 5824 return unwrapSubListElementResult.propagateErr(); 5825 } 5826 const EditorDOMPoint& pointToPutCaret = 5827 unwrapSubListElementResult.inspect(); 5828 if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret.IsSet()) { 5829 continue; 5830 } 5831 nsresult rv = CollapseSelectionTo(pointToPutCaret); 5832 if (NS_FAILED(rv)) { 5833 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 5834 return Err(rv); 5835 } 5836 continue; 5837 } 5838 5839 // If current content is a list element but its parent is not a list 5840 // element, move children to where it is and remove it from the tree. 5841 if (HTMLEditUtils::IsListElement(*content)) { 5842 // XXX If mutation event listener appends new children forever, this 5843 // becomes an infinite loop so that we should set limitation from 5844 // first child count. 5845 for (nsCOMPtr<nsIContent> lastChildContent = content->GetLastChild(); 5846 lastChildContent; lastChildContent = content->GetLastChild()) { 5847 if (HTMLEditUtils::IsListItemElement(*lastChildContent)) { 5848 nsresult rv = LiftUpListItemElement( 5849 MOZ_KnownLive(*lastChildContent->AsElement()), 5850 LiftUpFromAllParentListElements::No); 5851 if (NS_FAILED(rv)) { 5852 NS_WARNING( 5853 "HTMLEditor::LiftUpListItemElement(" 5854 "LiftUpFromAllParentListElements::No) failed"); 5855 return Err(rv); 5856 } 5857 continue; 5858 } 5859 5860 if (HTMLEditUtils::IsListElement(*lastChildContent)) { 5861 // We have an embedded list, so move it out from under the parent 5862 // list. Be sure to put it after the parent list because this 5863 // loop iterates backwards through the parent's list of children. 5864 EditorDOMPoint afterCurrentList(EditorDOMPoint::After(atContent)); 5865 NS_WARNING_ASSERTION( 5866 afterCurrentList.IsSet(), 5867 "Failed to set it to after current list element"); 5868 Result<MoveNodeResult, nsresult> moveListElementResult = 5869 MoveNodeWithTransaction(*lastChildContent, afterCurrentList); 5870 if (MOZ_UNLIKELY(moveListElementResult.isErr())) { 5871 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed"); 5872 return moveListElementResult.propagateErr(); 5873 } 5874 nsresult rv = moveListElementResult.inspect().SuggestCaretPointTo( 5875 *this, {SuggestCaret::OnlyIfHasSuggestion, 5876 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 5877 SuggestCaret::AndIgnoreTrivialError}); 5878 if (NS_FAILED(rv)) { 5879 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed"); 5880 return Err(rv); 5881 } 5882 NS_WARNING_ASSERTION( 5883 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 5884 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored"); 5885 continue; 5886 } 5887 5888 // Delete any non-list items for now 5889 // XXX Chrome moves it from the list element. We should follow it. 5890 nsresult rv = DeleteNodeWithTransaction(*lastChildContent); 5891 if (NS_FAILED(rv)) { 5892 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 5893 return Err(rv); 5894 } 5895 } 5896 // Delete the now-empty list 5897 Result<EditorDOMPoint, nsresult> unwrapListElementResult = 5898 RemoveBlockContainerWithTransaction( 5899 MOZ_KnownLive(*content->AsElement())); 5900 if (MOZ_UNLIKELY(unwrapListElementResult.isErr())) { 5901 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed"); 5902 return unwrapListElementResult.propagateErr(); 5903 } 5904 const EditorDOMPoint& pointToPutCaret = unwrapListElementResult.inspect(); 5905 if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret.IsSet()) { 5906 continue; 5907 } 5908 nsresult rv = CollapseSelectionTo(pointToPutCaret); 5909 if (NS_FAILED(rv)) { 5910 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 5911 return Err(rv); 5912 } 5913 continue; 5914 } 5915 5916 if (useCSS) { 5917 if (RefPtr<Element> element = content->GetAsElementOrParentElement()) { 5918 const Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 5919 ChangeMarginStart(*element, ChangeMargin::Decrease, aEditingHost); 5920 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 5921 if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() == 5922 NS_ERROR_EDITOR_DESTROYED)) { 5923 return Err(NS_ERROR_EDITOR_DESTROYED); 5924 } 5925 NS_WARNING( 5926 "HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed, " 5927 "but ignored"); 5928 } else if (AllowsTransactionsToChangeSelection() && 5929 pointToPutCaretOrError.inspect().IsSet()) { 5930 nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect()); 5931 if (NS_FAILED(rv)) { 5932 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 5933 return Err(rv); 5934 } 5935 } 5936 } 5937 continue; 5938 } 5939 } 5940 5941 if (!indentedParentElement) { 5942 return SplitRangeOffFromNodeResult(leftContentOfLastOutdented, 5943 middleContentOfLastOutdented, 5944 rightContentOfLastOutdented); 5945 } 5946 5947 // We have a <blockquote> we haven't finished handling. 5948 Result<SplitRangeOffFromNodeResult, nsresult> outdentResult = 5949 OutdentPartOfBlock(*indentedParentElement, *firstContentToBeOutdented, 5950 *lastContentToBeOutdented, indentedParentIndentedWith, 5951 aEditingHost); 5952 if (MOZ_UNLIKELY(outdentResult.isErr())) { 5953 NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed"); 5954 return outdentResult; 5955 } 5956 // We will restore selection soon. Therefore, callers do not need to restore 5957 // the selection. 5958 SplitRangeOffFromNodeResult unwrappedOutdentResult = outdentResult.unwrap(); 5959 unwrappedOutdentResult.ForgetCaretPointSuggestion(); 5960 return unwrappedOutdentResult; 5961 } 5962 5963 Result<SplitRangeOffFromNodeResult, nsresult> 5964 HTMLEditor::RemoveBlockContainerElementWithTransactionBetween( 5965 Element& aBlockContainerElement, nsIContent& aStartOfRange, 5966 nsIContent& aEndOfRange, BlockInlineCheck aBlockInlineCheck) { 5967 MOZ_ASSERT(IsEditActionDataAvailable()); 5968 5969 EditorDOMPoint pointToPutCaret; 5970 Result<SplitRangeOffFromNodeResult, nsresult> splitResult = 5971 SplitRangeOffFromElement(aBlockContainerElement, aStartOfRange, 5972 aEndOfRange); 5973 if (MOZ_UNLIKELY(splitResult.isErr())) { 5974 if (splitResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) { 5975 NS_WARNING("HTMLEditor::SplitRangeOffFromElement() failed"); 5976 return splitResult; 5977 } 5978 NS_WARNING( 5979 "HTMLEditor::SplitRangeOffFromElement() failed, but might be ignored"); 5980 return SplitRangeOffFromNodeResult(nullptr, nullptr, nullptr); 5981 } 5982 SplitRangeOffFromNodeResult unwrappedSplitResult = splitResult.unwrap(); 5983 unwrappedSplitResult.MoveCaretPointTo(pointToPutCaret, 5984 {SuggestCaret::OnlyIfHasSuggestion}); 5985 5986 // Even if either split aBlockContainerElement or did not split it, we should 5987 // unwrap the right most element which is split from aBlockContainerElement 5988 // (or aBlockContainerElement itself if it was not split without errors). 5989 Element* rightmostElement = 5990 unwrappedSplitResult.GetRightmostContentAs<Element>(); 5991 MOZ_ASSERT(rightmostElement); 5992 if (NS_WARN_IF(!rightmostElement)) { 5993 return Err(NS_ERROR_FAILURE); 5994 } 5995 5996 { 5997 // MOZ_KnownLive(rightmostElement) because it's grabbed by 5998 // unwrappedSplitResult. 5999 Result<EditorDOMPoint, nsresult> unwrapBlockElementResult = 6000 RemoveBlockContainerWithTransaction(MOZ_KnownLive(*rightmostElement)); 6001 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) { 6002 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed"); 6003 return unwrapBlockElementResult.propagateErr(); 6004 } 6005 if (unwrapBlockElementResult.inspect().IsSet()) { 6006 pointToPutCaret = unwrapBlockElementResult.unwrap(); 6007 } 6008 } 6009 6010 return SplitRangeOffFromNodeResult( 6011 unwrappedSplitResult.GetLeftContent(), nullptr, 6012 unwrappedSplitResult.GetRightContent(), std::move(pointToPutCaret)); 6013 } 6014 6015 Result<SplitRangeOffFromNodeResult, nsresult> 6016 HTMLEditor::SplitRangeOffFromElement(Element& aElementToSplit, 6017 nsIContent& aStartOfMiddleElement, 6018 nsIContent& aEndOfMiddleElement) { 6019 MOZ_ASSERT(IsEditActionDataAvailable()); 6020 6021 // aStartOfMiddleElement and aEndOfMiddleElement must be exclusive 6022 // descendants of aElementToSplit. 6023 MOZ_ASSERT( 6024 EditorUtils::IsDescendantOf(aStartOfMiddleElement, aElementToSplit)); 6025 MOZ_ASSERT(EditorUtils::IsDescendantOf(aEndOfMiddleElement, aElementToSplit)); 6026 6027 EditorDOMPoint pointToPutCaret; 6028 // Split at the start. 6029 Result<SplitNodeResult, nsresult> splitAtStartResult = 6030 SplitNodeDeepWithTransaction(aElementToSplit, 6031 EditorDOMPoint(&aStartOfMiddleElement), 6032 SplitAtEdges::eDoNotCreateEmptyContainer); 6033 if (MOZ_UNLIKELY(splitAtStartResult.isErr())) { 6034 if (splitAtStartResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) { 6035 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed (at left)"); 6036 return Err(NS_ERROR_EDITOR_DESTROYED); 6037 } 6038 NS_WARNING( 6039 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::" 6040 "eDoNotCreateEmptyContainer) at start of middle element failed"); 6041 } else { 6042 splitAtStartResult.inspect().CopyCaretPointTo( 6043 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 6044 } 6045 6046 // Split at after the end 6047 auto atAfterEnd = EditorDOMPoint::After(aEndOfMiddleElement); 6048 Element* rightElement = 6049 splitAtStartResult.isOk() && splitAtStartResult.inspect().DidSplit() 6050 ? splitAtStartResult.inspect().GetNextContentAs<Element>() 6051 : &aElementToSplit; 6052 // MOZ_KnownLive(rightElement) because it's grabbed by splitAtStartResult or 6053 // aElementToSplit whose lifetime is guaranteed by the caller. 6054 Result<SplitNodeResult, nsresult> splitAtEndResult = 6055 SplitNodeDeepWithTransaction(MOZ_KnownLive(*rightElement), atAfterEnd, 6056 SplitAtEdges::eDoNotCreateEmptyContainer); 6057 if (MOZ_UNLIKELY(splitAtEndResult.isErr())) { 6058 if (splitAtEndResult.inspectErr() == NS_ERROR_EDITOR_DESTROYED) { 6059 NS_WARNING( 6060 "HTMLEditor::SplitNodeDeepWithTransaction() failed (at right)"); 6061 return Err(NS_ERROR_EDITOR_DESTROYED); 6062 } 6063 NS_WARNING( 6064 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::" 6065 "eDoNotCreateEmptyContainer) after end of middle element failed"); 6066 } else { 6067 splitAtEndResult.inspect().CopyCaretPointTo( 6068 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 6069 } 6070 6071 if (splitAtStartResult.isOk() && splitAtStartResult.inspect().DidSplit() && 6072 splitAtEndResult.isOk() && splitAtEndResult.inspect().DidSplit()) { 6073 // Note that the middle node can be computed only with the latter split 6074 // result. 6075 return SplitRangeOffFromNodeResult( 6076 splitAtStartResult.inspect().GetPreviousContent(), 6077 splitAtEndResult.inspect().GetPreviousContent(), 6078 splitAtEndResult.inspect().GetNextContent(), 6079 std::move(pointToPutCaret)); 6080 } 6081 if (splitAtStartResult.isOk() && splitAtStartResult.inspect().DidSplit()) { 6082 return SplitRangeOffFromNodeResult( 6083 splitAtStartResult.inspect().GetPreviousContent(), 6084 splitAtStartResult.inspect().GetNextContent(), nullptr, 6085 std::move(pointToPutCaret)); 6086 } 6087 if (splitAtEndResult.isOk() && splitAtEndResult.inspect().DidSplit()) { 6088 return SplitRangeOffFromNodeResult( 6089 nullptr, splitAtEndResult.inspect().GetPreviousContent(), 6090 splitAtEndResult.inspect().GetNextContent(), 6091 std::move(pointToPutCaret)); 6092 } 6093 return SplitRangeOffFromNodeResult(nullptr, &aElementToSplit, nullptr, 6094 std::move(pointToPutCaret)); 6095 } 6096 6097 Result<SplitRangeOffFromNodeResult, nsresult> HTMLEditor::OutdentPartOfBlock( 6098 Element& aBlockElement, nsIContent& aStartOfOutdent, 6099 nsIContent& aEndOfOutdent, BlockIndentedWith aBlockIndentedWith, 6100 const Element& aEditingHost) { 6101 MOZ_ASSERT(IsEditActionDataAvailable()); 6102 6103 Result<SplitRangeOffFromNodeResult, nsresult> splitResult = 6104 SplitRangeOffFromElement(aBlockElement, aStartOfOutdent, aEndOfOutdent); 6105 if (MOZ_UNLIKELY(splitResult.isErr())) { 6106 NS_WARNING("HTMLEditor::SplitRangeOffFromElement() failed"); 6107 return splitResult; 6108 } 6109 6110 SplitRangeOffFromNodeResult unwrappedSplitResult = splitResult.unwrap(); 6111 Element* middleElement = unwrappedSplitResult.GetMiddleContentAs<Element>(); 6112 if (MOZ_UNLIKELY(!middleElement)) { 6113 NS_WARNING( 6114 "HTMLEditor::SplitRangeOffFromElement() didn't return middle content"); 6115 unwrappedSplitResult.IgnoreCaretPointSuggestion(); 6116 return Err(NS_ERROR_FAILURE); 6117 } 6118 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*middleElement))) { 6119 unwrappedSplitResult.IgnoreCaretPointSuggestion(); 6120 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 6121 } 6122 6123 nsresult rv = unwrappedSplitResult.SuggestCaretPointTo( 6124 *this, {SuggestCaret::OnlyIfHasSuggestion, 6125 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 6126 SuggestCaret::AndIgnoreTrivialError}); 6127 if (NS_FAILED(rv)) { 6128 NS_WARNING("SplitRangeOffFromNodeResult::SuggestCaretPointTo() failed"); 6129 return Err(rv); 6130 } 6131 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6132 "SplitRangeOffFromNodeResult::SuggestCaretPointTo() " 6133 "failed, but ignored"); 6134 6135 if (aBlockIndentedWith == BlockIndentedWith::HTML) { 6136 // MOZ_KnownLive(middleElement) because of grabbed by unwrappedSplitResult. 6137 Result<EditorDOMPoint, nsresult> unwrapBlockElementResult = 6138 RemoveBlockContainerWithTransaction(MOZ_KnownLive(*middleElement)); 6139 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) { 6140 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed"); 6141 return unwrapBlockElementResult.propagateErr(); 6142 } 6143 const EditorDOMPoint& pointToPutCaret = unwrapBlockElementResult.inspect(); 6144 if (AllowsTransactionsToChangeSelection() && pointToPutCaret.IsSet()) { 6145 nsresult rv = CollapseSelectionTo(pointToPutCaret); 6146 if (NS_FAILED(rv)) { 6147 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 6148 return Err(rv); 6149 } 6150 } 6151 return SplitRangeOffFromNodeResult(unwrappedSplitResult.GetLeftContent(), 6152 nullptr, 6153 unwrappedSplitResult.GetRightContent()); 6154 } 6155 6156 // MOZ_KnownLive(middleElement) because of grabbed by unwrappedSplitResult. 6157 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = ChangeMarginStart( 6158 MOZ_KnownLive(*middleElement), ChangeMargin::Decrease, aEditingHost); 6159 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 6160 NS_WARNING("HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed"); 6161 return pointToPutCaretOrError.propagateErr(); 6162 } 6163 if (AllowsTransactionsToChangeSelection() && 6164 pointToPutCaretOrError.inspect().IsSet()) { 6165 nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect()); 6166 if (NS_FAILED(rv)) { 6167 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 6168 return Err(rv); 6169 } 6170 } 6171 return unwrappedSplitResult; 6172 } 6173 6174 Result<CreateElementResult, nsresult> HTMLEditor::ChangeListElementType( 6175 Element& aListElement, nsAtom& aNewListTag, nsAtom& aNewListItemTag) { 6176 MOZ_ASSERT(IsEditActionDataAvailable()); 6177 6178 EditorDOMPoint pointToPutCaret; 6179 6180 AutoTArray<OwningNonNull<nsIContent>, 32> listElementChildren; 6181 HTMLEditUtils::CollectAllChildren(aListElement, listElementChildren); 6182 6183 for (const OwningNonNull<nsIContent>& childContent : listElementChildren) { 6184 if (!childContent->IsElement()) { 6185 continue; 6186 } 6187 Element& childElement = *childContent->AsElement(); 6188 if (HTMLEditUtils::IsListItemElement(childElement) && 6189 !childContent->IsHTMLElement(&aNewListItemTag)) { 6190 // MOZ_KnownLive(childElement) because its lifetime is guaranteed by 6191 // listElementChildren. 6192 Result<CreateElementResult, nsresult> 6193 replaceWithNewListItemElementResult = 6194 ReplaceContainerAndCloneAttributesWithTransaction( 6195 MOZ_KnownLive(childElement), aNewListItemTag); 6196 if (MOZ_UNLIKELY(replaceWithNewListItemElementResult.isErr())) { 6197 NS_WARNING( 6198 "HTMLEditor::ReplaceContainerAndCloneAttributesWithTransaction() " 6199 "failed"); 6200 return replaceWithNewListItemElementResult; 6201 } 6202 CreateElementResult unwrappedReplaceWithNewListItemElementResult = 6203 replaceWithNewListItemElementResult.unwrap(); 6204 unwrappedReplaceWithNewListItemElementResult.MoveCaretPointTo( 6205 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 6206 continue; 6207 } 6208 if (HTMLEditUtils::IsListElement(childElement) && 6209 !childElement.IsHTMLElement(&aNewListTag)) { 6210 // XXX List elements shouldn't have other list elements as their 6211 // child. Why do we handle such invalid tree? 6212 // -> Maybe, for bug 525888. 6213 // MOZ_KnownLive(childElement) because its lifetime is guaranteed by 6214 // listElementChildren. 6215 Result<CreateElementResult, nsresult> convertListTypeResult = 6216 ChangeListElementType(MOZ_KnownLive(childElement), aNewListTag, 6217 aNewListItemTag); 6218 if (MOZ_UNLIKELY(convertListTypeResult.isErr())) { 6219 NS_WARNING("HTMLEditor::ChangeListElementType() failed"); 6220 return convertListTypeResult; 6221 } 6222 CreateElementResult unwrappedConvertListTypeResult = 6223 convertListTypeResult.unwrap(); 6224 unwrappedConvertListTypeResult.MoveCaretPointTo( 6225 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 6226 continue; 6227 } 6228 } 6229 6230 if (aListElement.IsHTMLElement(&aNewListTag)) { 6231 return CreateElementResult(aListElement, std::move(pointToPutCaret)); 6232 } 6233 6234 // XXX If we replace the list element, shouldn't we create it first and then, 6235 // move children into it before inserting the new list element into the 6236 // DOM tree? Then, we could reduce the cost of dispatching DOM mutation 6237 // events. 6238 Result<CreateElementResult, nsresult> replaceWithNewListElementResult = 6239 ReplaceContainerWithTransaction(aListElement, aNewListTag); 6240 if (MOZ_UNLIKELY(replaceWithNewListElementResult.isErr())) { 6241 NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed"); 6242 return replaceWithNewListElementResult; 6243 } 6244 CreateElementResult unwrappedReplaceWithNewListElementResult = 6245 replaceWithNewListElementResult.unwrap(); 6246 unwrappedReplaceWithNewListElementResult.MoveCaretPointTo( 6247 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 6248 return CreateElementResult( 6249 unwrappedReplaceWithNewListElementResult.UnwrapNewNode(), 6250 std::move(pointToPutCaret)); 6251 } 6252 6253 Result<EditorDOMPoint, nsresult> HTMLEditor::CreateStyleForInsertText( 6254 const EditorDOMPoint& aPointToInsertText, const Element& aEditingHost) { 6255 MOZ_ASSERT(IsEditActionDataAvailable()); 6256 MOZ_ASSERT(aPointToInsertText.IsSetAndValid()); 6257 MOZ_ASSERT(mPendingStylesToApplyToNewContent); 6258 6259 const RefPtr<Element> documentRootElement = GetDocument()->GetRootElement(); 6260 if (NS_WARN_IF(!documentRootElement)) { 6261 return Err(NS_ERROR_FAILURE); 6262 } 6263 6264 // process clearing any styles first 6265 UniquePtr<PendingStyle> pendingStyle = 6266 mPendingStylesToApplyToNewContent->TakeClearingStyle(); 6267 6268 EditorDOMPoint pointToPutCaret(aPointToInsertText); 6269 { 6270 // Transactions may set selection, but we will set selection if necessary. 6271 AutoTransactionsConserveSelection dontChangeMySelection(*this); 6272 6273 while (pendingStyle && 6274 pointToPutCaret.GetContainer() != documentRootElement) { 6275 // MOZ_KnownLive because we own pendingStyle which guarantees the lifetime 6276 // of its members. 6277 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 6278 ClearStyleAt(pointToPutCaret, pendingStyle->ToInlineStyle(), 6279 pendingStyle->GetSpecifiedStyle(), aEditingHost); 6280 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 6281 NS_WARNING("HTMLEditor::ClearStyleAt() failed"); 6282 return pointToPutCaretOrError; 6283 } 6284 pointToPutCaret = pointToPutCaretOrError.unwrap(); 6285 if (NS_WARN_IF(!pointToPutCaret.IsSetAndValid())) { 6286 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 6287 } 6288 pendingStyle = mPendingStylesToApplyToNewContent->TakeClearingStyle(); 6289 } 6290 } 6291 6292 // then process setting any styles 6293 const int32_t relFontSize = 6294 mPendingStylesToApplyToNewContent->TakeRelativeFontSize(); 6295 AutoTArray<EditorInlineStyleAndValue, 32> stylesToSet; 6296 mPendingStylesToApplyToNewContent->TakeAllPreservedStyles(stylesToSet); 6297 if (stylesToSet.IsEmpty() && !relFontSize) { 6298 return pointToPutCaret; 6299 } 6300 6301 // We're in chrome, e.g., the email composer of Thunderbird, and there is 6302 // relative font size changes, we need to keep using legacy path until we port 6303 // IncrementOrDecrementFontSizeAsSubAction() to work with 6304 // AutoInlineStyleSetter. 6305 if (relFontSize) { 6306 // we have at least one style to add; make a new text node to insert style 6307 // nodes above. 6308 EditorDOMPoint pointToInsertTextNode(pointToPutCaret); 6309 if (pointToInsertTextNode.IsInTextNode()) { 6310 // if we are in a text node, split it 6311 Result<SplitNodeResult, nsresult> splitTextNodeResult = 6312 SplitNodeDeepWithTransaction( 6313 MOZ_KnownLive(*pointToInsertTextNode.ContainerAs<Text>()), 6314 pointToInsertTextNode, 6315 SplitAtEdges::eAllowToCreateEmptyContainer); 6316 if (MOZ_UNLIKELY(splitTextNodeResult.isErr())) { 6317 NS_WARNING( 6318 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::" 6319 "eAllowToCreateEmptyContainer) failed"); 6320 return splitTextNodeResult.propagateErr(); 6321 } 6322 SplitNodeResult unwrappedSplitTextNodeResult = 6323 splitTextNodeResult.unwrap(); 6324 unwrappedSplitTextNodeResult.MoveCaretPointTo( 6325 pointToPutCaret, *this, 6326 {SuggestCaret::OnlyIfHasSuggestion, 6327 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 6328 pointToInsertTextNode = 6329 unwrappedSplitTextNodeResult.AtSplitPoint<EditorDOMPoint>(); 6330 } 6331 if (!pointToInsertTextNode.IsInContentNode() || 6332 !HTMLEditUtils::IsContainerNode( 6333 *pointToInsertTextNode.ContainerAs<nsIContent>())) { 6334 return pointToPutCaret; 6335 } 6336 RefPtr<Text> newEmptyTextNode = CreateTextNode(u""_ns); 6337 if (!newEmptyTextNode) { 6338 NS_WARNING("EditorBase::CreateTextNode() failed"); 6339 return Err(NS_ERROR_FAILURE); 6340 } 6341 Result<CreateTextResult, nsresult> insertNewTextNodeResult = 6342 InsertNodeWithTransaction<Text>(*newEmptyTextNode, 6343 pointToInsertTextNode); 6344 if (MOZ_UNLIKELY(insertNewTextNodeResult.isErr())) { 6345 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed"); 6346 return insertNewTextNodeResult.propagateErr(); 6347 } 6348 insertNewTextNodeResult.inspect().IgnoreCaretPointSuggestion(); 6349 pointToPutCaret.Set(newEmptyTextNode, 0u); 6350 6351 // FIXME: If the stylesToSet have background-color style, it may 6352 // be applied shorter because outer <span> element height is not 6353 // computed with inner element's height. 6354 HTMLEditor::FontSize incrementOrDecrement = 6355 relFontSize > 0 ? HTMLEditor::FontSize::incr 6356 : HTMLEditor::FontSize::decr; 6357 for ([[maybe_unused]] uint32_t j : IntegerRange(Abs(relFontSize))) { 6358 Result<CreateElementResult, nsresult> wrapTextInBigOrSmallElementResult = 6359 SetFontSizeOnTextNode(*newEmptyTextNode, 0, UINT32_MAX, 6360 incrementOrDecrement); 6361 if (MOZ_UNLIKELY(wrapTextInBigOrSmallElementResult.isErr())) { 6362 NS_WARNING("HTMLEditor::SetFontSizeOnTextNode() failed"); 6363 return wrapTextInBigOrSmallElementResult.propagateErr(); 6364 } 6365 // We don't need to update here because we'll suggest caret position 6366 // which is computed above. 6367 MOZ_ASSERT(pointToPutCaret.IsSet()); 6368 wrapTextInBigOrSmallElementResult.inspect().IgnoreCaretPointSuggestion(); 6369 } 6370 6371 for (const EditorInlineStyleAndValue& styleToSet : stylesToSet) { 6372 AutoInlineStyleSetter inlineStyleSetter(styleToSet); 6373 // MOZ_KnownLive(...ContainerAs<nsIContent>()) because pointToPutCaret 6374 // grabs the result. 6375 Result<CaretPoint, nsresult> setStyleResult = 6376 inlineStyleSetter.ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle( 6377 *this, MOZ_KnownLive(*pointToPutCaret.ContainerAs<nsIContent>())); 6378 if (MOZ_UNLIKELY(setStyleResult.isErr())) { 6379 NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed"); 6380 return setStyleResult.propagateErr(); 6381 } 6382 // We don't need to update here because we'll suggest caret position which 6383 // is computed above. 6384 MOZ_ASSERT(pointToPutCaret.IsSet()); 6385 setStyleResult.unwrap().IgnoreCaretPointSuggestion(); 6386 } 6387 return pointToPutCaret; 6388 } 6389 6390 // If we have preserved commands except relative font style changes, we can 6391 // use inline style setting code which reuse ancestors better. 6392 AutoClonedRangeArray ranges(pointToPutCaret); 6393 if (MOZ_UNLIKELY(ranges.Ranges().IsEmpty())) { 6394 NS_WARNING("AutoClonedRangeArray::AutoClonedRangeArray() failed"); 6395 return Err(NS_ERROR_FAILURE); 6396 } 6397 nsresult rv = SetInlinePropertiesAroundRanges(ranges, stylesToSet); 6398 if (NS_FAILED(rv)) { 6399 NS_WARNING("HTMLEditor::SetInlinePropertiesAroundRanges() failed"); 6400 return Err(rv); 6401 } 6402 if (NS_WARN_IF(ranges.Ranges().IsEmpty())) { 6403 return Err(NS_ERROR_FAILURE); 6404 } 6405 // Now `ranges` selects new styled contents and the range may not be 6406 // collapsed. We should use the deepest editable start point of the range 6407 // to insert text. 6408 nsINode* container = ranges.FirstRangeRef()->GetStartContainer(); 6409 if (MOZ_UNLIKELY(!container->IsContent())) { 6410 container = ranges.FirstRangeRef()->GetChildAtStartOffset(); 6411 if (MOZ_UNLIKELY(!container)) { 6412 NS_WARNING("How did we get lost insertion point?"); 6413 return Err(NS_ERROR_FAILURE); 6414 } 6415 } 6416 pointToPutCaret = 6417 HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>( 6418 *container->AsContent(), {}); 6419 if (NS_WARN_IF(!pointToPutCaret.IsSet())) { 6420 return Err(NS_ERROR_FAILURE); 6421 } 6422 return pointToPutCaret; 6423 } 6424 6425 Result<EditActionResult, nsresult> HTMLEditor::AlignAsSubAction( 6426 const nsAString& aAlignType, const Element& aEditingHost) { 6427 MOZ_ASSERT(IsEditActionDataAvailable()); 6428 6429 AutoPlaceholderBatch treatAsOneTransaction( 6430 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 6431 IgnoredErrorResult ignoredError; 6432 AutoEditSubActionNotifier startToHandleEditSubAction( 6433 *this, EditSubAction::eSetOrClearAlignment, nsIEditor::eNext, 6434 ignoredError); 6435 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 6436 return Err(ignoredError.StealNSResult()); 6437 } 6438 NS_WARNING_ASSERTION( 6439 !ignoredError.Failed(), 6440 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 6441 6442 { 6443 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 6444 if (MOZ_UNLIKELY(result.isErr())) { 6445 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 6446 return result; 6447 } 6448 if (result.inspect().Canceled()) { 6449 return result; 6450 } 6451 } 6452 6453 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) { 6454 NS_WARNING("Some selection containers are not content node, but ignored"); 6455 return EditActionResult::IgnoredResult(); 6456 } 6457 6458 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor(); 6459 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 6460 return Err(NS_ERROR_EDITOR_DESTROYED); 6461 } 6462 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6463 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " 6464 "failed, but ignored"); 6465 6466 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) { 6467 NS_WARNING("Mutation event listener might have changed the selection"); 6468 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 6469 } 6470 6471 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) { 6472 nsresult rv = EnsureCaretNotAfterInvisibleBRElement(aEditingHost); 6473 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 6474 return Err(NS_ERROR_EDITOR_DESTROYED); 6475 } 6476 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6477 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " 6478 "failed, but ignored"); 6479 if (NS_SUCCEEDED(rv)) { 6480 nsresult rv = PrepareInlineStylesForCaret(); 6481 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 6482 return Err(NS_ERROR_EDITOR_DESTROYED); 6483 } 6484 NS_WARNING_ASSERTION( 6485 NS_SUCCEEDED(rv), 6486 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); 6487 } 6488 } 6489 6490 AutoClonedSelectionRangeArray selectionRanges(SelectionRef()); 6491 6492 // XXX Why do we do this only when there is only one selection range? 6493 if (!selectionRanges.IsCollapsed() && 6494 selectionRanges.Ranges().Length() == 1u) { 6495 Result<EditorRawDOMRange, nsresult> extendedRange = 6496 GetRangeExtendedToHardLineEdgesForBlockEditAction( 6497 selectionRanges.FirstRangeRef(), aEditingHost); 6498 if (MOZ_UNLIKELY(extendedRange.isErr())) { 6499 NS_WARNING( 6500 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() " 6501 "failed"); 6502 return extendedRange.propagateErr(); 6503 } 6504 // Note that end point may be prior to start point. So, we 6505 // cannot use etStartAndEnd() here. 6506 nsresult rv = selectionRanges.SetBaseAndExtent( 6507 extendedRange.inspect().StartRef(), extendedRange.inspect().EndRef()); 6508 if (NS_FAILED(rv)) { 6509 NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed"); 6510 return Err(rv); 6511 } 6512 } 6513 6514 rv = AlignContentsAtRanges(selectionRanges, aAlignType, aEditingHost); 6515 if (NS_FAILED(rv)) { 6516 NS_WARNING("HTMLEditor::AlignContentsAtSelection() failed"); 6517 return Err(rv); 6518 } 6519 6520 if (selectionRanges.IsCollapsed()) { 6521 // FIXME: If we get rid of the legacy mutation events, we should be able to 6522 // just insert a line break without empty check. 6523 Result<CreateLineBreakResult, nsresult> 6524 insertPaddingBRElementResultOrError = 6525 InsertPaddingBRElementIfInEmptyBlock( 6526 selectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>(), 6527 eNoStrip); 6528 if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { 6529 NS_WARNING( 6530 "HTMLEditor::InsertPaddingBRElementIfInEmptyBlock(eNoStrip) failed"); 6531 return insertPaddingBRElementResultOrError.propagateErr(); 6532 } 6533 EditorDOMPoint pointToPutCaret; 6534 insertPaddingBRElementResultOrError.unwrap().MoveCaretPointTo( 6535 pointToPutCaret, *this, 6536 {SuggestCaret::OnlyIfHasSuggestion, 6537 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 6538 if (pointToPutCaret.IsSet()) { 6539 nsresult rv = selectionRanges.Collapse(pointToPutCaret); 6540 if (NS_FAILED(rv)) { 6541 NS_WARNING("AutoClonedRangeArray::Collapse() failed"); 6542 return Err(rv); 6543 } 6544 } 6545 } 6546 6547 rv = selectionRanges.ApplyTo(SelectionRef()); 6548 if (NS_FAILED(rv)) { 6549 NS_WARNING("AutoClonedRangeArray::ApplyTo() failed"); 6550 return Err(rv); 6551 } 6552 6553 if (MOZ_UNLIKELY(IsSelectionRangeContainerNotContent())) { 6554 NS_WARNING("Mutation event listener might have changed the selection"); 6555 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 6556 } 6557 6558 return EditActionResult::HandledResult(); 6559 } 6560 6561 nsresult HTMLEditor::AlignContentsAtRanges( 6562 AutoClonedSelectionRangeArray& aRanges, const nsAString& aAlignType, 6563 const Element& aEditingHost) { 6564 MOZ_ASSERT(IsEditActionDataAvailable()); 6565 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); 6566 MOZ_ASSERT(aRanges.IsInContent()); 6567 6568 if (NS_WARN_IF(!aRanges.SaveAndTrackRanges(*this))) { 6569 return NS_ERROR_FAILURE; 6570 } 6571 6572 EditorDOMPoint pointToPutCaret; 6573 6574 // Convert the selection ranges into "promoted" selection ranges: This 6575 // basically just expands the range to include the immediate block parent, 6576 // and then further expands to include any ancestors whose children are all 6577 // in the range 6578 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents; 6579 { 6580 AutoClonedSelectionRangeArray extendedRanges(aRanges); 6581 extendedRanges.ExtendRangesToWrapLines( 6582 EditSubAction::eSetOrClearAlignment, 6583 BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost); 6584 Result<EditorDOMPoint, nsresult> splitResult = 6585 extendedRanges 6586 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries( 6587 *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost); 6588 if (MOZ_UNLIKELY(splitResult.isErr())) { 6589 NS_WARNING( 6590 "AutoClonedRangeArray::" 6591 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() " 6592 "failed"); 6593 return splitResult.unwrapErr(); 6594 } 6595 if (splitResult.inspect().IsSet()) { 6596 pointToPutCaret = splitResult.unwrap(); 6597 } 6598 nsresult rv = extendedRanges.CollectEditTargetNodes( 6599 *this, arrayOfContents, EditSubAction::eSetOrClearAlignment, 6600 AutoClonedRangeArray::CollectNonEditableNodes::Yes); 6601 if (NS_FAILED(rv)) { 6602 NS_WARNING( 6603 "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::" 6604 "eSetOrClearAlignment, CollectNonEditableNodes::Yes) failed"); 6605 return rv; 6606 } 6607 } 6608 6609 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult = 6610 MaybeSplitElementsAtEveryBRElement(arrayOfContents, 6611 EditSubAction::eSetOrClearAlignment); 6612 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) { 6613 NS_WARNING( 6614 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::" 6615 "eSetOrClearAlignment) failed"); 6616 return splitAtBRElementsResult.inspectErr(); 6617 } 6618 if (splitAtBRElementsResult.inspect().IsSet()) { 6619 pointToPutCaret = splitAtBRElementsResult.unwrap(); 6620 } 6621 6622 // If we don't have any nodes, or we have only a single br, then we are 6623 // creating an empty alignment div. We have to do some different things for 6624 // these. 6625 bool createEmptyDivElement = arrayOfContents.IsEmpty(); 6626 if (arrayOfContents.Length() == 1) { 6627 const OwningNonNull<nsIContent>& content = arrayOfContents[0]; 6628 6629 if (HTMLEditUtils::IsAlignAttrSupported(content) && 6630 HTMLEditUtils::IsBlockElement(content, 6631 BlockInlineCheck::UseHTMLDefaultStyle)) { 6632 // The node is a table element, an hr, a paragraph, a div or a section 6633 // header; in HTML 4, it can directly carry the ALIGN attribute and we 6634 // don't need to make a div! If we are in CSS mode, all the work is done 6635 // in SetBlockElementAlign(). 6636 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 6637 SetBlockElementAlign(MOZ_KnownLive(*content->AsElement()), aAlignType, 6638 EditTarget::OnlyDescendantsExceptTable); 6639 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 6640 NS_WARNING("HTMLEditor::SetBlockElementAlign() failed"); 6641 return pointToPutCaretOrError.unwrapErr(); 6642 } 6643 if (pointToPutCaretOrError.inspect().IsSet()) { 6644 pointToPutCaret = pointToPutCaretOrError.unwrap(); 6645 } 6646 } 6647 6648 if (content->IsHTMLElement(nsGkAtoms::br)) { 6649 // The special case createEmptyDivElement code (below) that consumes 6650 // `<br>` elements can cause tables to split if the start node of the 6651 // selection is not in a table cell or caption, for example parent is a 6652 // `<tr>`. Avoid this unnecessary splitting if possible by leaving 6653 // createEmptyDivElement false so that we fall through to the normal case 6654 // alignment code. 6655 // 6656 // XXX: It seems a little error prone for the createEmptyDivElement 6657 // special case code to assume that the start node of the selection 6658 // is the parent of the single node in the arrayOfContents, as the 6659 // paragraph above points out. Do we rely on the selection start 6660 // node because of the fact that arrayOfContents can be empty? We 6661 // should probably revisit this issue. - kin 6662 6663 const EditorDOMPoint firstRangeStartPoint = 6664 pointToPutCaret.IsSet() 6665 ? pointToPutCaret 6666 : aRanges.GetFirstRangeStartPoint<EditorDOMPoint>(); 6667 if (NS_WARN_IF(!firstRangeStartPoint.IsInContentNode())) { 6668 return NS_ERROR_FAILURE; 6669 } 6670 nsIContent& parent = *firstRangeStartPoint.ContainerAs<nsIContent>(); 6671 createEmptyDivElement = 6672 !HTMLEditUtils::IsAnyTableElementExceptColumnElement(parent) || 6673 HTMLEditUtils::IsTableCellOrCaptionElement(parent); 6674 } 6675 } 6676 6677 if (createEmptyDivElement) { 6678 if (MOZ_UNLIKELY(!pointToPutCaret.IsSet() && !aRanges.IsInContent())) { 6679 NS_WARNING("Mutation event listener might have changed the selection"); 6680 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE; 6681 } 6682 const EditorDOMPoint pointToInsertDivElement = 6683 pointToPutCaret.IsSet() 6684 ? pointToPutCaret 6685 : aRanges.GetFirstRangeStartPoint<EditorDOMPoint>(); 6686 Result<CreateElementResult, nsresult> insertNewDivElementResult = 6687 InsertDivElementToAlignContents(pointToInsertDivElement, aAlignType, 6688 aEditingHost); 6689 if (insertNewDivElementResult.isErr()) { 6690 NS_WARNING("HTMLEditor::InsertDivElementToAlignContents() failed"); 6691 return insertNewDivElementResult.unwrapErr(); 6692 } 6693 CreateElementResult unwrappedInsertNewDivElementResult = 6694 insertNewDivElementResult.unwrap(); 6695 aRanges.ClearSavedRanges(); 6696 EditorDOMPoint pointToPutCaret = 6697 unwrappedInsertNewDivElementResult.UnwrapCaretPoint(); 6698 nsresult rv = aRanges.Collapse(pointToPutCaret); 6699 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6700 "AutoClonedRangeArray::Collapse() failed"); 6701 return rv; 6702 } 6703 6704 Result<CreateElementResult, nsresult> maybeCreateDivElementResult = 6705 AlignNodesAndDescendants(arrayOfContents, aAlignType, aEditingHost); 6706 if (MOZ_UNLIKELY(maybeCreateDivElementResult.isErr())) { 6707 NS_WARNING("HTMLEditor::AlignNodesAndDescendants() failed"); 6708 return maybeCreateDivElementResult.unwrapErr(); 6709 } 6710 maybeCreateDivElementResult.inspect().IgnoreCaretPointSuggestion(); 6711 6712 MOZ_ASSERT(aRanges.HasSavedRanges()); 6713 aRanges.RestoreFromSavedRanges(); 6714 // If restored range is collapsed outside the latest cased <div> element, 6715 // we should move caret into the <div>. 6716 if (maybeCreateDivElementResult.inspect().GetNewNode() && 6717 aRanges.IsCollapsed() && !aRanges.Ranges().IsEmpty()) { 6718 const auto firstRangeStartRawPoint = 6719 aRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>(); 6720 if (MOZ_LIKELY(firstRangeStartRawPoint.IsSet())) { 6721 Result<EditorRawDOMPoint, nsresult> pointInNewDivOrError = 6722 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside< 6723 EditorRawDOMPoint>( 6724 *maybeCreateDivElementResult.inspect().GetNewNode(), 6725 firstRangeStartRawPoint); 6726 if (MOZ_UNLIKELY(pointInNewDivOrError.isErr())) { 6727 NS_WARNING( 6728 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, " 6729 "but ignored"); 6730 } else if (pointInNewDivOrError.inspect().IsSet()) { 6731 nsresult rv = aRanges.Collapse(pointInNewDivOrError.unwrap()); 6732 if (NS_FAILED(rv)) { 6733 NS_WARNING("AutoClonedRangeArray::Collapse() failed"); 6734 return rv; 6735 } 6736 } 6737 } 6738 } 6739 return NS_OK; 6740 } 6741 6742 Result<CreateElementResult, nsresult> 6743 HTMLEditor::InsertDivElementToAlignContents( 6744 const EditorDOMPoint& aPointToInsert, const nsAString& aAlignType, 6745 const Element& aEditingHost) { 6746 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); 6747 MOZ_ASSERT(!IsSelectionRangeContainerNotContent()); 6748 MOZ_ASSERT(aPointToInsert.IsSetAndValid()); 6749 6750 if (NS_WARN_IF(!aPointToInsert.IsSet())) { 6751 return Err(NS_ERROR_FAILURE); 6752 } 6753 6754 Result<CreateElementResult, nsresult> createNewDivElementResult = 6755 InsertElementWithSplittingAncestorsWithTransaction( 6756 *nsGkAtoms::div, aPointToInsert, BRElementNextToSplitPoint::Delete, 6757 aEditingHost); 6758 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) { 6759 NS_WARNING( 6760 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(" 6761 "nsGkAtoms::div, BRElementNextToSplitPoint::Delete) failed"); 6762 return createNewDivElementResult; 6763 } 6764 CreateElementResult unwrappedCreateNewDivElementResult = 6765 createNewDivElementResult.unwrap(); 6766 // We'll suggest start of the new <div>, so we don't need the suggested 6767 // position. 6768 unwrappedCreateNewDivElementResult.IgnoreCaretPointSuggestion(); 6769 6770 MOZ_ASSERT(unwrappedCreateNewDivElementResult.GetNewNode()); 6771 RefPtr<Element> newDivElement = 6772 unwrappedCreateNewDivElementResult.UnwrapNewNode(); 6773 // Set up the alignment on the div, using HTML or CSS 6774 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 6775 SetBlockElementAlign(*newDivElement, aAlignType, 6776 EditTarget::OnlyDescendantsExceptTable); 6777 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 6778 NS_WARNING( 6779 "HTMLEditor::SetBlockElementAlign(EditTarget::" 6780 "OnlyDescendantsExceptTable) failed"); 6781 return pointToPutCaretOrError.propagateErr(); 6782 } 6783 // We don't need the new suggested position too. 6784 6785 // Put in a padding <br> element for empty last line so that it won't get 6786 // deleted. 6787 { 6788 Result<CreateElementResult, nsresult> insertPaddingBRElementResult = 6789 InsertPaddingBRElementForEmptyLastLineWithTransaction( 6790 EditorDOMPoint(newDivElement, 0u)); 6791 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) { 6792 NS_WARNING( 6793 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() " 6794 "failed"); 6795 return insertPaddingBRElementResult; 6796 } 6797 insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion(); 6798 } 6799 6800 return CreateElementResult(std::move(newDivElement), 6801 EditorDOMPoint(newDivElement, 0u)); 6802 } 6803 6804 Result<CreateElementResult, nsresult> HTMLEditor::AlignNodesAndDescendants( 6805 nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, 6806 const nsAString& aAlignType, const Element& aEditingHost) { 6807 // Detect all the transitions in the array, where a transition means that 6808 // adjacent nodes in the array don't have the same parent. 6809 AutoTArray<bool, 64> transitionList; 6810 HTMLEditor::MakeTransitionList(aArrayOfContents, transitionList); 6811 6812 RefPtr<Element> latestCreatedDivElement; 6813 EditorDOMPoint pointToPutCaret; 6814 6815 // Okay, now go through all the nodes and give them an align attrib or put 6816 // them in a div, or whatever is appropriate. Woohoo! 6817 6818 RefPtr<Element> createdDivElement; 6819 const bool useCSS = IsCSSEnabled(); 6820 for (size_t i = 0; i < aArrayOfContents.Length(); i++) { 6821 const OwningNonNull<nsIContent>& content = aArrayOfContents[i]; 6822 6823 // Ignore all non-editable nodes. Leave them be. 6824 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) { 6825 continue; 6826 } 6827 6828 // The node is a table element, an hr, a paragraph, a div or a section 6829 // header; in HTML 4, it can directly carry the ALIGN attribute and we 6830 // don't need to nest it, just set the alignment. In CSS, assign the 6831 // corresponding CSS styles in SetBlockElementAlign(). 6832 if (HTMLEditUtils::IsAlignAttrSupported(content)) { 6833 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 6834 SetBlockElementAlign(MOZ_KnownLive(*content->AsElement()), aAlignType, 6835 EditTarget::NodeAndDescendantsExceptTable); 6836 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 6837 NS_WARNING( 6838 "HTMLEditor::SetBlockElementAlign(EditTarget::" 6839 "NodeAndDescendantsExceptTable) failed"); 6840 return pointToPutCaretOrError.propagateErr(); 6841 } 6842 if (pointToPutCaretOrError.inspect().IsSet()) { 6843 pointToPutCaret = pointToPutCaretOrError.unwrap(); 6844 } 6845 // Clear out createdDivElement so that we don't put nodes after this one 6846 // into it 6847 createdDivElement = nullptr; 6848 continue; 6849 } 6850 6851 EditorDOMPoint atContent(content); 6852 if (NS_WARN_IF(!atContent.IsInContentNode())) { 6853 continue; 6854 } 6855 6856 // Skip insignificant formatting text nodes to prevent unnecessary 6857 // structure splitting! 6858 if (content->IsText() && 6859 ((HTMLEditUtils::IsAnyTableElementExceptColumnElement( 6860 *atContent.ContainerAs<nsIContent>()) && 6861 !HTMLEditUtils::IsTableCellOrCaptionElement( 6862 *atContent.ContainerAs<nsIContent>())) || 6863 HTMLEditUtils::IsListElement(*atContent.ContainerAs<nsIContent>()) || 6864 HTMLEditUtils::IsEmptyNode( 6865 *content, 6866 {EmptyCheckOption::TreatSingleBRElementAsVisible, 6867 EmptyCheckOption::TreatNonEditableContentAsInvisible}))) { 6868 continue; 6869 } 6870 6871 // If it's a list item, or a list inside a list, forget any "current" div, 6872 // and instead put divs inside the appropriate block (td, li, etc.) 6873 if (HTMLEditUtils::IsListItemElement(*content) || 6874 HTMLEditUtils::IsListElement(*content)) { 6875 Element* listOrListItemElement = content->AsElement(); 6876 { 6877 AutoEditorDOMPointOffsetInvalidator lockChild(atContent); 6878 // MOZ_KnownLive(*listOrListItemElement): An element of aArrayOfContents 6879 // which is array of OwningNonNull. 6880 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 6881 RemoveAlignFromDescendants(MOZ_KnownLive(*listOrListItemElement), 6882 aAlignType, 6883 EditTarget::OnlyDescendantsExceptTable); 6884 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 6885 NS_WARNING( 6886 "HTMLEditor::RemoveAlignFromDescendants(EditTarget::" 6887 "OnlyDescendantsExceptTable) failed"); 6888 return pointToPutCaretOrError.propagateErr(); 6889 } 6890 if (pointToPutCaretOrError.inspect().IsSet()) { 6891 pointToPutCaret = pointToPutCaretOrError.unwrap(); 6892 } 6893 } 6894 if (NS_WARN_IF(!atContent.IsSetAndValid())) { 6895 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 6896 } 6897 6898 if (useCSS) { 6899 nsStyledElement* styledListOrListItemElement = 6900 nsStyledElement::FromNode(listOrListItemElement); 6901 if (styledListOrListItemElement && 6902 EditorElementStyle::Align().IsCSSSettable( 6903 *styledListOrListItemElement)) { 6904 // MOZ_KnownLive(*styledListOrListItemElement): An element of 6905 // aArrayOfContents which is array of OwningNonNull. 6906 Result<size_t, nsresult> result = 6907 CSSEditUtils::SetCSSEquivalentToStyle( 6908 WithTransaction::Yes, *this, 6909 MOZ_KnownLive(*styledListOrListItemElement), 6910 EditorElementStyle::Align(), &aAlignType); 6911 if (MOZ_UNLIKELY(result.isErr())) { 6912 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) { 6913 return result.propagateErr(); 6914 } 6915 NS_WARNING( 6916 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::" 6917 "Align()) failed, but ignored"); 6918 } 6919 } 6920 createdDivElement = nullptr; 6921 continue; 6922 } 6923 6924 if (HTMLEditUtils::IsListElement(*atContent.ContainerAs<nsIContent>())) { 6925 // If we don't use CSS, add a content to list element: they have to 6926 // be inside another list, i.e., >= second level of nesting. 6927 // XXX AlignContentsInAllTableCellsAndListItems() handles only list 6928 // item elements and table cells. Is it intentional? Why don't 6929 // we need to align contents in other type blocks? 6930 // MOZ_KnownLive(*listOrListItemElement): An element of aArrayOfContents 6931 // which is array of OwningNonNull. 6932 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 6933 AlignContentsInAllTableCellsAndListItems( 6934 MOZ_KnownLive(*listOrListItemElement), aAlignType); 6935 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 6936 NS_WARNING( 6937 "HTMLEditor::AlignContentsInAllTableCellsAndListItems() failed"); 6938 return pointToPutCaretOrError.propagateErr(); 6939 } 6940 if (pointToPutCaretOrError.inspect().IsSet()) { 6941 pointToPutCaret = pointToPutCaretOrError.unwrap(); 6942 } 6943 createdDivElement = nullptr; 6944 continue; 6945 } 6946 6947 // Clear out createdDivElement so that we don't put nodes after this one 6948 // into it 6949 } 6950 6951 // Need to make a div to put things in if we haven't already, or if this 6952 // node doesn't go in div we used earlier. 6953 if (!createdDivElement || transitionList[i]) { 6954 // First, check that our element can contain a div. 6955 if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(), 6956 *nsGkAtoms::div)) { 6957 // XXX Why do we return "OK" here rather than returning error or 6958 // doing continue? 6959 return latestCreatedDivElement 6960 ? CreateElementResult(std::move(latestCreatedDivElement), 6961 std::move(pointToPutCaret)) 6962 : CreateElementResult::NotHandled( 6963 std::move(pointToPutCaret)); 6964 } 6965 6966 Result<CreateElementResult, nsresult> createNewDivElementResult = 6967 InsertElementWithSplittingAncestorsWithTransaction( 6968 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep, 6969 aEditingHost); 6970 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) { 6971 NS_WARNING( 6972 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(" 6973 "nsGkAtoms::div) failed"); 6974 return createNewDivElementResult; 6975 } 6976 CreateElementResult unwrappedCreateNewDivElementResult = 6977 createNewDivElementResult.unwrap(); 6978 if (unwrappedCreateNewDivElementResult.HasCaretPointSuggestion()) { 6979 pointToPutCaret = unwrappedCreateNewDivElementResult.UnwrapCaretPoint(); 6980 } 6981 6982 MOZ_ASSERT(unwrappedCreateNewDivElementResult.GetNewNode()); 6983 createdDivElement = unwrappedCreateNewDivElementResult.UnwrapNewNode(); 6984 // Set up the alignment on the div 6985 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 6986 SetBlockElementAlign(*createdDivElement, aAlignType, 6987 EditTarget::OnlyDescendantsExceptTable); 6988 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 6989 if (NS_WARN_IF(pointToPutCaretOrError.inspectErr() == 6990 NS_ERROR_EDITOR_DESTROYED)) { 6991 return pointToPutCaretOrError.propagateErr(); 6992 } 6993 NS_WARNING( 6994 "HTMLEditor::SetBlockElementAlign(EditTarget::" 6995 "OnlyDescendantsExceptTable) failed, but ignored"); 6996 } else if (pointToPutCaretOrError.inspect().IsSet()) { 6997 pointToPutCaret = pointToPutCaretOrError.unwrap(); 6998 } 6999 latestCreatedDivElement = createdDivElement; 7000 } 7001 7002 const OwningNonNull<nsIContent> lastContent = [&]() { 7003 nsIContent* lastContent = content; 7004 for (; i + 1 < aArrayOfContents.Length(); i++) { 7005 const OwningNonNull<nsIContent>& nextContent = aArrayOfContents[i + 1]; 7006 if (lastContent->GetNextSibling() != nextContent || 7007 !EditorUtils::IsEditableContent(content, EditorType::HTML) || 7008 !HTMLEditUtils::IsAlignAttrSupported(nextContent) || 7009 // If we meets an invisible `Text` in table or list, we don't move 7010 // it to avoid to handle ancestors for them. However, ignoring the 7011 // empty `Text` nodes is more expensive than moving them here. 7012 // Therefore, here does not check whether the following sibling of 7013 // `content` is an empty `Text`. 7014 7015 // In some cases, we reach here even if `content` is a list or a 7016 // list item. However, anyway we need to run a preparation for such 7017 // element. Therefore, we cannot move such type of elements with 7018 // `content` here. 7019 HTMLEditUtils::IsListItemElement(*nextContent) || 7020 HTMLEditUtils::IsListElement(*nextContent) || 7021 // Similarly, if the sibling is in the transitionList, we need to 7022 // handle it separately. 7023 transitionList[i + 1]) { 7024 break; 7025 } 7026 lastContent = nextContent; 7027 } 7028 return OwningNonNull<nsIContent>(*lastContent); 7029 }(); 7030 7031 // Tuck the node into the end of the active div 7032 // 7033 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it alive. 7034 Result<MoveNodeResult, nsresult> moveNodeResult = 7035 MoveSiblingsToEndWithTransaction(MOZ_KnownLive(content), lastContent, 7036 *createdDivElement); 7037 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 7038 NS_WARNING("HTMLEditor::MoveSiblingsToEndWithTransaction() failed"); 7039 return moveNodeResult.propagateErr(); 7040 } 7041 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap(); 7042 if (unwrappedMoveNodeResult.HasCaretPointSuggestion()) { 7043 pointToPutCaret = unwrappedMoveNodeResult.UnwrapCaretPoint(); 7044 } 7045 } 7046 7047 return latestCreatedDivElement 7048 ? CreateElementResult(std::move(latestCreatedDivElement), 7049 std::move(pointToPutCaret)) 7050 : CreateElementResult::NotHandled(std::move(pointToPutCaret)); 7051 } 7052 7053 Result<EditorDOMPoint, nsresult> 7054 HTMLEditor::AlignContentsInAllTableCellsAndListItems( 7055 Element& aElement, const nsAString& aAlignType) { 7056 MOZ_ASSERT(IsEditActionDataAvailable()); 7057 7058 // Gather list of table cells or list items 7059 AutoTArray<OwningNonNull<Element>, 64> arrayOfTableCellsAndListItems; 7060 DOMIterator iter(aElement); 7061 iter.AppendNodesToArray( 7062 +[](nsINode& aNode, void*) -> bool { 7063 MOZ_ASSERT(Element::FromNode(&aNode)); 7064 return HTMLEditUtils::IsTableCellElement(*aNode.AsElement()) || 7065 HTMLEditUtils::IsListItemElement(*aNode.AsElement()); 7066 }, 7067 arrayOfTableCellsAndListItems); 7068 7069 // Now that we have the list, align their contents as requested 7070 EditorDOMPoint pointToPutCaret; 7071 for (auto& tableCellOrListItemElement : arrayOfTableCellsAndListItems) { 7072 // MOZ_KnownLive because 'arrayOfTableCellsAndListItems' is guaranteed to 7073 // keep it alive. 7074 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 7075 AlignBlockContentsWithDivElement( 7076 MOZ_KnownLive(tableCellOrListItemElement), aAlignType); 7077 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 7078 NS_WARNING("HTMLEditor::AlignBlockContentsWithDivElement() failed"); 7079 return pointToPutCaretOrError; 7080 } 7081 if (pointToPutCaretOrError.inspect().IsSet()) { 7082 pointToPutCaret = pointToPutCaretOrError.unwrap(); 7083 } 7084 } 7085 7086 return pointToPutCaret; 7087 } 7088 7089 Result<EditorDOMPoint, nsresult> HTMLEditor::AlignBlockContentsWithDivElement( 7090 Element& aBlockElement, const nsAString& aAlignType) { 7091 MOZ_ASSERT(IsEditActionDataAvailable()); 7092 7093 // XXX Chrome wraps text into a <div> only when the container has different 7094 // blocks. At that time, Chrome seems treating non-editable nodes as a line 7095 // break. So, their behavior is also odd so that it does not make sense to 7096 // follow their behavior when there is non-editable content. 7097 7098 // XXX I don't understand why we should NOT align non-editable children 7099 // with modifying EDITABLE `<div>` element. 7100 const nsCOMPtr<nsIContent> firstEditableContent = 7101 HTMLEditUtils::GetFirstChild(aBlockElement, 7102 {WalkTreeOption::IgnoreNonEditableNode}); 7103 if (!firstEditableContent) { 7104 // This block has no editable content, nothing to align. 7105 return EditorDOMPoint(); 7106 } 7107 7108 // If there is only one editable content and it's a `<div>` element, 7109 // just set `align` attribute of it. 7110 const nsCOMPtr<nsIContent> lastEditableContent = HTMLEditUtils::GetLastChild( 7111 aBlockElement, {WalkTreeOption::IgnoreNonEditableNode}); 7112 if (firstEditableContent == lastEditableContent && 7113 firstEditableContent->IsHTMLElement(nsGkAtoms::div)) { 7114 // XXX Chrome uses `style="text-align: foo"` instead of the legacy `align` 7115 // attribute. That does not allow to align the child blocks center and they 7116 // put the style to every blocks in the selection range. So, it requires a 7117 // complicated change to follow their behavior. 7118 nsresult rv = SetAttributeOrEquivalent( 7119 MOZ_KnownLive(firstEditableContent->AsElement()), nsGkAtoms::align, 7120 aAlignType, false); 7121 if (NS_WARN_IF(Destroyed())) { 7122 NS_WARNING( 7123 "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) caused " 7124 "destroying the editor"); 7125 return Err(NS_ERROR_EDITOR_DESTROYED); 7126 } 7127 if (NS_FAILED(rv)) { 7128 NS_WARNING( 7129 "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed"); 7130 return Err(rv); 7131 } 7132 return EditorDOMPoint(); 7133 } 7134 7135 // Otherwise, we need to insert a `<div>` element to set `align` attribute. 7136 Result<CreateElementResult, nsresult> createNewDivElementResultOrError = 7137 CreateAndInsertElement( 7138 WithTransaction::Yes, *nsGkAtoms::div, 7139 EditorDOMPoint(&aBlockElement, 0u), 7140 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868 7141 [&](HTMLEditor& aHTMLEditor, Element& aDivElement, 7142 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY { 7143 MOZ_ASSERT(!aDivElement.IsInComposedDoc()); 7144 // If aDivElement has not been connected yet, we do not need 7145 // transaction of setting align attribute here. 7146 nsresult rv = aHTMLEditor.SetAttributeOrEquivalent( 7147 &aDivElement, nsGkAtoms::align, aAlignType, false); 7148 if (NS_FAILED(rv)) { 7149 NS_WARNING( 7150 "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align, " 7151 "\"...\", false) failed"); 7152 return rv; 7153 } 7154 if (!aBlockElement.HasChildren()) { 7155 return NS_OK; 7156 } 7157 // FIXME: This will move non-editable children between 7158 // firstEditableContent and lastEditableContent. So, the design of 7159 // this method is odd. 7160 Result<MoveNodeResult, nsresult> moveChildrenResultOrError = 7161 aHTMLEditor.MoveSiblingsWithTransaction( 7162 *firstEditableContent, *lastEditableContent, 7163 EditorDOMPoint(&aDivElement, 0)); 7164 if (MOZ_UNLIKELY(moveChildrenResultOrError.isErr())) { 7165 NS_WARNING_ASSERTION( 7166 moveChildrenResultOrError.isOk(), 7167 "HTMLEditor::MoveSiblingsWithTransaction() failed"); 7168 return moveChildrenResultOrError.unwrapErr(); 7169 } 7170 moveChildrenResultOrError.unwrap().IgnoreCaretPointSuggestion(); 7171 return NS_OK; 7172 }); 7173 if (MOZ_UNLIKELY(createNewDivElementResultOrError.isErr())) { 7174 NS_WARNING( 7175 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes, " 7176 "nsGkAtoms::div) failed"); 7177 return createNewDivElementResultOrError.propagateErr(); 7178 } 7179 return createNewDivElementResultOrError.unwrap().UnwrapCaretPoint(); 7180 } 7181 7182 Result<EditorRawDOMRange, nsresult> 7183 HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction( 7184 const nsRange* aRange, const Element& aEditingHost) const { 7185 MOZ_ASSERT(IsEditActionDataAvailable()); 7186 7187 // This tweaks selections to be more "natural". 7188 // Idea here is to adjust edges of selection ranges so that they do not cross 7189 // breaks or block boundaries unless something editable beyond that boundary 7190 // is also selected. This adjustment makes it much easier for the various 7191 // block operations to determine what nodes to act on. 7192 if (NS_WARN_IF(!aRange) || NS_WARN_IF(!aRange->IsPositioned())) { 7193 return Err(NS_ERROR_FAILURE); 7194 } 7195 7196 const EditorRawDOMPoint startPoint(aRange->StartRef()); 7197 if (NS_WARN_IF(!startPoint.IsSet())) { 7198 return Err(NS_ERROR_FAILURE); 7199 } 7200 const EditorRawDOMPoint endPoint(aRange->EndRef()); 7201 if (NS_WARN_IF(!endPoint.IsSet())) { 7202 return Err(NS_ERROR_FAILURE); 7203 } 7204 7205 // adjusted values default to original values 7206 EditorRawDOMRange newRange(startPoint, endPoint); 7207 7208 { 7209 // Is there any intervening visible white-space? If so we can't push 7210 // selection past that, it would visibly change meaning of users selection. 7211 const WSScanResult prevVisibleThingOfEndPoint = 7212 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 7213 { 7214 // We should refer only the default style of HTML because we 7215 // need to wrap any elements with a specific HTML element. So 7216 // we should not refer actual style. For example, we want to 7217 // reformat parent HTML block element even if selected in a 7218 // blocked phrase element or non-HTMLelement. 7219 WSRunScanner::Option::ReferHTMLDefaultStyle, 7220 }, 7221 endPoint, &aEditingHost); 7222 if (MOZ_UNLIKELY(prevVisibleThingOfEndPoint.Failed())) { 7223 NS_WARNING( 7224 "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary() failed"); 7225 return Err(NS_ERROR_FAILURE); 7226 } 7227 if (prevVisibleThingOfEndPoint.ReachedSomethingNonTextContent()) { 7228 // eThisBlock and eOtherBlock conveniently distinguish cases 7229 // of going "down" into a block and "up" out of a block. 7230 if (prevVisibleThingOfEndPoint.ReachedOtherBlockElement()) { 7231 // endpoint is just after the close of a block. 7232 if (nsIContent* child = HTMLEditUtils::GetLastLeafContent( 7233 *prevVisibleThingOfEndPoint.ElementPtr(), 7234 {LeafNodeType::LeafNodeOrChildBlock}, 7235 BlockInlineCheck::UseHTMLDefaultStyle)) { 7236 newRange.SetEnd(EditorRawDOMPoint::After(*child)); 7237 } 7238 // else block is empty - we can leave selection alone here, i think. 7239 } else if (prevVisibleThingOfEndPoint.ReachedCurrentBlockBoundary() || 7240 prevVisibleThingOfEndPoint 7241 .ReachedInlineEditingHostBoundary()) { 7242 // endpoint is just after start of this block 7243 if (nsIContent* child = HTMLEditUtils::GetPreviousContent( 7244 endPoint, {WalkTreeOption::IgnoreNonEditableNode}, 7245 BlockInlineCheck::UseHTMLDefaultStyle, &aEditingHost)) { 7246 newRange.SetEnd(EditorRawDOMPoint::After(*child)); 7247 } 7248 // else block is empty - we can leave selection alone here, i think. 7249 } else if (prevVisibleThingOfEndPoint.ReachedBRElement()) { 7250 // endpoint is just after break. lets adjust it to before it. 7251 newRange.SetEnd(prevVisibleThingOfEndPoint 7252 .PointAtReachedContent<EditorRawDOMPoint>()); 7253 } 7254 } 7255 } 7256 { 7257 // Is there any intervening visible white-space? If so we can't push 7258 // selection past that, it would visibly change meaning of users selection. 7259 const WSScanResult nextVisibleThingOfStartPoint = 7260 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 7261 {WSRunScanner::Option::ReferHTMLDefaultStyle}, startPoint, 7262 &aEditingHost); 7263 if (MOZ_UNLIKELY(nextVisibleThingOfStartPoint.Failed())) { 7264 NS_WARNING( 7265 "WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary() failed"); 7266 return Err(NS_ERROR_FAILURE); 7267 } 7268 if (nextVisibleThingOfStartPoint.ReachedSomethingNonTextContent()) { 7269 // eThisBlock and eOtherBlock conveniently distinguish cases 7270 // of going "down" into a block and "up" out of a block. 7271 if (nextVisibleThingOfStartPoint.ReachedOtherBlockElement()) { 7272 // startpoint is just before the start of a block. 7273 if (nsIContent* child = HTMLEditUtils::GetFirstLeafContent( 7274 *nextVisibleThingOfStartPoint.ElementPtr(), 7275 {LeafNodeType::LeafNodeOrChildBlock}, 7276 BlockInlineCheck::UseHTMLDefaultStyle)) { 7277 newRange.SetStart(EditorRawDOMPoint(child)); 7278 } 7279 // else block is empty - we can leave selection alone here, i think. 7280 } else if (nextVisibleThingOfStartPoint.ReachedCurrentBlockBoundary() || 7281 nextVisibleThingOfStartPoint 7282 .ReachedInlineEditingHostBoundary()) { 7283 // startpoint is just before end of this block 7284 if (nsIContent* child = HTMLEditUtils::GetNextContent( 7285 startPoint, {WalkTreeOption::IgnoreNonEditableNode}, 7286 BlockInlineCheck::UseHTMLDefaultStyle, &aEditingHost)) { 7287 newRange.SetStart(EditorRawDOMPoint(child)); 7288 } 7289 // else block is empty - we can leave selection alone here, i think. 7290 } else if (nextVisibleThingOfStartPoint.ReachedBRElement()) { 7291 // startpoint is just before a break. lets adjust it to after it. 7292 // XXX If it's an invisible <br>, does this work? Will the following 7293 // checks solve that? 7294 newRange.SetStart(nextVisibleThingOfStartPoint 7295 .PointAfterReachedContent<EditorRawDOMPoint>()); 7296 } 7297 } 7298 } 7299 7300 // There is a demented possibility we have to check for. We might have a very 7301 // strange selection that is not collapsed and yet does not contain any 7302 // editable content, and satisfies some of the above conditions that cause 7303 // tweaking. In this case we don't want to tweak the selection into a block 7304 // it was never in, etc. There are a variety of strategies one might use to 7305 // try to detect these cases, but I think the most straightforward is to see 7306 // if the adjusted locations "cross" the old values: i.e., new end before old 7307 // start, or new start after old end. If so then just leave things alone. 7308 7309 Maybe<int32_t> comp = nsContentUtils::ComparePoints( 7310 startPoint.ToRawRangeBoundary(), newRange.EndRef().ToRawRangeBoundary()); 7311 7312 if (NS_WARN_IF(!comp)) { 7313 return Err(NS_ERROR_FAILURE); 7314 } 7315 7316 if (*comp == 1) { 7317 return EditorRawDOMRange(); // New end before old start. 7318 } 7319 7320 comp = nsContentUtils::ComparePoints(newRange.StartRef().ToRawRangeBoundary(), 7321 endPoint.ToRawRangeBoundary()); 7322 7323 if (NS_WARN_IF(!comp)) { 7324 return Err(NS_ERROR_FAILURE); 7325 } 7326 7327 if (*comp == 1) { 7328 return EditorRawDOMRange(); // New start after old end. 7329 } 7330 7331 return newRange; 7332 } 7333 7334 template <typename EditorDOMRangeType> 7335 already_AddRefed<nsRange> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces( 7336 const EditorDOMRangeType& aRange) { 7337 MOZ_DIAGNOSTIC_ASSERT(aRange.IsPositioned()); 7338 return CreateRangeIncludingAdjuscentWhiteSpaces(aRange.StartRef(), 7339 aRange.EndRef()); 7340 } 7341 7342 template <typename EditorDOMPointType1, typename EditorDOMPointType2> 7343 already_AddRefed<nsRange> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces( 7344 const EditorDOMPointType1& aStartPoint, 7345 const EditorDOMPointType2& aEndPoint) { 7346 MOZ_DIAGNOSTIC_ASSERT(!aStartPoint.IsInNativeAnonymousSubtree()); 7347 MOZ_DIAGNOSTIC_ASSERT(!aEndPoint.IsInNativeAnonymousSubtree()); 7348 7349 if (!aStartPoint.IsInContentNode() || !aEndPoint.IsInContentNode()) { 7350 NS_WARNING_ASSERTION(aStartPoint.IsSet(), "aStartPoint was not set"); 7351 NS_WARNING_ASSERTION(aEndPoint.IsSet(), "aEndPoint was not set"); 7352 return nullptr; 7353 } 7354 7355 const Element* const editingHost = ComputeEditingHost(); 7356 if (NS_WARN_IF(!editingHost)) { 7357 return nullptr; 7358 } 7359 7360 EditorDOMPoint startPoint = aStartPoint.template To<EditorDOMPoint>(); 7361 EditorDOMPoint endPoint = aEndPoint.template To<EditorDOMPoint>(); 7362 AutoClonedRangeArray:: 7363 UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement( 7364 startPoint, endPoint, *editingHost); 7365 7366 if (NS_WARN_IF(!startPoint.IsInContentNode()) || 7367 NS_WARN_IF(!endPoint.IsInContentNode())) { 7368 NS_WARNING( 7369 "AutoClonedRangeArray::" 7370 "UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement() " 7371 "failed"); 7372 return nullptr; 7373 } 7374 7375 // For text actions, we want to look backwards (or forwards, as 7376 // appropriate) for additional white-space or nbsp's. We may have to act 7377 // on these later even though they are outside of the initial selection. 7378 // Even if they are in another node! 7379 // XXX Those scanners do not treat siblings of the text nodes. Perhaps, 7380 // we should use `WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo()` 7381 // and `WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces()` instead. 7382 if (startPoint.IsInTextNode()) { 7383 while (!startPoint.IsStartOfContainer()) { 7384 if (!startPoint.IsPreviousCharASCIISpaceOrNBSP()) { 7385 break; 7386 } 7387 MOZ_ALWAYS_TRUE(startPoint.RewindOffset()); 7388 } 7389 } 7390 if (!startPoint.GetChildOrContainerIfDataNode() || 7391 !startPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf( 7392 editingHost)) { 7393 return nullptr; 7394 } 7395 if (endPoint.IsInTextNode()) { 7396 while (!endPoint.IsEndOfContainer()) { 7397 if (!endPoint.IsCharASCIISpaceOrNBSP()) { 7398 break; 7399 } 7400 MOZ_ALWAYS_TRUE(endPoint.AdvanceOffset()); 7401 } 7402 } 7403 EditorDOMPoint lastRawPoint(endPoint); 7404 if (!lastRawPoint.IsStartOfContainer()) { 7405 lastRawPoint.RewindOffset(); 7406 } 7407 if (!lastRawPoint.GetChildOrContainerIfDataNode() || 7408 !lastRawPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf( 7409 editingHost)) { 7410 return nullptr; 7411 } 7412 7413 RefPtr<nsRange> range = 7414 nsRange::Create(startPoint.ToRawRangeBoundary(), 7415 endPoint.ToRawRangeBoundary(), IgnoreErrors()); 7416 NS_WARNING_ASSERTION(range, "nsRange::Create() failed"); 7417 return range.forget(); 7418 } 7419 7420 Result<EditorDOMPoint, nsresult> HTMLEditor::MaybeSplitElementsAtEveryBRElement( 7421 nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, 7422 EditSubAction aEditSubAction) { 7423 // Post-process the list to break up inline containers that contain br's, but 7424 // only for operations that might care, like making lists or paragraphs 7425 switch (aEditSubAction) { 7426 case EditSubAction::eCreateOrRemoveBlock: 7427 case EditSubAction::eFormatBlockForHTMLCommand: 7428 case EditSubAction::eMergeBlockContents: 7429 case EditSubAction::eCreateOrChangeList: 7430 case EditSubAction::eSetOrClearAlignment: 7431 case EditSubAction::eSetPositionToAbsolute: 7432 case EditSubAction::eIndent: 7433 case EditSubAction::eOutdent: { 7434 EditorDOMPoint pointToPutCaret; 7435 for (size_t index : Reversed(IntegerRange(aArrayOfContents.Length()))) { 7436 OwningNonNull<nsIContent>& content = aArrayOfContents[index]; 7437 if (HTMLEditUtils::IsInlineContent( 7438 content, BlockInlineCheck::UseHTMLDefaultStyle) && 7439 HTMLEditUtils::IsContainerNode(content) && !content->IsText()) { 7440 AutoTArray<OwningNonNull<nsIContent>, 24> arrayOfInlineContents; 7441 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it 7442 // alive. 7443 Result<EditorDOMPoint, nsresult> splitResult = 7444 SplitElementsAtEveryBRElement(MOZ_KnownLive(content), 7445 arrayOfInlineContents); 7446 if (splitResult.isErr()) { 7447 NS_WARNING("HTMLEditor::SplitElementsAtEveryBRElement() failed"); 7448 return splitResult; 7449 } 7450 if (splitResult.inspect().IsSet()) { 7451 pointToPutCaret = splitResult.unwrap(); 7452 } 7453 // Put these nodes in aArrayOfContents, replacing the current node 7454 aArrayOfContents.RemoveElementAt(index); 7455 aArrayOfContents.InsertElementsAt(index, arrayOfInlineContents); 7456 } 7457 } 7458 return pointToPutCaret; 7459 } 7460 default: 7461 return EditorDOMPoint(); 7462 } 7463 } 7464 7465 Result<EditorDOMPoint, nsresult> 7466 HTMLEditor::SplitInlineAncestorsAtRangeBoundaries( 7467 RangeItem& aRangeItem, BlockInlineCheck aBlockInlineCheck, 7468 const Element& aEditingHost, 7469 const nsIContent* aAncestorLimiter /* = nullptr */) { 7470 MOZ_ASSERT(IsEditActionDataAvailable()); 7471 7472 EditorDOMPoint pointToPutCaret; 7473 if (!aRangeItem.Collapsed() && aRangeItem.mEndContainer && 7474 aRangeItem.mEndContainer->IsContent()) { 7475 nsCOMPtr<nsIContent> mostAncestorInlineContentAtEnd = 7476 HTMLEditUtils::GetMostDistantAncestorInlineElement( 7477 *aRangeItem.mEndContainer->AsContent(), aBlockInlineCheck, 7478 &aEditingHost, aAncestorLimiter); 7479 7480 if (mostAncestorInlineContentAtEnd) { 7481 Result<SplitNodeResult, nsresult> splitEndInlineResult = 7482 SplitNodeDeepWithTransaction( 7483 *mostAncestorInlineContentAtEnd, aRangeItem.EndPoint(), 7484 SplitAtEdges::eDoNotCreateEmptyContainer); 7485 if (MOZ_UNLIKELY(splitEndInlineResult.isErr())) { 7486 NS_WARNING( 7487 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::" 7488 "eDoNotCreateEmptyContainer) failed"); 7489 return splitEndInlineResult.propagateErr(); 7490 } 7491 SplitNodeResult unwrappedSplitEndInlineResult = 7492 splitEndInlineResult.unwrap(); 7493 unwrappedSplitEndInlineResult.MoveCaretPointTo( 7494 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 7495 if (pointToPutCaret.IsInContentNode() && 7496 MOZ_UNLIKELY( 7497 &aEditingHost != 7498 ComputeEditingHost(*pointToPutCaret.ContainerAs<nsIContent>()))) { 7499 NS_WARNING( 7500 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::" 7501 "eDoNotCreateEmptyContainer) caused changing editing host"); 7502 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 7503 } 7504 const auto splitPointAtEnd = 7505 unwrappedSplitEndInlineResult.AtSplitPoint<EditorRawDOMPoint>(); 7506 if (MOZ_UNLIKELY(!splitPointAtEnd.IsSet())) { 7507 NS_WARNING( 7508 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::" 7509 "eDoNotCreateEmptyContainer) didn't return split point"); 7510 return Err(NS_ERROR_FAILURE); 7511 } 7512 aRangeItem.mEndContainer = splitPointAtEnd.GetContainer(); 7513 aRangeItem.mEndOffset = splitPointAtEnd.Offset(); 7514 } 7515 } 7516 7517 if (!aRangeItem.mStartContainer || !aRangeItem.mStartContainer->IsContent()) { 7518 return pointToPutCaret; 7519 } 7520 7521 nsCOMPtr<nsIContent> mostAncestorInlineContentAtStart = 7522 HTMLEditUtils::GetMostDistantAncestorInlineElement( 7523 *aRangeItem.mStartContainer->AsContent(), aBlockInlineCheck, 7524 &aEditingHost, aAncestorLimiter); 7525 7526 if (mostAncestorInlineContentAtStart) { 7527 Result<SplitNodeResult, nsresult> splitStartInlineResult = 7528 SplitNodeDeepWithTransaction(*mostAncestorInlineContentAtStart, 7529 aRangeItem.StartPoint(), 7530 SplitAtEdges::eDoNotCreateEmptyContainer); 7531 if (MOZ_UNLIKELY(splitStartInlineResult.isErr())) { 7532 NS_WARNING( 7533 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::" 7534 "eDoNotCreateEmptyContainer) failed"); 7535 return splitStartInlineResult.propagateErr(); 7536 } 7537 SplitNodeResult unwrappedSplitStartInlineResult = 7538 splitStartInlineResult.unwrap(); 7539 // XXX Why don't we check editing host like above?? 7540 unwrappedSplitStartInlineResult.MoveCaretPointTo( 7541 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 7542 // XXX If we split only here because of collapsed range, we're modifying 7543 // only start point of aRangeItem. Shouldn't we modify end point here 7544 // if it's collapsed? 7545 const auto splitPointAtStart = 7546 unwrappedSplitStartInlineResult.AtSplitPoint<EditorRawDOMPoint>(); 7547 if (MOZ_UNLIKELY(!splitPointAtStart.IsSet())) { 7548 NS_WARNING( 7549 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::" 7550 "eDoNotCreateEmptyContainer) didn't return split point"); 7551 return Err(NS_ERROR_FAILURE); 7552 } 7553 aRangeItem.mStartContainer = splitPointAtStart.GetContainer(); 7554 aRangeItem.mStartOffset = splitPointAtStart.Offset(); 7555 } 7556 7557 return pointToPutCaret; 7558 } 7559 7560 Result<EditorDOMPoint, nsresult> HTMLEditor::SplitElementsAtEveryBRElement( 7561 nsIContent& aMostAncestorToBeSplit, 7562 nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents) { 7563 MOZ_ASSERT(IsEditActionDataAvailable()); 7564 7565 // First build up a list of all the break nodes inside the inline container. 7566 AutoTArray<OwningNonNull<HTMLBRElement>, 24> arrayOfBRElements; 7567 DOMIterator iter(aMostAncestorToBeSplit); 7568 iter.AppendAllNodesToArray(arrayOfBRElements); 7569 7570 // If there aren't any breaks, just put inNode itself in the array 7571 if (arrayOfBRElements.IsEmpty()) { 7572 aOutArrayOfContents.AppendElement(aMostAncestorToBeSplit); 7573 return EditorDOMPoint(); 7574 } 7575 7576 // Else we need to bust up aMostAncestorToBeSplit along all the breaks 7577 nsCOMPtr<nsIContent> nextContent = &aMostAncestorToBeSplit; 7578 EditorDOMPoint pointToPutCaret; 7579 for (OwningNonNull<HTMLBRElement>& brElement : arrayOfBRElements) { 7580 EditorDOMPoint atBRNode(brElement); 7581 if (NS_WARN_IF(!atBRNode.IsSet())) { 7582 return Err(NS_ERROR_FAILURE); 7583 } 7584 Result<SplitNodeResult, nsresult> splitNodeResult = 7585 SplitNodeDeepWithTransaction( 7586 *nextContent, atBRNode, SplitAtEdges::eAllowToCreateEmptyContainer); 7587 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 7588 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed"); 7589 return splitNodeResult.propagateErr(); 7590 } 7591 SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap(); 7592 unwrappedSplitNodeResult.MoveCaretPointTo( 7593 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 7594 // Put previous node at the split point. 7595 if (nsIContent* previousContent = 7596 unwrappedSplitNodeResult.GetPreviousContent()) { 7597 // Might not be a left node. A break might have been at the very 7598 // beginning of inline container, in which case 7599 // SplitNodeDeepWithTransaction() would not actually split anything. 7600 aOutArrayOfContents.AppendElement(*previousContent); 7601 } 7602 7603 // Move break outside of container and also put in node list 7604 // MOZ_KnownLive because 'arrayOfBRElements' is guaranteed to keep it alive. 7605 Result<MoveNodeResult, nsresult> moveBRElementResult = 7606 MoveNodeWithTransaction( 7607 MOZ_KnownLive(brElement), 7608 unwrappedSplitNodeResult.AtNextContent<EditorDOMPoint>()); 7609 if (MOZ_UNLIKELY(moveBRElementResult.isErr())) { 7610 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed"); 7611 return moveBRElementResult.propagateErr(); 7612 } 7613 MoveNodeResult unwrappedMoveBRElementResult = moveBRElementResult.unwrap(); 7614 unwrappedMoveBRElementResult.MoveCaretPointTo( 7615 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 7616 aOutArrayOfContents.AppendElement(brElement); 7617 7618 nextContent = unwrappedSplitNodeResult.GetNextContent(); 7619 } 7620 7621 // Now tack on remaining next node. 7622 aOutArrayOfContents.AppendElement(*nextContent); 7623 7624 return pointToPutCaret; 7625 } 7626 7627 // static 7628 void HTMLEditor::MakeTransitionList( 7629 const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, 7630 nsTArray<bool>& aTransitionArray) { 7631 nsINode* prevParent = nullptr; 7632 aTransitionArray.EnsureLengthAtLeast(aArrayOfContents.Length()); 7633 for (uint32_t i = 0; i < aArrayOfContents.Length(); i++) { 7634 aTransitionArray[i] = aArrayOfContents[i]->GetParentNode() != prevParent; 7635 prevParent = aArrayOfContents[i]->GetParentNode(); 7636 } 7637 } 7638 7639 Result<CreateElementResult, nsresult> 7640 HTMLEditor::WrapContentsInBlockquoteElementsWithTransaction( 7641 const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, 7642 const Element& aEditingHost) { 7643 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); 7644 7645 // The idea here is to put the nodes into a minimal number of blockquotes. 7646 // When the user blockquotes something, they expect one blockquote. That 7647 // may not be possible (for instance, if they have two table cells selected, 7648 // you need two blockquotes inside the cells). 7649 RefPtr<Element> curBlock, blockElementToPutCaret; 7650 nsCOMPtr<nsINode> prevParent; 7651 7652 EditorDOMPoint pointToPutCaret; 7653 for (size_t i = 0; i < aArrayOfContents.Length(); i++) { 7654 const OwningNonNull<nsIContent>& content = aArrayOfContents[i]; 7655 7656 const auto IsNewBlockRequired = [](const nsIContent& aContent) { 7657 return HTMLEditUtils::IsAnyTableElementExceptTableElementAndColumElement( 7658 aContent) || 7659 HTMLEditUtils::IsListItemElement(aContent); 7660 }; 7661 7662 if (IsNewBlockRequired(content)) { 7663 // Forget any previous block 7664 curBlock = nullptr; 7665 // Recursion time 7666 AutoTArray<OwningNonNull<nsIContent>, 24> childContents; 7667 HTMLEditUtils::CollectAllChildren(*content, childContents); 7668 Result<CreateElementResult, nsresult> 7669 wrapChildrenInAnotherBlockquoteResult = 7670 WrapContentsInBlockquoteElementsWithTransaction(childContents, 7671 aEditingHost); 7672 if (MOZ_UNLIKELY(wrapChildrenInAnotherBlockquoteResult.isErr())) { 7673 NS_WARNING( 7674 "HTMLEditor::WrapContentsInBlockquoteElementsWithTransaction() " 7675 "failed"); 7676 return wrapChildrenInAnotherBlockquoteResult; 7677 } 7678 CreateElementResult unwrappedWrapChildrenInAnotherBlockquoteResult = 7679 wrapChildrenInAnotherBlockquoteResult.unwrap(); 7680 unwrappedWrapChildrenInAnotherBlockquoteResult.MoveCaretPointTo( 7681 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 7682 if (unwrappedWrapChildrenInAnotherBlockquoteResult.GetNewNode()) { 7683 blockElementToPutCaret = 7684 unwrappedWrapChildrenInAnotherBlockquoteResult.UnwrapNewNode(); 7685 } 7686 } 7687 7688 // If the node has different parent than previous node, further nodes in a 7689 // new parent 7690 if (prevParent) { 7691 if (prevParent != content->GetParentNode()) { 7692 // Forget any previous blockquote node we were using 7693 curBlock = nullptr; 7694 prevParent = content->GetParentNode(); 7695 } 7696 } else { 7697 prevParent = content->GetParentNode(); 7698 } 7699 7700 // If no curBlock, make one 7701 if (!curBlock) { 7702 Result<CreateElementResult, nsresult> createNewBlockquoteElementResult = 7703 InsertElementWithSplittingAncestorsWithTransaction( 7704 *nsGkAtoms::blockquote, EditorDOMPoint(content), 7705 BRElementNextToSplitPoint::Keep, aEditingHost); 7706 if (MOZ_UNLIKELY(createNewBlockquoteElementResult.isErr())) { 7707 NS_WARNING( 7708 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(" 7709 "nsGkAtoms::blockquote) failed"); 7710 return createNewBlockquoteElementResult; 7711 } 7712 CreateElementResult unwrappedCreateNewBlockquoteElementResult = 7713 createNewBlockquoteElementResult.unwrap(); 7714 unwrappedCreateNewBlockquoteElementResult.MoveCaretPointTo( 7715 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 7716 MOZ_ASSERT(unwrappedCreateNewBlockquoteElementResult.GetNewNode()); 7717 blockElementToPutCaret = 7718 unwrappedCreateNewBlockquoteElementResult.GetNewNode(); 7719 curBlock = unwrappedCreateNewBlockquoteElementResult.UnwrapNewNode(); 7720 } 7721 7722 const OwningNonNull<nsIContent> lastContent = [&]() { 7723 nsIContent* lastContent = content; 7724 for (; i + 1 < aArrayOfContents.Length(); i++) { 7725 const OwningNonNull<nsIContent>& nextContent = aArrayOfContents[i + 1]; 7726 if (lastContent->GetNextSibling() == nextContent || 7727 !IsNewBlockRequired(nextContent)) { 7728 break; 7729 } 7730 lastContent = nextContent; 7731 } 7732 return OwningNonNull<nsIContent>(*lastContent); 7733 }(); 7734 7735 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to/ keep it alive. 7736 Result<MoveNodeResult, nsresult> moveNodeResult = 7737 MoveSiblingsToEndWithTransaction(MOZ_KnownLive(content), lastContent, 7738 *curBlock); 7739 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 7740 NS_WARNING("HTMLEditor::MoveSiblingsToEndWithTransaction() failed"); 7741 return moveNodeResult.propagateErr(); 7742 } 7743 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap(); 7744 unwrappedMoveNodeResult.MoveCaretPointTo( 7745 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 7746 } 7747 return blockElementToPutCaret 7748 ? CreateElementResult(std::move(blockElementToPutCaret), 7749 std::move(pointToPutCaret)) 7750 : CreateElementResult::NotHandled(std::move(pointToPutCaret)); 7751 } 7752 7753 Result<EditorDOMPoint, nsresult> 7754 HTMLEditor::RemoveBlockContainerElementsWithTransaction( 7755 const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, 7756 FormatBlockMode aFormatBlockMode, BlockInlineCheck aBlockInlineCheck) { 7757 MOZ_ASSERT(IsEditActionDataAvailable()); 7758 MOZ_ASSERT(aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand); 7759 7760 // Intent of this routine is to be used for converting to/from headers, 7761 // paragraphs, pre, and address. Those blocks that pretty much just contain 7762 // inline things... 7763 RefPtr<Element> blockElement; 7764 nsCOMPtr<nsIContent> firstContent, lastContent; 7765 EditorDOMPoint pointToPutCaret; 7766 for (const auto& content : aArrayOfContents) { 7767 // If the current node is a format element, remove it. 7768 if (HTMLEditUtils::IsFormatElementForParagraphStateCommand(content)) { 7769 // Process any partial progress saved 7770 if (blockElement) { 7771 Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult = 7772 RemoveBlockContainerElementWithTransactionBetween( 7773 *blockElement, *firstContent, *lastContent, aBlockInlineCheck); 7774 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) { 7775 NS_WARNING( 7776 "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() " 7777 "failed"); 7778 return unwrapBlockElementResult.propagateErr(); 7779 } 7780 unwrapBlockElementResult.unwrap().MoveCaretPointTo( 7781 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 7782 firstContent = lastContent = blockElement = nullptr; 7783 } 7784 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) { 7785 continue; 7786 } 7787 // Remove current block 7788 Result<EditorDOMPoint, nsresult> unwrapFormatBlockResult = 7789 RemoveBlockContainerWithTransaction( 7790 MOZ_KnownLive(*content->AsElement())); 7791 if (MOZ_UNLIKELY(unwrapFormatBlockResult.isErr())) { 7792 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed"); 7793 return unwrapFormatBlockResult; 7794 } 7795 if (unwrapFormatBlockResult.inspect().IsSet()) { 7796 pointToPutCaret = unwrapFormatBlockResult.unwrap(); 7797 } 7798 continue; 7799 } 7800 7801 // XXX How about, <th>, <thead>, <tfoot>, <dt>, <dl>? 7802 if (content->IsAnyOfHTMLElements( 7803 nsGkAtoms::table, nsGkAtoms::tr, nsGkAtoms::tbody, nsGkAtoms::td, 7804 nsGkAtoms::li, nsGkAtoms::blockquote, nsGkAtoms::div) || 7805 HTMLEditUtils::IsListElement(*content)) { 7806 // Process any partial progress saved 7807 if (blockElement) { 7808 Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult = 7809 RemoveBlockContainerElementWithTransactionBetween( 7810 *blockElement, *firstContent, *lastContent, aBlockInlineCheck); 7811 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) { 7812 NS_WARNING( 7813 "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() " 7814 "failed"); 7815 return unwrapBlockElementResult.propagateErr(); 7816 } 7817 unwrapBlockElementResult.unwrap().MoveCaretPointTo( 7818 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 7819 firstContent = lastContent = blockElement = nullptr; 7820 } 7821 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) { 7822 continue; 7823 } 7824 // Recursion time 7825 AutoTArray<OwningNonNull<nsIContent>, 24> childContents; 7826 HTMLEditUtils::CollectAllChildren(*content, childContents); 7827 Result<EditorDOMPoint, nsresult> removeBlockContainerElementsResult = 7828 RemoveBlockContainerElementsWithTransaction( 7829 childContents, aFormatBlockMode, aBlockInlineCheck); 7830 if (MOZ_UNLIKELY(removeBlockContainerElementsResult.isErr())) { 7831 NS_WARNING( 7832 "HTMLEditor::RemoveBlockContainerElementsWithTransaction() failed"); 7833 return removeBlockContainerElementsResult; 7834 } 7835 if (removeBlockContainerElementsResult.inspect().IsSet()) { 7836 pointToPutCaret = removeBlockContainerElementsResult.unwrap(); 7837 } 7838 continue; 7839 } 7840 7841 if (HTMLEditUtils::IsInlineContent(content, aBlockInlineCheck)) { 7842 if (blockElement) { 7843 // If so, is this node a descendant? 7844 if (EditorUtils::IsDescendantOf(*content, *blockElement)) { 7845 // Then we don't need to do anything different for this node 7846 lastContent = content; 7847 continue; 7848 } 7849 // Otherwise, we have progressed beyond end of blockElement, so let's 7850 // handle it now. We need to remove the portion of blockElement that 7851 // contains [firstContent - lastContent]. 7852 Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult = 7853 RemoveBlockContainerElementWithTransactionBetween( 7854 *blockElement, *firstContent, *lastContent, aBlockInlineCheck); 7855 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) { 7856 NS_WARNING( 7857 "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() " 7858 "failed"); 7859 return unwrapBlockElementResult.propagateErr(); 7860 } 7861 unwrapBlockElementResult.unwrap().MoveCaretPointTo( 7862 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 7863 firstContent = lastContent = blockElement = nullptr; 7864 // Fall out and handle content 7865 } 7866 blockElement = HTMLEditUtils::GetAncestorElement( 7867 content, HTMLEditUtils::ClosestEditableBlockElement, 7868 aBlockInlineCheck); 7869 if (!blockElement || 7870 !HTMLEditUtils::IsFormatElementForParagraphStateCommand( 7871 *blockElement) || 7872 !HTMLEditUtils::IsRemovableNode(*blockElement)) { 7873 // Not a block kind that we care about. 7874 blockElement = nullptr; 7875 } else { 7876 firstContent = lastContent = content; 7877 } 7878 continue; 7879 } 7880 7881 if (blockElement) { 7882 // Some node that is already sans block style. Skip over it and process 7883 // any partial progress saved. 7884 Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult = 7885 RemoveBlockContainerElementWithTransactionBetween( 7886 *blockElement, *firstContent, *lastContent, aBlockInlineCheck); 7887 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) { 7888 NS_WARNING( 7889 "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() " 7890 "failed"); 7891 return unwrapBlockElementResult.propagateErr(); 7892 } 7893 unwrapBlockElementResult.unwrap().MoveCaretPointTo( 7894 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 7895 firstContent = lastContent = blockElement = nullptr; 7896 continue; 7897 } 7898 } 7899 // Process any partial progress saved 7900 if (blockElement) { 7901 Result<SplitRangeOffFromNodeResult, nsresult> unwrapBlockElementResult = 7902 RemoveBlockContainerElementWithTransactionBetween( 7903 *blockElement, *firstContent, *lastContent, aBlockInlineCheck); 7904 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) { 7905 NS_WARNING( 7906 "HTMLEditor::RemoveBlockContainerElementWithTransactionBetween() " 7907 "failed"); 7908 return unwrapBlockElementResult.propagateErr(); 7909 } 7910 unwrapBlockElementResult.unwrap().MoveCaretPointTo( 7911 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 7912 firstContent = lastContent = blockElement = nullptr; 7913 } 7914 return pointToPutCaret; 7915 } 7916 7917 Result<CreateElementResult, nsresult> 7918 HTMLEditor::CreateOrChangeFormatContainerElement( 7919 nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, 7920 const nsStaticAtom& aNewFormatTagName, FormatBlockMode aFormatBlockMode, 7921 const Element& aEditingHost) { 7922 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); 7923 7924 // Intent of this routine is to be used for converting to/from headers, 7925 // paragraphs, pre, and address. Those blocks that pretty much just contain 7926 // inline things... 7927 RefPtr<Element> newBlock, curBlock, blockElementToPutCaret; 7928 // If we found a <br> element which should be moved into curBlock, this keeps 7929 // storing the <br> element after removing it from the tree. 7930 RefPtr<Element> pendingBRElementToMoveCurBlock; 7931 EditorDOMPoint pointToPutCaret; 7932 for (size_t i = 0; i < aArrayOfContents.Length(); i++) { 7933 const OwningNonNull<nsIContent>& content = aArrayOfContents[i]; 7934 7935 EditorDOMPoint atContent(content); 7936 if (NS_WARN_IF(!atContent.IsInContentNode())) { 7937 // If given node has been removed from the document, let's ignore it 7938 // since the following code may need its parent replace it with new 7939 // block. 7940 curBlock = nullptr; 7941 newBlock = nullptr; 7942 pendingBRElementToMoveCurBlock = nullptr; 7943 continue; 7944 } 7945 7946 const auto IsSameFormatBlockOrNonEditableBlock = 7947 [&aNewFormatTagName](const nsIContent& aContent) { 7948 return aContent.IsHTMLElement(&aNewFormatTagName) || 7949 (!EditorUtils::IsEditableContent(aContent, EditorType::HTML) && 7950 HTMLEditUtils::IsBlockElement( 7951 aContent, BlockInlineCheck::UseHTMLDefaultStyle)); 7952 }; 7953 7954 const auto IsMozDivOrFormatBlock = 7955 [&aFormatBlockMode](const nsIContent& aContent) { 7956 return HTMLEditUtils::IsMozDivElement(aContent) || 7957 HTMLEditor::IsFormatElement(aFormatBlockMode, aContent); 7958 }; 7959 7960 const auto IsNewFormatBlockRequired = [](const nsIContent& aContent) { 7961 return aContent.IsHTMLElement(nsGkAtoms::table) || 7962 HTMLEditUtils::IsListElement(aContent) || 7963 aContent.IsAnyOfHTMLElements( 7964 nsGkAtoms::tbody, nsGkAtoms::tr, nsGkAtoms::td, nsGkAtoms::li, 7965 nsGkAtoms::blockquote, nsGkAtoms::div); 7966 }; 7967 7968 const auto IsMovableInlineContent = [&aNewFormatTagName]( 7969 const nsIContent& aContent) { 7970 return HTMLEditUtils::IsInlineContent( 7971 aContent, BlockInlineCheck::UseHTMLDefaultStyle) && 7972 // If content is a non editable, drop it if we are going to <pre>. 7973 !(&aNewFormatTagName == nsGkAtoms::pre && 7974 !EditorUtils::IsEditableContent(aContent, EditorType::HTML)); 7975 }; 7976 7977 const auto IsMovableInlineContentSibling = [&](const nsIContent& aContent) { 7978 return !IsSameFormatBlockOrNonEditableBlock(aContent) && 7979 !IsMozDivOrFormatBlock(aContent) && 7980 !IsNewFormatBlockRequired(aContent) && 7981 !aContent.IsHTMLElement(nsGkAtoms::br) && 7982 IsMovableInlineContent(aContent); 7983 }; 7984 7985 // Is it already the right kind of block, or an uneditable block? 7986 if (IsSameFormatBlockOrNonEditableBlock(content)) { 7987 // Forget any previous block used for previous inline nodes 7988 curBlock = nullptr; 7989 pendingBRElementToMoveCurBlock = nullptr; 7990 // Do nothing to this block 7991 continue; 7992 } 7993 7994 // If content is a format element, replace it with a new block of correct 7995 // type. 7996 // XXX: pre can't hold everything the others can 7997 if (IsMozDivOrFormatBlock(content)) { 7998 // Forget any previous block used for previous inline nodes 7999 curBlock = nullptr; 8000 pendingBRElementToMoveCurBlock = nullptr; 8001 RefPtr<Element> expectedContainerOfNewBlock = 8002 atContent.IsContainerHTMLElement(nsGkAtoms::dl) && 8003 HTMLEditUtils::IsSplittableNode( 8004 *atContent.ContainerAs<Element>()) 8005 ? atContent.GetContainerParentAs<Element>() 8006 : atContent.GetContainerAs<Element>(); 8007 Result<CreateElementResult, nsresult> replaceWithNewBlockElementResult = 8008 ReplaceContainerAndCloneAttributesWithTransaction( 8009 MOZ_KnownLive(*content->AsElement()), aNewFormatTagName); 8010 if (MOZ_UNLIKELY(replaceWithNewBlockElementResult.isErr())) { 8011 NS_WARNING( 8012 "EditorBase::ReplaceContainerAndCloneAttributesWithTransaction() " 8013 "failed"); 8014 return replaceWithNewBlockElementResult; 8015 } 8016 CreateElementResult unwrappedReplaceWithNewBlockElementResult = 8017 replaceWithNewBlockElementResult.unwrap(); 8018 // If the new block element was moved to different element or removed by 8019 // the web app via mutation event listener, we should stop handling this 8020 // action since we cannot handle each of a lot of edge cases. 8021 if (NS_WARN_IF(unwrappedReplaceWithNewBlockElementResult.GetNewNode() 8022 ->GetParentNode() != expectedContainerOfNewBlock)) { 8023 unwrappedReplaceWithNewBlockElementResult.IgnoreCaretPointSuggestion(); 8024 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 8025 } 8026 unwrappedReplaceWithNewBlockElementResult.MoveCaretPointTo( 8027 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 8028 newBlock = unwrappedReplaceWithNewBlockElementResult.UnwrapNewNode(); 8029 continue; 8030 } 8031 8032 if (IsNewFormatBlockRequired(content)) { 8033 // Forget any previous block used for previous inline nodes 8034 curBlock = nullptr; 8035 pendingBRElementToMoveCurBlock = nullptr; 8036 // Recursion time 8037 AutoTArray<OwningNonNull<nsIContent>, 24> childContents; 8038 HTMLEditUtils::CollectAllChildren(*content, childContents); 8039 if (!childContents.IsEmpty()) { 8040 Result<CreateElementResult, nsresult> wrapChildrenInBlockElementResult = 8041 CreateOrChangeFormatContainerElement( 8042 childContents, aNewFormatTagName, aFormatBlockMode, 8043 aEditingHost); 8044 if (MOZ_UNLIKELY(wrapChildrenInBlockElementResult.isErr())) { 8045 NS_WARNING( 8046 "HTMLEditor::CreateOrChangeFormatContainerElement() failed"); 8047 return wrapChildrenInBlockElementResult; 8048 } 8049 CreateElementResult unwrappedWrapChildrenInBlockElementResult = 8050 wrapChildrenInBlockElementResult.unwrap(); 8051 unwrappedWrapChildrenInBlockElementResult.MoveCaretPointTo( 8052 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 8053 if (unwrappedWrapChildrenInBlockElementResult.GetNewNode()) { 8054 blockElementToPutCaret = 8055 unwrappedWrapChildrenInBlockElementResult.UnwrapNewNode(); 8056 } 8057 continue; 8058 } 8059 8060 // Make sure we can put a block here 8061 Result<CreateElementResult, nsresult> createNewBlockElementResult = 8062 InsertElementWithSplittingAncestorsWithTransaction( 8063 aNewFormatTagName, atContent, BRElementNextToSplitPoint::Keep, 8064 aEditingHost); 8065 if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) { 8066 NS_WARNING( 8067 nsPrintfCString( 8068 "HTMLEditor::" 8069 "InsertElementWithSplittingAncestorsWithTransaction(%s) failed", 8070 nsAtomCString(&aNewFormatTagName).get()) 8071 .get()); 8072 return createNewBlockElementResult; 8073 } 8074 CreateElementResult unwrappedCreateNewBlockElementResult = 8075 createNewBlockElementResult.unwrap(); 8076 unwrappedCreateNewBlockElementResult.MoveCaretPointTo( 8077 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 8078 MOZ_ASSERT(unwrappedCreateNewBlockElementResult.GetNewNode()); 8079 blockElementToPutCaret = 8080 unwrappedCreateNewBlockElementResult.UnwrapNewNode(); 8081 continue; 8082 } 8083 8084 if (content->IsHTMLElement(nsGkAtoms::br)) { 8085 if (curBlock) { 8086 if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand) { 8087 // If the node is a break, we honor it by putting further nodes in a 8088 // new parent. 8089 8090 // Forget any previous block used for previous inline nodes. 8091 curBlock = nullptr; 8092 pendingBRElementToMoveCurBlock = nullptr; 8093 } else { 8094 // If the node is a break, we need to move it into end of the curBlock 8095 // if we'll move following content into curBlock. 8096 pendingBRElementToMoveCurBlock = content->AsElement(); 8097 } 8098 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it 8099 // alive. 8100 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content)); 8101 if (NS_FAILED(rv)) { 8102 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 8103 return Err(rv); 8104 } 8105 continue; 8106 } 8107 8108 // The break is the first (or even only) node we encountered. Create a 8109 // block for it. 8110 Result<CreateElementResult, nsresult> createNewBlockElementResult = 8111 InsertElementWithSplittingAncestorsWithTransaction( 8112 aNewFormatTagName, atContent, BRElementNextToSplitPoint::Keep, 8113 aEditingHost); 8114 if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) { 8115 NS_WARNING(nsPrintfCString("HTMLEditor::" 8116 "InsertElementWithSplittingAncestorsWith" 8117 "Transaction(%s) failed", 8118 nsAtomCString(&aNewFormatTagName).get()) 8119 .get()); 8120 return createNewBlockElementResult; 8121 } 8122 CreateElementResult unwrappedCreateNewBlockElementResult = 8123 createNewBlockElementResult.unwrap(); 8124 unwrappedCreateNewBlockElementResult.MoveCaretPointTo( 8125 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 8126 RefPtr<Element> newBlockElement = 8127 unwrappedCreateNewBlockElementResult.UnwrapNewNode(); 8128 MOZ_ASSERT(newBlockElement); 8129 blockElementToPutCaret = newBlockElement; 8130 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it 8131 // alive. 8132 Result<MoveNodeResult, nsresult> moveNodeResult = 8133 MoveNodeToEndWithTransaction(MOZ_KnownLive(content), 8134 *newBlockElement); 8135 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 8136 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); 8137 return moveNodeResult.propagateErr(); 8138 } 8139 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap(); 8140 unwrappedMoveNodeResult.MoveCaretPointTo( 8141 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 8142 curBlock = std::move(newBlockElement); 8143 continue; 8144 } 8145 8146 if (!IsMovableInlineContent(content)) { 8147 // Do nothing to this block 8148 continue; 8149 } 8150 MOZ_ASSERT(IsMovableInlineContentSibling(content)); 8151 8152 // If no curBlock, make one 8153 if (!curBlock) { 8154 Result<CreateElementResult, nsresult> createNewBlockElementResult = 8155 InsertElementWithSplittingAncestorsWithTransaction( 8156 aNewFormatTagName, atContent, BRElementNextToSplitPoint::Keep, 8157 aEditingHost); 8158 if (MOZ_UNLIKELY(createNewBlockElementResult.isErr())) { 8159 NS_WARNING(nsPrintfCString("HTMLEditor::" 8160 "InsertElementWithSplittingAncestorsWith" 8161 "Transaction(%s) failed", 8162 nsAtomCString(&aNewFormatTagName).get()) 8163 .get()); 8164 return createNewBlockElementResult; 8165 } 8166 CreateElementResult unwrappedCreateNewBlockElementResult = 8167 createNewBlockElementResult.unwrap(); 8168 unwrappedCreateNewBlockElementResult.MoveCaretPointTo( 8169 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 8170 MOZ_ASSERT(unwrappedCreateNewBlockElementResult.GetNewNode()); 8171 blockElementToPutCaret = 8172 unwrappedCreateNewBlockElementResult.GetNewNode(); 8173 curBlock = unwrappedCreateNewBlockElementResult.UnwrapNewNode(); 8174 8175 // Update container of content. 8176 atContent.Set(content); 8177 if (NS_WARN_IF(!atContent.IsSet())) { 8178 // This is possible due to mutation events, let's not assert 8179 return Err(NS_ERROR_UNEXPECTED); 8180 } 8181 } else if (pendingBRElementToMoveCurBlock) { 8182 Result<CreateElementResult, nsresult> insertBRElementResult = 8183 InsertNodeWithTransaction<Element>( 8184 *pendingBRElementToMoveCurBlock, 8185 EditorDOMPoint::AtEndOf(*curBlock)); 8186 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) { 8187 NS_WARNING("EditorBase::InsertNodeWithTransaction<Element>() failed"); 8188 return insertBRElementResult.propagateErr(); 8189 } 8190 insertBRElementResult.inspect().IgnoreCaretPointSuggestion(); 8191 pendingBRElementToMoveCurBlock = nullptr; 8192 } 8193 8194 // This is a continuation of some inline nodes that belong together in 8195 // the same block item. Use curBlock. 8196 const OwningNonNull<nsIContent> lastContent = [&]() { 8197 nsIContent* lastContent = content; 8198 for (; i + 1 < aArrayOfContents.Length(); i++) { 8199 const OwningNonNull<nsIContent>& nextContent = aArrayOfContents[i + 1]; 8200 if (lastContent->GetNextSibling() != nextContent || 8201 !IsMovableInlineContentSibling(nextContent)) { 8202 break; 8203 } 8204 lastContent = nextContent; 8205 } 8206 return OwningNonNull<nsIContent>(*lastContent); 8207 }(); 8208 // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it 8209 // alive. We could try to make that a rvalue ref and create a const array 8210 // on the stack here, but callers are passing in auto arrays, and we don't 8211 // want to introduce copies.. 8212 Result<MoveNodeResult, nsresult> moveNodeResult = 8213 MoveSiblingsToEndWithTransaction(MOZ_KnownLive(content), lastContent, 8214 *curBlock); 8215 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 8216 NS_WARNING("HTMLEditor::MoveSiblingsToEndWithTransaction() failed"); 8217 return moveNodeResult.propagateErr(); 8218 } 8219 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap(); 8220 unwrappedMoveNodeResult.MoveCaretPointTo( 8221 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 8222 } 8223 return blockElementToPutCaret 8224 ? CreateElementResult(std::move(blockElementToPutCaret), 8225 std::move(pointToPutCaret)) 8226 : CreateElementResult::NotHandled(std::move(pointToPutCaret)); 8227 } 8228 8229 Result<SplitNodeResult, nsresult> 8230 HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction( 8231 const nsAtom& aTag, const EditorDOMPoint& aStartOfDeepestRightNode, 8232 const Element& aEditingHost) { 8233 MOZ_ASSERT(IsEditActionDataAvailable()); 8234 8235 if (NS_WARN_IF(!aEditingHost.IsInComposedDoc())) { 8236 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 8237 } 8238 8239 if (NS_WARN_IF(!aStartOfDeepestRightNode.IsSet())) { 8240 return Err(NS_ERROR_INVALID_ARG); 8241 } 8242 MOZ_ASSERT(aStartOfDeepestRightNode.IsSetAndValid()); 8243 8244 // The point must be descendant of editing host. 8245 // XXX Isn't it a valid case if it points a direct child of aEditingHost? 8246 if (NS_WARN_IF( 8247 !aStartOfDeepestRightNode.GetContainer()->IsInclusiveDescendantOf( 8248 &aEditingHost))) { 8249 return Err(NS_ERROR_INVALID_ARG); 8250 } 8251 8252 // Look for a node that can legally contain the tag. 8253 const EditorDOMPoint pointToInsert = 8254 HTMLEditUtils::GetInsertionPointInInclusiveAncestor( 8255 aTag, aStartOfDeepestRightNode, &aEditingHost); 8256 if (MOZ_UNLIKELY(!pointToInsert.IsSet())) { 8257 NS_WARNING( 8258 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() reached " 8259 "editing host"); 8260 return Err(NS_ERROR_FAILURE); 8261 } 8262 // If the point itself can contain the tag, we don't need to split any 8263 // ancestor nodes. In this case, we should return the given split point 8264 // as is. 8265 if (pointToInsert.GetContainer() == aStartOfDeepestRightNode.GetContainer()) { 8266 return SplitNodeResult::NotHandled(aStartOfDeepestRightNode); 8267 } 8268 8269 Result<SplitNodeResult, nsresult> splitNodeResult = 8270 SplitNodeDeepWithTransaction(MOZ_KnownLive(*pointToInsert.GetChild()), 8271 aStartOfDeepestRightNode, 8272 SplitAtEdges::eAllowToCreateEmptyContainer); 8273 NS_WARNING_ASSERTION(splitNodeResult.isOk(), 8274 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::" 8275 "eAllowToCreateEmptyContainer) failed"); 8276 return splitNodeResult; 8277 } 8278 8279 Result<CreateElementResult, nsresult> 8280 HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction( 8281 const nsAtom& aTagName, const EditorDOMPoint& aPointToInsert, 8282 BRElementNextToSplitPoint aBRElementNextToSplitPoint, 8283 const Element& aEditingHost, 8284 const InitializeInsertingElement& aInitializer) { 8285 MOZ_ASSERT(aPointToInsert.IsSetAndValid()); 8286 8287 const nsCOMPtr<nsIContent> childAtPointToInsert = aPointToInsert.GetChild(); 8288 Result<SplitNodeResult, nsresult> splitNodeResult = 8289 MaybeSplitAncestorsForInsertWithTransaction(aTagName, aPointToInsert, 8290 aEditingHost); 8291 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 8292 NS_WARNING( 8293 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() failed"); 8294 return splitNodeResult.propagateErr(); 8295 } 8296 SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap(); 8297 DebugOnly<bool> wasCaretPositionSuggestedAtSplit = 8298 unwrappedSplitNodeResult.HasCaretPointSuggestion(); 8299 // We'll update selection below, and nobody touches selection until then. 8300 // Therefore, we don't need to touch selection here. 8301 unwrappedSplitNodeResult.IgnoreCaretPointSuggestion(); 8302 8303 // If current handling node has been moved from the container by a 8304 // mutation event listener when we need to do something more for it, 8305 // we should stop handling this action since we cannot handle each 8306 // edge case. 8307 if (childAtPointToInsert && 8308 NS_WARN_IF(!childAtPointToInsert->IsInclusiveDescendantOf( 8309 unwrappedSplitNodeResult.DidSplit() 8310 ? unwrappedSplitNodeResult.GetNextContent() 8311 : aPointToInsert.GetContainer()))) { 8312 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 8313 } 8314 8315 auto splitPoint = unwrappedSplitNodeResult.AtSplitPoint<EditorDOMPoint>(); 8316 if (aBRElementNextToSplitPoint == BRElementNextToSplitPoint::Delete) { 8317 // Consume a trailing br, if any. This is to keep an alignment from 8318 // creating extra lines, if possible. 8319 if (nsCOMPtr<nsIContent> maybeBRContent = HTMLEditUtils::GetNextContent( 8320 splitPoint, 8321 {WalkTreeOption::IgnoreNonEditableNode, 8322 WalkTreeOption::StopAtBlockBoundary}, 8323 BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost)) { 8324 if (maybeBRContent->IsHTMLElement(nsGkAtoms::br) && 8325 splitPoint.GetChild()) { 8326 // Making use of html structure... if next node after where we are 8327 // putting our div is not a block, then the br we found is in same 8328 // block we are, so it's safe to consume it. 8329 if (nsIContent* nextEditableSibling = HTMLEditUtils::GetNextSibling( 8330 *splitPoint.GetChild(), 8331 {WalkTreeOption::IgnoreNonEditableNode})) { 8332 if (!HTMLEditUtils::IsBlockElement( 8333 *nextEditableSibling, 8334 BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 8335 AutoEditorDOMPointChildInvalidator lockOffset(splitPoint); 8336 nsresult rv = DeleteNodeWithTransaction(*maybeBRContent); 8337 if (NS_FAILED(rv)) { 8338 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 8339 return Err(rv); 8340 } 8341 } 8342 } 8343 } 8344 } 8345 } 8346 8347 Result<CreateElementResult, nsresult> createNewElementResult = 8348 CreateAndInsertElement(WithTransaction::Yes, aTagName, splitPoint, 8349 aInitializer); 8350 if (MOZ_UNLIKELY(createNewElementResult.isErr())) { 8351 NS_WARNING( 8352 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed"); 8353 return createNewElementResult; 8354 } 8355 MOZ_ASSERT_IF(wasCaretPositionSuggestedAtSplit, 8356 createNewElementResult.inspect().HasCaretPointSuggestion()); 8357 MOZ_ASSERT(createNewElementResult.inspect().GetNewNode()); 8358 8359 // If the new block element was moved to different element or removed by 8360 // the web app via mutation event listener, we should stop handling this 8361 // action since we cannot handle each of a lot of edge cases. 8362 if (NS_WARN_IF( 8363 createNewElementResult.inspect().GetNewNode()->GetParentNode() != 8364 splitPoint.GetContainer())) { 8365 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 8366 } 8367 8368 return createNewElementResult; 8369 } 8370 8371 nsresult HTMLEditor::JoinNearestEditableNodesWithTransaction( 8372 nsIContent& aNodeLeft, nsIContent& aNodeRight, 8373 EditorDOMPoint* aNewFirstChildOfRightNode) { 8374 MOZ_ASSERT(IsEditActionDataAvailable()); 8375 MOZ_ASSERT(aNewFirstChildOfRightNode); 8376 8377 // Caller responsible for left and right node being the same type 8378 if (NS_WARN_IF(!aNodeLeft.GetParentNode())) { 8379 return NS_ERROR_FAILURE; 8380 } 8381 // If they don't have the same parent, first move the right node to after 8382 // the left one 8383 if (aNodeLeft.GetParentNode() != aNodeRight.GetParentNode()) { 8384 Result<MoveNodeResult, nsresult> moveNodeResult = 8385 MoveNodeWithTransaction(aNodeRight, EditorDOMPoint(&aNodeLeft)); 8386 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 8387 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed"); 8388 return moveNodeResult.unwrapErr(); 8389 } 8390 nsresult rv = moveNodeResult.inspect().SuggestCaretPointTo( 8391 *this, {SuggestCaret::OnlyIfHasSuggestion, 8392 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 8393 SuggestCaret::AndIgnoreTrivialError}); 8394 if (NS_FAILED(rv)) { 8395 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed"); 8396 return rv; 8397 } 8398 NS_WARNING_ASSERTION( 8399 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 8400 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored"); 8401 } 8402 8403 // Separate join rules for differing blocks 8404 if (HTMLEditUtils::IsListElement(aNodeLeft) || aNodeLeft.IsText()) { 8405 // For lists, merge shallow (wouldn't want to combine list items) 8406 Result<JoinNodesResult, nsresult> joinNodesResult = 8407 JoinNodesWithTransaction(aNodeLeft, aNodeRight); 8408 if (MOZ_UNLIKELY(joinNodesResult.isErr())) { 8409 NS_WARNING("HTMLEditor::JoinNodesWithTransaction failed"); 8410 return joinNodesResult.unwrapErr(); 8411 } 8412 *aNewFirstChildOfRightNode = 8413 joinNodesResult.inspect().AtJoinedPoint<EditorDOMPoint>(); 8414 return NS_OK; 8415 } 8416 8417 // Remember the last left child, and first right child 8418 nsCOMPtr<nsIContent> lastEditableChildOfLeftContent = 8419 HTMLEditUtils::GetLastChild(aNodeLeft, 8420 {WalkTreeOption::IgnoreNonEditableNode}); 8421 if (MOZ_UNLIKELY(NS_WARN_IF(!lastEditableChildOfLeftContent))) { 8422 return NS_ERROR_FAILURE; 8423 } 8424 8425 nsCOMPtr<nsIContent> firstEditableChildOfRightContent = 8426 HTMLEditUtils::GetFirstChild(aNodeRight, 8427 {WalkTreeOption::IgnoreNonEditableNode}); 8428 if (NS_WARN_IF(!firstEditableChildOfRightContent)) { 8429 return NS_ERROR_FAILURE; 8430 } 8431 8432 // For list items, divs, etc., merge smart 8433 Result<JoinNodesResult, nsresult> joinNodesResult = 8434 JoinNodesWithTransaction(aNodeLeft, aNodeRight); 8435 if (MOZ_UNLIKELY(joinNodesResult.isErr())) { 8436 NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed"); 8437 return joinNodesResult.unwrapErr(); 8438 } 8439 8440 if ((lastEditableChildOfLeftContent->IsText() || 8441 lastEditableChildOfLeftContent->IsElement()) && 8442 HTMLEditUtils::CanContentsBeJoined(*lastEditableChildOfLeftContent, 8443 *firstEditableChildOfRightContent)) { 8444 nsresult rv = JoinNearestEditableNodesWithTransaction( 8445 *lastEditableChildOfLeftContent, *firstEditableChildOfRightContent, 8446 aNewFirstChildOfRightNode); 8447 NS_WARNING_ASSERTION( 8448 NS_SUCCEEDED(rv), 8449 "HTMLEditor::JoinNearestEditableNodesWithTransaction() failed"); 8450 return rv; 8451 } 8452 *aNewFirstChildOfRightNode = 8453 joinNodesResult.inspect().AtJoinedPoint<EditorDOMPoint>(); 8454 return NS_OK; 8455 } 8456 8457 Element* HTMLEditor::GetMostDistantAncestorMailCiteElement( 8458 const nsINode& aNode) const { 8459 Element* mailCiteElement = nullptr; 8460 const bool isPlaintextEditor = IsPlaintextMailComposer(); 8461 for (Element* element : aNode.InclusiveAncestorsOfType<Element>()) { 8462 if ((isPlaintextEditor && element->IsHTMLElement(nsGkAtoms::pre)) || 8463 HTMLEditUtils::IsMailCiteElement(*element)) { 8464 mailCiteElement = element; 8465 continue; 8466 } 8467 if (element->IsHTMLElement(nsGkAtoms::body)) { 8468 break; 8469 } 8470 } 8471 return mailCiteElement; 8472 } 8473 8474 nsresult HTMLEditor::CacheInlineStyles(Element& Element) { 8475 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); 8476 8477 nsresult rv = GetInlineStyles( 8478 Element, *TopLevelEditSubActionDataRef().mCachedPendingStyles); 8479 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 8480 "HTMLEditor::GetInlineStyles() failed"); 8481 return rv; 8482 } 8483 8484 nsresult HTMLEditor::GetInlineStyles( 8485 Element& aElement, AutoPendingStyleCacheArray& aPendingStyleCacheArray) { 8486 MOZ_ASSERT(IsEditActionDataAvailable()); 8487 MOZ_ASSERT(aPendingStyleCacheArray.IsEmpty()); 8488 8489 if (!IsCSSEnabled()) { 8490 // In the HTML styling mode, we should preserve the order of inline styles 8491 // specified with HTML elements, then, we can keep same order as original 8492 // one when we create new elements to apply the styles at new place. 8493 // XXX Currently, we don't preserve all inline parents, therefore, we cannot 8494 // restore all inline elements as-is. Perhaps, we should store all 8495 // inline elements with more details (e.g., all attributes), and store 8496 // same elements. For example, web apps may give style as: 8497 // em { 8498 // font-style: italic; 8499 // } 8500 // em em { 8501 // font-style: normal; 8502 // font-weight: bold; 8503 // } 8504 // but we cannot restore the style as-is. 8505 nsString value; 8506 const bool givenElementIsEditable = 8507 HTMLEditUtils::IsSimplyEditableNode(aElement); 8508 auto NeedToAppend = [&](nsStaticAtom& aTagName, nsStaticAtom* aAttribute) { 8509 if (mPendingStylesToApplyToNewContent->GetStyleState( 8510 aTagName, aAttribute) != PendingStyleState::NotUpdated) { 8511 return false; // The style has already been changed. 8512 } 8513 if (aPendingStyleCacheArray.Contains(aTagName, aAttribute)) { 8514 return false; // Already preserved 8515 } 8516 return true; 8517 }; 8518 for (Element* const inclusiveAncestor : 8519 aElement.InclusiveAncestorsOfType<Element>()) { 8520 if (HTMLEditUtils::IsBlockElement( 8521 *inclusiveAncestor, 8522 BlockInlineCheck::UseComputedDisplayOutsideStyle) || 8523 (givenElementIsEditable && 8524 !HTMLEditUtils::IsSimplyEditableNode(*inclusiveAncestor))) { 8525 break; 8526 } 8527 if (inclusiveAncestor->IsAnyOfHTMLElements( 8528 nsGkAtoms::b, nsGkAtoms::i, nsGkAtoms::u, nsGkAtoms::s, 8529 nsGkAtoms::strike, nsGkAtoms::tt, nsGkAtoms::em, 8530 nsGkAtoms::strong, nsGkAtoms::dfn, nsGkAtoms::code, 8531 nsGkAtoms::samp, nsGkAtoms::var, nsGkAtoms::cite, nsGkAtoms::abbr, 8532 nsGkAtoms::acronym, nsGkAtoms::sub, nsGkAtoms::sup)) { 8533 nsStaticAtom& tagName = const_cast<nsStaticAtom&>( 8534 *inclusiveAncestor->NodeInfo()->NameAtom()->AsStatic()); 8535 if (NeedToAppend(tagName, nullptr)) { 8536 aPendingStyleCacheArray.AppendElement( 8537 PendingStyleCache(tagName, nullptr, EmptyString())); 8538 } 8539 continue; 8540 } 8541 if (inclusiveAncestor->IsHTMLElement(nsGkAtoms::font)) { 8542 if (NeedToAppend(*nsGkAtoms::font, nsGkAtoms::face)) { 8543 inclusiveAncestor->GetAttr(nsGkAtoms::face, value); 8544 if (!value.IsEmpty()) { 8545 aPendingStyleCacheArray.AppendElement( 8546 PendingStyleCache(*nsGkAtoms::font, nsGkAtoms::face, value)); 8547 value.Truncate(); 8548 } 8549 } 8550 if (NeedToAppend(*nsGkAtoms::font, nsGkAtoms::size)) { 8551 inclusiveAncestor->GetAttr(nsGkAtoms::size, value); 8552 if (!value.IsEmpty()) { 8553 aPendingStyleCacheArray.AppendElement( 8554 PendingStyleCache(*nsGkAtoms::font, nsGkAtoms::size, value)); 8555 value.Truncate(); 8556 } 8557 } 8558 if (NeedToAppend(*nsGkAtoms::font, nsGkAtoms::color)) { 8559 inclusiveAncestor->GetAttr(nsGkAtoms::color, value); 8560 if (!value.IsEmpty()) { 8561 aPendingStyleCacheArray.AppendElement( 8562 PendingStyleCache(*nsGkAtoms::font, nsGkAtoms::color, value)); 8563 value.Truncate(); 8564 } 8565 } 8566 continue; 8567 } 8568 } 8569 return NS_OK; 8570 } 8571 8572 for (nsStaticAtom* property : {nsGkAtoms::b, 8573 nsGkAtoms::i, 8574 nsGkAtoms::u, 8575 nsGkAtoms::s, 8576 nsGkAtoms::strike, 8577 nsGkAtoms::face, 8578 nsGkAtoms::size, 8579 nsGkAtoms::color, 8580 nsGkAtoms::tt, 8581 nsGkAtoms::em, 8582 nsGkAtoms::strong, 8583 nsGkAtoms::dfn, 8584 nsGkAtoms::code, 8585 nsGkAtoms::samp, 8586 nsGkAtoms::var, 8587 nsGkAtoms::cite, 8588 nsGkAtoms::abbr, 8589 nsGkAtoms::acronym, 8590 nsGkAtoms::background_color, 8591 nsGkAtoms::sub, 8592 nsGkAtoms::sup}) { 8593 const EditorInlineStyle style = 8594 property == nsGkAtoms::face || property == nsGkAtoms::size || 8595 property == nsGkAtoms::color 8596 ? EditorInlineStyle(*nsGkAtoms::font, property) 8597 : EditorInlineStyle(*property); 8598 // If type-in state is set, don't intervene 8599 const PendingStyleState styleState = 8600 mPendingStylesToApplyToNewContent->GetStyleState(*style.mHTMLProperty, 8601 style.mAttribute); 8602 if (styleState != PendingStyleState::NotUpdated) { 8603 continue; 8604 } 8605 bool isSet = false; 8606 nsString value; // Don't use nsAutoString here because it requires memcpy 8607 // at creating new PendingStyleCache instance. 8608 // Don't use CSS for <font size>, we don't support it usefully (bug 780035) 8609 if (property == nsGkAtoms::size) { 8610 isSet = HTMLEditUtils::IsInlineStyleSetByElement(aElement, style, nullptr, 8611 &value); 8612 } else if (style.IsCSSSettable(aElement)) { 8613 Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError = 8614 CSSEditUtils::IsComputedCSSEquivalentTo(*this, aElement, style, 8615 value); 8616 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) { 8617 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed"); 8618 return isComputedCSSEquivalentToStyleOrError.unwrapErr(); 8619 } 8620 isSet = isComputedCSSEquivalentToStyleOrError.unwrap(); 8621 } 8622 if (isSet) { 8623 aPendingStyleCacheArray.AppendElement( 8624 style.ToPendingStyleCache(std::move(value))); 8625 } 8626 } 8627 return NS_OK; 8628 } 8629 8630 nsresult HTMLEditor::ReapplyCachedStyles() { 8631 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable()); 8632 8633 // The idea here is to examine our cached list of styles and see if any have 8634 // been removed. If so, add typeinstate for them, so that they will be 8635 // reinserted when new content is added. 8636 8637 if (TopLevelEditSubActionDataRef().mCachedPendingStyles->IsEmpty() || 8638 !SelectionRef().RangeCount()) { 8639 return NS_OK; 8640 } 8641 8642 // remember if we are in css mode 8643 const bool useCSS = IsCSSEnabled(); 8644 8645 const RangeBoundary& atStartOfSelection = 8646 SelectionRef().GetRangeAt(0)->StartRef(); 8647 const RefPtr<Element> startContainerElement = 8648 atStartOfSelection.GetContainer() && 8649 atStartOfSelection.GetContainer()->IsContent() 8650 ? atStartOfSelection.GetContainer()->GetAsElementOrParentElement() 8651 : nullptr; 8652 if (NS_WARN_IF(!startContainerElement)) { 8653 return NS_OK; 8654 } 8655 8656 AutoPendingStyleCacheArray styleCacheArrayAtInsertionPoint; 8657 nsresult rv = 8658 GetInlineStyles(*startContainerElement, styleCacheArrayAtInsertionPoint); 8659 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 8660 return NS_ERROR_EDITOR_DESTROYED; 8661 } 8662 if (NS_FAILED(rv)) { 8663 NS_WARNING("HTMLEditor::GetInlineStyles() failed, but ignored"); 8664 return NS_OK; 8665 } 8666 8667 for (PendingStyleCache& styleCacheBeforeEdit : 8668 Reversed(*TopLevelEditSubActionDataRef().mCachedPendingStyles)) { 8669 bool isFirst = false, isAny = false, isAll = false; 8670 nsAutoString currentValue; 8671 const EditorInlineStyle inlineStyle = styleCacheBeforeEdit.ToInlineStyle(); 8672 if (useCSS && inlineStyle.IsCSSSettable(*startContainerElement)) { 8673 // check computed style first in css case 8674 // MOZ_KnownLive(styleCacheBeforeEdit.*) because they are nsStaticAtom 8675 // and its instances are alive until shutting down. 8676 Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError = 8677 CSSEditUtils::IsComputedCSSEquivalentTo(*this, *startContainerElement, 8678 inlineStyle, currentValue); 8679 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) { 8680 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed"); 8681 return isComputedCSSEquivalentToStyleOrError.unwrapErr(); 8682 } 8683 isAny = isComputedCSSEquivalentToStyleOrError.unwrap(); 8684 } 8685 if (!isAny) { 8686 // then check typeinstate and html style 8687 nsresult rv = GetInlinePropertyBase( 8688 inlineStyle, &styleCacheBeforeEdit.AttributeValueOrCSSValueRef(), 8689 &isFirst, &isAny, &isAll, ¤tValue); 8690 if (NS_FAILED(rv)) { 8691 NS_WARNING("HTMLEditor::GetInlinePropertyBase() failed"); 8692 return rv; 8693 } 8694 } 8695 // This style has disappeared through deletion. Let's add the styles to 8696 // mPendingStylesToApplyToNewContent when same style isn't applied to the 8697 // node already. 8698 if (isAny && 8699 !IsPendingStyleCachePreservingSubAction(GetTopLevelEditSubAction())) { 8700 continue; 8701 } 8702 AutoPendingStyleCacheArray::index_type index = 8703 styleCacheArrayAtInsertionPoint.IndexOf( 8704 styleCacheBeforeEdit.TagRef(), styleCacheBeforeEdit.GetAttribute()); 8705 if (index == AutoPendingStyleCacheArray::NoIndex || 8706 styleCacheBeforeEdit.AttributeValueOrCSSValueRef() != 8707 styleCacheArrayAtInsertionPoint.ElementAt(index) 8708 .AttributeValueOrCSSValueRef()) { 8709 mPendingStylesToApplyToNewContent->PreserveStyle(styleCacheBeforeEdit); 8710 } 8711 } 8712 return NS_OK; 8713 } 8714 8715 nsresult HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange( 8716 const RawRangeBoundary& aStartRef, const RawRangeBoundary& aEndRef) { 8717 MOZ_ASSERT(IsEditActionDataAvailable()); 8718 8719 AutoTArray<OwningNonNull<Element>, 64> arrayOfEmptyElements; 8720 DOMIterator iter; 8721 if (NS_FAILED(iter.Init(aStartRef, aEndRef))) { 8722 NS_WARNING("DOMIterator::Init() failed"); 8723 return NS_ERROR_FAILURE; 8724 } 8725 iter.AppendNodesToArray( 8726 +[](nsINode& aNode, void* aSelf) { 8727 MOZ_ASSERT(Element::FromNode(&aNode)); 8728 MOZ_ASSERT(aSelf); 8729 Element& element = *aNode.AsElement(); 8730 if (!EditorUtils::IsEditableContent(element, EditorType::HTML) || 8731 (!HTMLEditUtils::IsListItemElement(element) && 8732 !HTMLEditUtils::IsTableCellOrCaptionElement(element))) { 8733 return false; 8734 } 8735 return HTMLEditUtils::IsEmptyNode( 8736 element, {EmptyCheckOption::TreatSingleBRElementAsVisible, 8737 EmptyCheckOption::TreatNonEditableContentAsInvisible}); 8738 }, 8739 arrayOfEmptyElements, this); 8740 8741 // Put padding <br> elements for empty <li> and <td>. 8742 EditorDOMPoint pointToPutCaret; 8743 for (auto& emptyElement : arrayOfEmptyElements) { 8744 // Need to put br at END of node. It may have empty containers in it and 8745 // still pass the "IsEmptyNode" test, and we want the br's to be after 8746 // them. Also, we want the br to be after the selection if the selection 8747 // is in this node. 8748 EditorDOMPoint endOfNode(EditorDOMPoint::AtEndOf(emptyElement)); 8749 Result<CreateElementResult, nsresult> insertPaddingBRElementResult = 8750 InsertPaddingBRElementForEmptyLastLineWithTransaction(endOfNode); 8751 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) { 8752 NS_WARNING( 8753 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() " 8754 "failed"); 8755 return insertPaddingBRElementResult.unwrapErr(); 8756 } 8757 CreateElementResult unwrappedInsertPaddingBRElementResult = 8758 insertPaddingBRElementResult.unwrap(); 8759 unwrappedInsertPaddingBRElementResult.MoveCaretPointTo( 8760 pointToPutCaret, *this, 8761 {SuggestCaret::OnlyIfHasSuggestion, 8762 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 8763 } 8764 if (pointToPutCaret.IsSet()) { 8765 nsresult rv = CollapseSelectionTo(pointToPutCaret); 8766 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 8767 NS_WARNING( 8768 "EditorBase::CollapseSelectionTo() caused destroying the editor"); 8769 return NS_ERROR_EDITOR_DESTROYED; 8770 } 8771 NS_WARNING_ASSERTION( 8772 NS_SUCCEEDED(rv), 8773 "EditorBase::CollapseSelectionTo() failed, but ignored"); 8774 } 8775 return NS_OK; 8776 } 8777 8778 void HTMLEditor::SetSelectionInterlinePosition() { 8779 MOZ_ASSERT(IsEditActionDataAvailable()); 8780 MOZ_ASSERT(SelectionRef().IsCollapsed()); 8781 8782 // Get the (collapsed) selection location 8783 const nsRange* firstRange = SelectionRef().GetRangeAt(0); 8784 if (NS_WARN_IF(!firstRange)) { 8785 return; 8786 } 8787 8788 EditorDOMPoint atCaret(firstRange->StartRef()); 8789 if (NS_WARN_IF(!atCaret.IsSet())) { 8790 return; 8791 } 8792 MOZ_ASSERT(atCaret.IsSetAndValid()); 8793 8794 // First, let's check to see if we are after a `<br>`. We take care of this 8795 // special-case first so that we don't accidentally fall through into one of 8796 // the other conditionals. 8797 // XXX Although I don't understand "interline position", if caret is 8798 // immediately after non-editable contents, but previous editable 8799 // content is `<br>`, does this do right thing? 8800 if (Element* editingHost = ComputeEditingHost()) { 8801 if (nsIContent* previousEditableContentInBlock = 8802 HTMLEditUtils::GetPreviousContent( 8803 atCaret, 8804 {WalkTreeOption::IgnoreNonEditableNode, 8805 WalkTreeOption::StopAtBlockBoundary}, 8806 BlockInlineCheck::UseComputedDisplayStyle, editingHost)) { 8807 if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br)) { 8808 DebugOnly<nsresult> rvIgnored = SelectionRef().SetInterlinePosition( 8809 InterlinePosition::StartOfNextLine); 8810 NS_WARNING_ASSERTION( 8811 NS_SUCCEEDED(rvIgnored), 8812 "Selection::SetInterlinePosition(InterlinePosition::" 8813 "StartOfNextLine) failed, but ignored"); 8814 return; 8815 } 8816 } 8817 } 8818 8819 if (!atCaret.GetChild()) { 8820 return; 8821 } 8822 8823 // If caret is immediately after a block, set interline position to "right". 8824 // XXX Although I don't understand "interline position", if caret is 8825 // immediately after non-editable contents, but previous editable 8826 // content is a block, does this do right thing? 8827 if (nsIContent* previousEditableContentInBlockAtCaret = 8828 HTMLEditUtils::GetPreviousSibling( 8829 *atCaret.GetChild(), {WalkTreeOption::IgnoreNonEditableNode})) { 8830 if (HTMLEditUtils::IsBlockElement( 8831 *previousEditableContentInBlockAtCaret, 8832 BlockInlineCheck::UseComputedDisplayStyle)) { 8833 DebugOnly<nsresult> rvIgnored = SelectionRef().SetInterlinePosition( 8834 InterlinePosition::StartOfNextLine); 8835 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 8836 "Selection::SetInterlinePosition(InterlinePosition::" 8837 "StartOfNextLine) failed, but ignored"); 8838 return; 8839 } 8840 } 8841 8842 // If caret is immediately before a block, set interline position to "left". 8843 // XXX Although I don't understand "interline position", if caret is 8844 // immediately before non-editable contents, but next editable 8845 // content is a block, does this do right thing? 8846 if (nsIContent* nextEditableContentInBlockAtCaret = 8847 HTMLEditUtils::GetNextSibling( 8848 *atCaret.GetChild(), {WalkTreeOption::IgnoreNonEditableNode})) { 8849 if (HTMLEditUtils::IsBlockElement( 8850 *nextEditableContentInBlockAtCaret, 8851 BlockInlineCheck::UseComputedDisplayStyle)) { 8852 DebugOnly<nsresult> rvIgnored = 8853 SelectionRef().SetInterlinePosition(InterlinePosition::EndOfLine); 8854 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 8855 "Selection::SetInterlinePosition(InterlinePosition::" 8856 "EndOfLine) failed, but ignored"); 8857 } 8858 } 8859 } 8860 8861 nsresult HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement( 8862 nsIEditor::EDirection aDirectionAndAmount) { 8863 MOZ_ASSERT(IsEditActionDataAvailable()); 8864 MOZ_ASSERT(SelectionRef().IsCollapsed()); 8865 8866 auto point = GetFirstSelectionStartPoint<EditorDOMPoint>(); 8867 if (NS_WARN_IF(!point.IsInContentNode())) { 8868 return NS_ERROR_FAILURE; 8869 } 8870 8871 // If selection start is not editable, climb up the tree until editable one. 8872 while (!EditorUtils::IsEditableContent(*point.ContainerAs<nsIContent>(), 8873 EditorType::HTML)) { 8874 point.Set(point.GetContainer()); 8875 if (NS_WARN_IF(!point.IsInContentNode())) { 8876 return NS_ERROR_FAILURE; 8877 } 8878 } 8879 8880 // If caret is in empty block element, we need to insert a `<br>` element 8881 // because the block should have one-line height. 8882 // XXX Even if only a part of the block is editable, shouldn't we put 8883 // caret if the block element is now empty? 8884 if (Element* const editableBlockElement = 8885 HTMLEditUtils::GetInclusiveAncestorElement( 8886 *point.ContainerAs<nsIContent>(), 8887 HTMLEditUtils::ClosestEditableBlockElement, 8888 BlockInlineCheck::UseComputedDisplayStyle)) { 8889 if (editableBlockElement && 8890 HTMLEditUtils::IsEmptyNode( 8891 *editableBlockElement, 8892 {EmptyCheckOption::TreatSingleBRElementAsVisible}) && 8893 HTMLEditUtils::CanNodeContain(*point.GetContainer(), *nsGkAtoms::br)) { 8894 Element* bodyOrDocumentElement = GetRoot(); 8895 if (NS_WARN_IF(!bodyOrDocumentElement)) { 8896 return NS_ERROR_FAILURE; 8897 } 8898 if (point.GetContainer() == bodyOrDocumentElement) { 8899 // Our root node is completely empty. Don't add a <br> here. 8900 // AfterEditInner() will add one for us when it calls 8901 // EditorBase::MaybeCreatePaddingBRElementForEmptyEditor(). 8902 // XXX This kind of dependency between methods makes us spaghetti. 8903 // Let's handle it here later. 8904 // XXX This looks odd check. If active editing host is not a 8905 // `<body>`, what are we doing? 8906 return NS_OK; 8907 } 8908 Result<CreateElementResult, nsresult> insertPaddingBRElementResult = 8909 InsertPaddingBRElementForEmptyLastLineWithTransaction(point); 8910 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) { 8911 NS_WARNING( 8912 "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction(" 8913 ") failed"); 8914 return insertPaddingBRElementResult.unwrapErr(); 8915 } 8916 nsresult rv = insertPaddingBRElementResult.inspect().SuggestCaretPointTo( 8917 *this, {SuggestCaret::OnlyIfHasSuggestion, 8918 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 8919 SuggestCaret::AndIgnoreTrivialError}); 8920 if (NS_FAILED(rv)) { 8921 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed"); 8922 return rv; 8923 } 8924 NS_WARNING_ASSERTION( 8925 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 8926 "CreateElementResult::SuggestCaretPointTo() failed, but ignored"); 8927 return NS_OK; 8928 } 8929 } 8930 8931 // XXX Perhaps, we should do something if we're in a data node but not 8932 // a text node. 8933 if (point.IsInTextNode()) { 8934 return NS_OK; 8935 } 8936 8937 // Do we need to insert a padding <br> element for empty last line? We do 8938 // if we are: 8939 // 1) prior node is in same block where selection is AND 8940 // 2) prior node is a br AND 8941 // 3) that br is not visible 8942 RefPtr<Element> editingHost = ComputeEditingHost(); 8943 if (!editingHost) { 8944 return NS_OK; 8945 } 8946 8947 if (nsCOMPtr<nsIContent> previousEditableContent = 8948 HTMLEditUtils::GetPreviousContent( 8949 point, {WalkTreeOption::IgnoreNonEditableNode}, 8950 BlockInlineCheck::UseComputedDisplayStyle, editingHost)) { 8951 // If caret and previous editable content are in same block element 8952 // (even if it's a non-editable element), we should put a padding <br> 8953 // element at end of the block. 8954 const Element* const blockElementContainingCaret = 8955 HTMLEditUtils::GetInclusiveAncestorElement( 8956 *point.ContainerAs<nsIContent>(), 8957 HTMLEditUtils::ClosestBlockElement, 8958 BlockInlineCheck::UseComputedDisplayStyle); 8959 const Element* const blockElementContainingPreviousEditableContent = 8960 HTMLEditUtils::GetAncestorElement( 8961 *previousEditableContent, HTMLEditUtils::ClosestBlockElement, 8962 BlockInlineCheck::UseComputedDisplayStyle); 8963 // If previous editable content of caret is in same block and a `<br>` 8964 // element, we need to adjust interline position. 8965 if (blockElementContainingCaret && 8966 blockElementContainingCaret == 8967 blockElementContainingPreviousEditableContent && 8968 point.ContainerAs<nsIContent>()->GetEditingHost() == 8969 previousEditableContent->GetEditingHost() && 8970 previousEditableContent && 8971 previousEditableContent->IsHTMLElement(nsGkAtoms::br)) { 8972 // If it's an invisible `<br>` element, we need to insert a padding 8973 // `<br>` element for making empty line have one-line height. 8974 if (HTMLEditUtils::IsInvisibleBRElement(*previousEditableContent) && 8975 !EditorUtils::IsPaddingBRElementForEmptyLastLine( 8976 *previousEditableContent)) { 8977 AutoEditorDOMPointChildInvalidator lockOffset(point); 8978 Result<CreateElementResult, nsresult> insertPaddingBRElementResult = 8979 InsertPaddingBRElementForEmptyLastLineWithTransaction(point); 8980 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) { 8981 NS_WARNING( 8982 "HTMLEditor::" 8983 "InsertPaddingBRElementForEmptyLastLineWithTransaction() failed"); 8984 return insertPaddingBRElementResult.unwrapErr(); 8985 } 8986 insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion(); 8987 nsresult rv = CollapseSelectionTo(EditorRawDOMPoint( 8988 insertPaddingBRElementResult.inspect().GetNewNode(), 8989 InterlinePosition::StartOfNextLine)); 8990 if (NS_FAILED(rv)) { 8991 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 8992 return rv; 8993 } 8994 } 8995 // If it's a visible `<br>` element and next editable content is a 8996 // padding `<br>` element, we need to set interline position. 8997 else if (nsIContent* nextEditableContentInBlock = 8998 HTMLEditUtils::GetNextContent( 8999 *previousEditableContent, 9000 {WalkTreeOption::IgnoreNonEditableNode, 9001 WalkTreeOption::StopAtBlockBoundary}, 9002 BlockInlineCheck::UseComputedDisplayStyle, 9003 editingHost)) { 9004 if (EditorUtils::IsPaddingBRElementForEmptyLastLine( 9005 *nextEditableContentInBlock)) { 9006 // Make it stick to the padding `<br>` element so that it will be 9007 // on blank line. 9008 DebugOnly<nsresult> rvIgnored = SelectionRef().SetInterlinePosition( 9009 InterlinePosition::StartOfNextLine); 9010 NS_WARNING_ASSERTION( 9011 NS_SUCCEEDED(rvIgnored), 9012 "Selection::SetInterlinePosition(InterlinePosition::" 9013 "StartOfNextLine) failed, but ignored"); 9014 } 9015 } 9016 } 9017 } 9018 9019 // If previous editable content in same block is `<br>`, text node, `<img>` 9020 // or `<hr>`, current caret position is fine. 9021 if (nsIContent* const previousEditableContentInBlock = 9022 HTMLEditUtils::GetPreviousContent( 9023 point, 9024 {WalkTreeOption::IgnoreNonEditableNode, 9025 WalkTreeOption::StopAtBlockBoundary}, 9026 BlockInlineCheck::UseComputedDisplayStyle, editingHost)) { 9027 if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br) || 9028 previousEditableContentInBlock->IsText() || 9029 HTMLEditUtils::IsImageElement(*previousEditableContentInBlock) || 9030 previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::hr)) { 9031 return NS_OK; 9032 } 9033 } 9034 9035 // If next editable content in same block is `<br>`, text node, `<img>` or 9036 // `<hr>`, current caret position is fine. 9037 if (nsIContent* nextEditableContentInBlock = HTMLEditUtils::GetNextContent( 9038 point, 9039 {WalkTreeOption::IgnoreNonEditableNode, 9040 WalkTreeOption::StopAtBlockBoundary}, 9041 BlockInlineCheck::UseComputedDisplayStyle, editingHost)) { 9042 if (nextEditableContentInBlock->IsText() || 9043 nextEditableContentInBlock->IsAnyOfHTMLElements( 9044 nsGkAtoms::br, nsGkAtoms::img, nsGkAtoms::hr)) { 9045 return NS_OK; 9046 } 9047 } 9048 9049 // Otherwise, look for a near editable content towards edit action direction. 9050 9051 // If there is no editable content, keep current caret position. 9052 // XXX Why do we treat `nsIEditor::ePreviousWord` etc as forward direction? 9053 nsIContent* nearEditableContent = HTMLEditUtils::GetAdjacentContentToPutCaret( 9054 point, 9055 aDirectionAndAmount == nsIEditor::ePrevious ? WalkTreeDirection::Backward 9056 : WalkTreeDirection::Forward, 9057 *editingHost); 9058 if (!nearEditableContent) { 9059 return NS_OK; 9060 } 9061 9062 EditorRawDOMPoint pointToPutCaret = 9063 HTMLEditUtils::GetGoodCaretPointFor<EditorRawDOMPoint>( 9064 *nearEditableContent, aDirectionAndAmount); 9065 if (!pointToPutCaret.IsSet()) { 9066 NS_WARNING("HTMLEditUtils::GetGoodCaretPointFor() failed"); 9067 return NS_ERROR_FAILURE; 9068 } 9069 nsresult rv = CollapseSelectionTo(pointToPutCaret); 9070 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 9071 "EditorBase::CollapseSelectionTo() failed"); 9072 return rv; 9073 } 9074 9075 nsresult HTMLEditor::RemoveEmptyNodesIn(const EditorDOMRange& aRange) { 9076 MOZ_ASSERT(IsEditActionDataAvailable()); 9077 MOZ_ASSERT(aRange.IsPositioned()); 9078 9079 // Some general notes on the algorithm used here: the goal is to examine all 9080 // the nodes in aRange, and remove the empty ones. We do this by 9081 // using a content iterator to traverse all the nodes in the range, and 9082 // placing the empty nodes into an array. After finishing the iteration, 9083 // we delete the empty nodes in the array. (They cannot be deleted as we 9084 // find them because that would invalidate the iterator.) 9085 // 9086 // Since checking to see if a node is empty can be costly for nodes with 9087 // many descendants, there are some optimizations made. I rely on the fact 9088 // that the iterator is post-order: it will visit children of a node before 9089 // visiting the parent node. So if I find that a child node is not empty, I 9090 // know that its parent is not empty without even checking. So I put the 9091 // parent on a "skipList" which is just a voidArray of nodes I can skip the 9092 // empty check on. If I encounter a node on the skiplist, i skip the 9093 // processing for that node and replace its slot in the skiplist with that 9094 // node's parent. 9095 // 9096 // An interesting idea is to go ahead and regard parent nodes that are NOT 9097 // on the skiplist as being empty (without even doing the IsEmptyNode check) 9098 // on the theory that if they weren't empty, we would have encountered a 9099 // non-empty child earlier and thus put this parent node on the skiplist. 9100 // 9101 // Unfortunately I can't use that strategy here, because the range may 9102 // include some children of a node while excluding others. Thus I could 9103 // find all the _examined_ children empty, but still not have an empty 9104 // parent. 9105 9106 const RawRangeBoundary endOfRange = [&]() { 9107 // If the range is not collapsed and end of the range is start of a 9108 // container, it means that the inclusive ancestor empty element may be 9109 // created by splitting the left nodes. 9110 if (aRange.Collapsed() || !aRange.IsInContentNodes() || 9111 !aRange.EndRef().IsStartOfContainer()) { 9112 return aRange.EndRef().ToRawRangeBoundary(); 9113 } 9114 nsINode* const commonAncestor = 9115 nsContentUtils::GetClosestCommonInclusiveAncestor( 9116 aRange.StartRef().ContainerAs<nsIContent>(), 9117 aRange.EndRef().ContainerAs<nsIContent>()); 9118 if (!commonAncestor) { 9119 return aRange.EndRef().ToRawRangeBoundary(); 9120 } 9121 nsIContent* maybeRightContent = nullptr; 9122 for (nsIContent* content : aRange.EndRef() 9123 .ContainerAs<nsIContent>() 9124 ->InclusiveAncestorsOfType<nsIContent>()) { 9125 if (!HTMLEditUtils::IsSimplyEditableNode(*content) || 9126 content == commonAncestor) { 9127 break; 9128 } 9129 if (aRange.StartRef().ContainerAs<nsIContent>() == content) { 9130 break; 9131 } 9132 EmptyCheckOptions options = { 9133 EmptyCheckOption::TreatListItemAsVisible, 9134 EmptyCheckOption::TreatTableCellAsVisible, 9135 EmptyCheckOption::TreatNonEditableContentAsInvisible}; 9136 if (!HTMLEditUtils::IsBlockElement( 9137 *content, BlockInlineCheck::UseComputedDisplayStyle)) { 9138 options += EmptyCheckOption::TreatSingleBRElementAsVisible; 9139 } 9140 if (!HTMLEditUtils::IsEmptyNode(*content, options)) { 9141 break; 9142 } 9143 maybeRightContent = content; 9144 } 9145 if (!maybeRightContent) { 9146 return aRange.EndRef().ToRawRangeBoundary(); 9147 } 9148 return EditorRawDOMPoint::After(*maybeRightContent).ToRawRangeBoundary(); 9149 }(); 9150 9151 PostContentIterator postOrderIter; 9152 nsresult rv = 9153 postOrderIter.Init(aRange.StartRef().ToRawRangeBoundary(), endOfRange); 9154 if (NS_FAILED(rv)) { 9155 NS_WARNING("PostContentIterator::Init() failed"); 9156 return rv; 9157 } 9158 9159 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfEmptyContents, 9160 arrayOfEmptyCites; 9161 9162 // Collect empty nodes first. 9163 { 9164 const bool isMailEditor = IsMailEditor(); 9165 AutoTArray<OwningNonNull<nsIContent>, 64> knownNonEmptyContents; 9166 Maybe<AutoClonedSelectionRangeArray> maybeSelectionRanges; 9167 for (; !postOrderIter.IsDone(); postOrderIter.Next()) { 9168 MOZ_ASSERT(postOrderIter.GetCurrentNode()->IsContent()); 9169 9170 nsIContent* content = postOrderIter.GetCurrentNode()->AsContent(); 9171 nsIContent* parentContent = content->GetParent(); 9172 9173 size_t idx = knownNonEmptyContents.IndexOf(content); 9174 if (idx != decltype(knownNonEmptyContents)::NoIndex) { 9175 // This node is on our skip list. Skip processing for this node, and 9176 // replace its value in the skip list with the value of its parent 9177 if (parentContent) { 9178 knownNonEmptyContents[idx] = parentContent; 9179 } 9180 continue; 9181 } 9182 9183 const bool isEmptyNode = [&]() { 9184 if (!content->IsElement()) { 9185 return false; 9186 } 9187 Element& element = *content->AsElement(); 9188 const bool isMailCite = 9189 isMailEditor && HTMLEditUtils::IsMailCiteElement(element); 9190 const bool isCandidate = [&]() { 9191 if (element.IsHTMLElement(nsGkAtoms::body)) { 9192 // Don't delete the body 9193 return false; 9194 } 9195 if (isMailCite || element.IsHTMLElement(nsGkAtoms::a) || 9196 HTMLEditUtils::IsInlineStyleElement(element) || 9197 HTMLEditUtils::IsListElement(element) || 9198 element.IsHTMLElement(nsGkAtoms::div)) { 9199 // Only consider certain nodes to be empty for purposes of removal 9200 return true; 9201 } 9202 if (HTMLEditUtils::IsFormatElementForFormatBlockCommand(element) || 9203 HTMLEditUtils::IsListItemElement(element) || 9204 element.IsHTMLElement(nsGkAtoms::blockquote)) { 9205 // These node types are candidates if selection is not in them. If 9206 // it is one of these, don't delete if selection inside. This is so 9207 // we can create empty headings, etc., for the user to type into. 9208 if (maybeSelectionRanges.isNothing()) { 9209 maybeSelectionRanges.emplace(SelectionRef()); 9210 } 9211 return !maybeSelectionRanges 9212 ->IsAtLeastOneContainerOfRangeBoundariesInclusiveDescendantOf( 9213 element); 9214 } 9215 return false; 9216 }(); 9217 9218 if (!isCandidate) { 9219 return false; 9220 } 9221 9222 // We delete mailcites even if they have a solo br in them. Other 9223 // nodes we require to be empty. 9224 HTMLEditUtils::EmptyCheckOptions options{ 9225 EmptyCheckOption::TreatListItemAsVisible, 9226 EmptyCheckOption::TreatTableCellAsVisible}; 9227 if (!isMailCite) { 9228 options += EmptyCheckOption::TreatSingleBRElementAsVisible; 9229 } else { 9230 // XXX Maybe unnecessary to specify this. 9231 options += EmptyCheckOption::TreatNonEditableContentAsInvisible; 9232 } 9233 if (!HTMLEditUtils::IsEmptyNode(*content, options)) { 9234 return false; 9235 } 9236 9237 if (isMailCite) { 9238 // mailcites go on a separate list from other empty nodes 9239 arrayOfEmptyCites.AppendElement(*content); 9240 } 9241 // Don't delete non-editable nodes in this method because this is a 9242 // clean up method to remove unnecessary nodes of the result of 9243 // editing. So, we shouldn't delete non-editable nodes which were 9244 // there before editing. Additionally, if the element is some special 9245 // elements such as <body>, we shouldn't delete it. 9246 else if (HTMLEditUtils::IsSimplyEditableNode(*content) && 9247 HTMLEditUtils::IsRemovableNode(*content)) { 9248 arrayOfEmptyContents.AppendElement(*content); 9249 } 9250 return true; 9251 }(); 9252 if (!isEmptyNode && parentContent) { 9253 knownNonEmptyContents.AppendElement(*parentContent); 9254 } 9255 } // end of the for-loop iterating with postOrderIter 9256 } 9257 9258 // now delete the empty nodes 9259 for (OwningNonNull<nsIContent>& emptyContent : arrayOfEmptyContents) { 9260 // MOZ_KnownLive due to bug 1622253 9261 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(emptyContent)); 9262 if (NS_FAILED(rv)) { 9263 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 9264 return rv; 9265 } 9266 } 9267 9268 // Now delete the empty mailcites. This is a separate step because we want 9269 // to pull out any br's and preserve them. 9270 EditorDOMPoint pointToPutCaret; 9271 for (OwningNonNull<nsIContent>& emptyCite : arrayOfEmptyCites) { 9272 if (!HTMLEditUtils::IsEmptyNode( 9273 emptyCite, 9274 {EmptyCheckOption::TreatSingleBRElementAsVisible, 9275 EmptyCheckOption::TreatListItemAsVisible, 9276 EmptyCheckOption::TreatTableCellAsVisible, 9277 EmptyCheckOption::TreatNonEditableContentAsInvisible})) { 9278 // We are deleting a cite that has just a `<br>`. We want to delete cite, 9279 // but preserve `<br>`. 9280 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 9281 InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement, 9282 EditorDOMPoint(emptyCite)); 9283 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 9284 NS_WARNING( 9285 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 9286 "LineBreakType::BRElement) failed"); 9287 return insertBRElementResultOrError.unwrapErr(); 9288 } 9289 CreateLineBreakResult insertBRElementResult = 9290 insertBRElementResultOrError.unwrap(); 9291 MOZ_ASSERT(insertBRElementResult.Handled()); 9292 // XXX Is this intentional selection change? 9293 insertBRElementResult.MoveCaretPointTo( 9294 pointToPutCaret, *this, 9295 {SuggestCaret::OnlyIfHasSuggestion, 9296 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 9297 } 9298 // MOZ_KnownLive because 'arrayOfEmptyCites' is guaranteed to keep it alive. 9299 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(emptyCite)); 9300 if (NS_FAILED(rv)) { 9301 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 9302 return rv; 9303 } 9304 } 9305 // XXX Is this intentional selection change? 9306 if (pointToPutCaret.IsSet()) { 9307 nsresult rv = CollapseSelectionTo(pointToPutCaret); 9308 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 9309 NS_WARNING( 9310 "EditorBase::CollapseSelectionTo() caused destroying the editor"); 9311 return NS_ERROR_EDITOR_DESTROYED; 9312 } 9313 NS_WARNING_ASSERTION( 9314 NS_SUCCEEDED(rv), 9315 "EditorBase::CollapseSelectionTo() failed, but ignored"); 9316 } 9317 9318 return NS_OK; 9319 } 9320 9321 nsresult HTMLEditor::LiftUpListItemElement( 9322 Element& aListItemElement, 9323 LiftUpFromAllParentListElements aLiftUpFromAllParentListElements) { 9324 MOZ_ASSERT(IsEditActionDataAvailable()); 9325 9326 if (!HTMLEditUtils::IsListItemElement(aListItemElement)) { 9327 return NS_ERROR_INVALID_ARG; 9328 } 9329 9330 if (NS_WARN_IF(!aListItemElement.GetParentElement()) || 9331 NS_WARN_IF(!aListItemElement.GetParentElement()->GetParentNode())) { 9332 return NS_ERROR_FAILURE; 9333 } 9334 9335 // if it's first or last list item, don't need to split the list 9336 // otherwise we do. 9337 const bool isFirstListItem = HTMLEditUtils::IsFirstChild( 9338 aListItemElement, {WalkTreeOption::IgnoreNonEditableNode}); 9339 const bool isLastListItem = HTMLEditUtils::IsLastChild( 9340 aListItemElement, {WalkTreeOption::IgnoreNonEditableNode}); 9341 9342 Element* leftListElement = aListItemElement.GetParentElement(); 9343 if (NS_WARN_IF(!leftListElement)) { 9344 return NS_ERROR_FAILURE; 9345 } 9346 9347 // If it's at middle of parent list element, split the parent list element. 9348 // Then, aListItem becomes the first list item of the right list element. 9349 if (!isFirstListItem && !isLastListItem) { 9350 EditorDOMPoint atListItemElement(&aListItemElement); 9351 if (NS_WARN_IF(!atListItemElement.IsSet())) { 9352 return NS_ERROR_FAILURE; 9353 } 9354 MOZ_ASSERT(atListItemElement.IsSetAndValid()); 9355 Result<SplitNodeResult, nsresult> splitListItemParentResult = 9356 SplitNodeWithTransaction(atListItemElement); 9357 if (MOZ_UNLIKELY(splitListItemParentResult.isErr())) { 9358 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed"); 9359 return splitListItemParentResult.unwrapErr(); 9360 } 9361 nsresult rv = splitListItemParentResult.inspect().SuggestCaretPointTo( 9362 *this, {SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 9363 if (NS_FAILED(rv)) { 9364 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed"); 9365 return rv; 9366 } 9367 9368 leftListElement = 9369 splitListItemParentResult.inspect().GetPreviousContentAs<Element>(); 9370 if (MOZ_UNLIKELY(!leftListElement)) { 9371 NS_WARNING( 9372 "HTMLEditor::SplitNodeWithTransaction() didn't return left list " 9373 "element"); 9374 return NS_ERROR_FAILURE; 9375 } 9376 } 9377 9378 // In most cases, insert the list item into the new left list node.. 9379 EditorDOMPoint pointToInsertListItem(leftListElement); 9380 if (NS_WARN_IF(!pointToInsertListItem.IsInContentNode())) { 9381 return NS_ERROR_FAILURE; 9382 } 9383 9384 // But when the list item was the first child of the right list, it should 9385 // be inserted between the both list elements. This allows user to hit 9386 // Enter twice at a list item breaks the parent list node. 9387 if (!isFirstListItem) { 9388 DebugOnly<bool> advanced = pointToInsertListItem.AdvanceOffset(); 9389 NS_WARNING_ASSERTION(advanced, 9390 "Failed to advance offset to right list node"); 9391 } 9392 9393 EditorDOMPoint pointToPutCaret; 9394 { 9395 Result<MoveNodeResult, nsresult> moveListItemElementResult = 9396 MoveNodeWithTransaction(aListItemElement, pointToInsertListItem); 9397 if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) { 9398 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed"); 9399 return moveListItemElementResult.unwrapErr(); 9400 } 9401 MoveNodeResult unwrappedMoveListItemElementResult = 9402 moveListItemElementResult.unwrap(); 9403 unwrappedMoveListItemElementResult.MoveCaretPointTo( 9404 pointToPutCaret, *this, 9405 {SuggestCaret::OnlyIfHasSuggestion, 9406 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 9407 } 9408 9409 // Unwrap list item contents if they are no longer in a list 9410 // XXX If the parent list element is a child of another list element 9411 // (although invalid tree), the list item element won't be unwrapped. 9412 // That makes the parent ancestor element tree valid, but might be 9413 // unexpected result. 9414 // XXX If aListItemElement is <dl> or <dd> and current parent is <ul> or <ol>, 9415 // the list items won't be unwrapped. If aListItemElement is <li> and its 9416 // current parent is <dl>, there is same issue. 9417 if (!HTMLEditUtils::IsListElement( 9418 *pointToInsertListItem.ContainerAs<nsIContent>()) && 9419 HTMLEditUtils::IsListItemElement(aListItemElement)) { 9420 Result<EditorDOMPoint, nsresult> unwrapOrphanListItemElementResult = 9421 RemoveBlockContainerWithTransaction(aListItemElement); 9422 if (MOZ_UNLIKELY(unwrapOrphanListItemElementResult.isErr())) { 9423 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed"); 9424 return unwrapOrphanListItemElementResult.unwrapErr(); 9425 } 9426 if (AllowsTransactionsToChangeSelection() && 9427 unwrapOrphanListItemElementResult.inspect().IsSet()) { 9428 pointToPutCaret = unwrapOrphanListItemElementResult.unwrap(); 9429 } 9430 if (!pointToPutCaret.IsSet()) { 9431 return NS_OK; 9432 } 9433 nsresult rv = CollapseSelectionTo(pointToPutCaret); 9434 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 9435 "EditorBase::CollapseSelectionTo() failed"); 9436 return rv; 9437 } 9438 9439 if (pointToPutCaret.IsSet()) { 9440 nsresult rv = CollapseSelectionTo(pointToPutCaret); 9441 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 9442 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 9443 return rv; 9444 } 9445 NS_WARNING_ASSERTION( 9446 NS_SUCCEEDED(rv), 9447 "EditorBase::CollapseSelectionTo() failed, but ignored"); 9448 } 9449 9450 if (aLiftUpFromAllParentListElements == LiftUpFromAllParentListElements::No) { 9451 return NS_OK; 9452 } 9453 // XXX If aListItemElement is moved to unexpected element by mutation event 9454 // listener, shouldn't we stop calling this? 9455 nsresult rv = LiftUpListItemElement(aListItemElement, 9456 LiftUpFromAllParentListElements::Yes); 9457 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 9458 "HTMLEditor::LiftUpListItemElement(" 9459 "LiftUpFromAllParentListElements::Yes) failed"); 9460 return rv; 9461 } 9462 9463 nsresult HTMLEditor::DestroyListStructureRecursively(Element& aListElement) { 9464 MOZ_ASSERT(IsEditActionDataAvailable()); 9465 MOZ_ASSERT(HTMLEditUtils::IsListElement(aListElement)); 9466 9467 // XXX If mutation event listener inserts new child into `aListElement`, 9468 // this becomes infinite loop so that we should set limit of the 9469 // loop count from original child count. 9470 while (aListElement.GetFirstChild()) { 9471 const OwningNonNull<nsIContent> child = *aListElement.GetFirstChild(); 9472 9473 if (HTMLEditUtils::IsListItemElement(*child)) { 9474 // XXX Using LiftUpListItemElement() is too expensive for this purpose. 9475 // Looks like the reason why this method uses it is, only this loop 9476 // wants to work with first child of aListElement. However, what it 9477 // actually does is removing <li> as container. Perhaps, we should 9478 // decide destination first, and then, move contents in `child`. 9479 // XXX If aListElement is is a child of another list element (although 9480 // it's invalid tree), this moves the list item to outside of 9481 // aListElement's parent. Is that really intentional behavior? 9482 nsresult rv = LiftUpListItemElement( 9483 MOZ_KnownLive(*child->AsElement()), 9484 HTMLEditor::LiftUpFromAllParentListElements::Yes); 9485 if (NS_FAILED(rv)) { 9486 NS_WARNING( 9487 "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:" 9488 ":Yes) failed"); 9489 return rv; 9490 } 9491 continue; 9492 } 9493 9494 if (HTMLEditUtils::IsListElement(*child)) { 9495 nsresult rv = 9496 DestroyListStructureRecursively(MOZ_KnownLive(*child->AsElement())); 9497 if (NS_FAILED(rv)) { 9498 NS_WARNING("HTMLEditor::DestroyListStructureRecursively() failed"); 9499 return rv; 9500 } 9501 continue; 9502 } 9503 9504 // Delete any non-list items for now 9505 // XXX This is not HTML5 aware. HTML5 allows all list elements to have 9506 // <script> and <template> and <dl> element to have <div> to group 9507 // some <dt> and <dd> elements. So, this may break valid children. 9508 nsresult rv = DeleteNodeWithTransaction(*child); 9509 if (NS_FAILED(rv)) { 9510 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 9511 return rv; 9512 } 9513 } 9514 9515 // Delete the now-empty list 9516 const Result<EditorDOMPoint, nsresult> unwrapListElementResult = 9517 RemoveBlockContainerWithTransaction(aListElement); 9518 if (MOZ_UNLIKELY(unwrapListElementResult.isErr())) { 9519 NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed"); 9520 return unwrapListElementResult.inspectErr(); 9521 } 9522 const EditorDOMPoint& pointToPutCaret = unwrapListElementResult.inspect(); 9523 if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret.IsSet()) { 9524 return NS_OK; 9525 } 9526 nsresult rv = CollapseSelectionTo(pointToPutCaret); 9527 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 9528 "EditorBase::CollapseSelectionTo() failed"); 9529 return rv; 9530 } 9531 9532 nsresult HTMLEditor::EnsureSelectionInBodyOrDocumentElement() { 9533 MOZ_ASSERT(IsEditActionDataAvailable()); 9534 9535 RefPtr<Element> bodyOrDocumentElement = GetRoot(); 9536 if (NS_WARN_IF(!bodyOrDocumentElement)) { 9537 return NS_ERROR_FAILURE; 9538 } 9539 9540 const auto atCaret = GetFirstSelectionStartPoint<EditorRawDOMPoint>(); 9541 if (NS_WARN_IF(!atCaret.IsSet())) { 9542 return NS_ERROR_FAILURE; 9543 } 9544 9545 // XXX This does wrong things. Web apps can put any elements as sibling 9546 // of `<body>` element. Therefore, this collapses `Selection` into 9547 // the `<body>` element which `HTMLDocument.body` is set to. So, 9548 // this makes users impossible to modify content outside of the 9549 // `<body>` element even if caret is in an editing host. 9550 9551 // Check that selection start container is inside the <body> element. 9552 // XXXsmaug this code is insane. 9553 nsINode* temp = atCaret.GetContainer(); 9554 while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) { 9555 temp = temp->GetParentOrShadowHostNode(); 9556 } 9557 9558 // If we aren't in the <body> element, force the issue. 9559 if (!temp) { 9560 nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement); 9561 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 9562 NS_WARNING( 9563 "EditorBase::CollapseSelectionToStartOf() caused destroying the " 9564 "editor"); 9565 return NS_ERROR_EDITOR_DESTROYED; 9566 } 9567 NS_WARNING_ASSERTION( 9568 NS_SUCCEEDED(rv), 9569 "EditorBase::CollapseSelectionToStartOf() failed, but ignored"); 9570 return NS_OK; 9571 } 9572 9573 const auto selectionEndPoint = GetFirstSelectionEndPoint<EditorRawDOMPoint>(); 9574 if (NS_WARN_IF(!selectionEndPoint.IsSet())) { 9575 return NS_ERROR_FAILURE; 9576 } 9577 9578 // check that selNode is inside body 9579 // XXXsmaug this code is insane. 9580 temp = selectionEndPoint.GetContainer(); 9581 while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) { 9582 temp = temp->GetParentOrShadowHostNode(); 9583 } 9584 9585 // If we aren't in the <body> element, force the issue. 9586 if (!temp) { 9587 nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement); 9588 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 9589 NS_WARNING( 9590 "EditorBase::CollapseSelectionToStartOf() caused destroying the " 9591 "editor"); 9592 return NS_ERROR_EDITOR_DESTROYED; 9593 } 9594 NS_WARNING_ASSERTION( 9595 NS_SUCCEEDED(rv), 9596 "EditorBase::CollapseSelectionToStartOf() failed, but ignored"); 9597 } 9598 9599 return NS_OK; 9600 } 9601 9602 Result<CreateLineBreakResult, nsresult> 9603 HTMLEditor::InsertPaddingBRElementIfInEmptyBlock( 9604 const EditorDOMPoint& aPoint, 9605 nsIEditor::EStripWrappers aDeleteEmptyInlines) { 9606 MOZ_ASSERT(IsEditActionDataAvailable()); 9607 9608 if (MOZ_UNLIKELY(!aPoint.IsInContentNode())) { 9609 return CreateLineBreakResult::NotHandled(); 9610 } 9611 9612 const RefPtr<Element> editableBlockElement = 9613 HTMLEditUtils::GetInclusiveAncestorElement( 9614 *aPoint.ContainerAs<nsIContent>(), 9615 HTMLEditUtils::ClosestEditableBlockElement, 9616 BlockInlineCheck::UseComputedDisplayStyle); 9617 9618 if (!editableBlockElement || 9619 !HTMLEditUtils::IsEmptyNode( 9620 *editableBlockElement, 9621 {EmptyCheckOption::TreatSingleBRElementAsVisible, 9622 EmptyCheckOption::TreatBlockAsVisible})) { 9623 return CreateLineBreakResult::NotHandled(); 9624 } 9625 9626 EditorDOMPoint pointToInsertLineBreak; 9627 if (aDeleteEmptyInlines == nsIEditor::eStrip && 9628 aPoint.ContainerAs<nsIContent>() != editableBlockElement) { 9629 nsCOMPtr<nsIContent> emptyInlineAncestor = 9630 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 9631 *aPoint.ContainerAs<nsIContent>(), 9632 BlockInlineCheck::UseComputedDisplayStyle); 9633 if (!emptyInlineAncestor) { 9634 emptyInlineAncestor = aPoint.ContainerAs<nsIContent>(); 9635 } 9636 nsresult rv = DeleteNodeWithTransaction(*emptyInlineAncestor); 9637 if (NS_FAILED(rv)) { 9638 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 9639 return Err(rv); 9640 } 9641 pointToInsertLineBreak = EditorDOMPoint(editableBlockElement, 0u); 9642 } else { 9643 pointToInsertLineBreak = aPoint; 9644 } 9645 9646 // TODO: Use InsertLineBreak instead even if we're inserting a <br>. 9647 Result<CreateElementResult, nsresult> insertPaddingLineBreakResultOrError = 9648 InsertPaddingBRElementForEmptyLastLineWithTransaction( 9649 pointToInsertLineBreak); 9650 if (MOZ_UNLIKELY(insertPaddingLineBreakResultOrError.isErr())) { 9651 NS_WARNING( 9652 "EditorBase::InsertPaddingBRElementForEmptyLastLineWithTransaction() " 9653 "failed"); 9654 return insertPaddingLineBreakResultOrError.propagateErr(); 9655 } 9656 CreateElementResult insertPaddingLineBreakResult = 9657 insertPaddingLineBreakResultOrError.unwrap(); 9658 RefPtr<HTMLBRElement> paddingBRElement = 9659 HTMLBRElement::FromNodeOrNull(insertPaddingLineBreakResult.GetNewNode()); 9660 if (NS_WARN_IF(!paddingBRElement)) { 9661 return Err(NS_ERROR_FAILURE); 9662 } 9663 if (NS_WARN_IF(!paddingBRElement->IsInComposedDoc())) { 9664 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 9665 } 9666 insertPaddingLineBreakResult.IgnoreCaretPointSuggestion(); 9667 return CreateLineBreakResult(EditorLineBreak(std::move(paddingBRElement)), 9668 EditorDOMPoint(paddingBRElement)); 9669 } 9670 9671 Result<CreateLineBreakResult, nsresult> 9672 HTMLEditor::InsertPaddingBRElementIfNeeded( 9673 const EditorDOMPoint& aPoint, nsIEditor::EStripWrappers aDeleteEmptyInlines, 9674 const Element& aEditingHost) { 9675 MOZ_ASSERT(aPoint.IsInContentNode()); 9676 MOZ_ASSERT(HTMLEditUtils::NodeIsEditableOrNotInComposedDoc( 9677 *aPoint.ContainerAs<nsIContent>())); 9678 9679 auto pointToInsertPaddingBR = [&]() MOZ_NEVER_INLINE_DEBUG -> EditorDOMPoint { 9680 // If the point is immediately before a block boundary which is for a 9681 // mailcite in plaintext mail composer (it is a <span> styled as block), we 9682 // should not treat it as a block because it's required by the serializer to 9683 // give the mailcite contents are not appear with outer content in the same 9684 // lines. 9685 if (IsPlaintextMailComposer()) { 9686 const WSScanResult nextVisibleThing = 9687 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 9688 {WSRunScanner::Option::OnlyEditableNodes}, aPoint); 9689 if (nextVisibleThing.ReachedBlockBoundary() && 9690 HTMLEditUtils::IsMailCiteElement(*nextVisibleThing.ElementPtr()) && 9691 HTMLEditUtils::IsInlineContent( 9692 *nextVisibleThing.ElementPtr(), 9693 BlockInlineCheck::UseHTMLDefaultStyle)) { 9694 return nextVisibleThing.ReachedCurrentBlockBoundary() 9695 ? EditorDOMPoint::AtEndOf(*nextVisibleThing.ElementPtr()) 9696 : EditorDOMPoint(nextVisibleThing.ElementPtr()); 9697 } 9698 } 9699 return HTMLEditUtils::LineRequiresPaddingLineBreakToBeVisible(aPoint, 9700 aEditingHost); 9701 }(); 9702 if (!pointToInsertPaddingBR.IsSet()) { 9703 return CreateLineBreakResult::NotHandled(); 9704 } 9705 if (aDeleteEmptyInlines == nsIEditor::eStrip && 9706 pointToInsertPaddingBR.IsContainerElement() && 9707 HTMLEditUtils::IsEmptyInlineContainer( 9708 *pointToInsertPaddingBR.ContainerAs<Element>(), 9709 {EmptyCheckOption::TreatSingleBRElementAsVisible, 9710 EmptyCheckOption::TreatBlockAsVisible, 9711 EmptyCheckOption::TreatListItemAsVisible, 9712 EmptyCheckOption::TreatTableCellAsVisible}, 9713 BlockInlineCheck::UseComputedDisplayStyle)) { 9714 RefPtr<Element> emptyInlineAncestor = 9715 HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 9716 *pointToInsertPaddingBR.ContainerAs<nsIContent>(), 9717 BlockInlineCheck::UseComputedDisplayStyle); 9718 if (!emptyInlineAncestor) { 9719 emptyInlineAncestor = pointToInsertPaddingBR.ContainerAs<Element>(); 9720 } 9721 AutoTrackDOMPoint trackPointToInsertPaddingBR(RangeUpdaterRef(), 9722 &pointToInsertPaddingBR); 9723 nsresult rv = DeleteNodeWithTransaction(*emptyInlineAncestor); 9724 if (NS_FAILED(rv)) { 9725 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 9726 return Err(rv); 9727 } 9728 } 9729 9730 // Padding <br> elements may appear and disappear a lot even during IME has a 9731 // composition. Therefore, IME may be confused with the mutation if we use 9732 // normal <br> element since it does not match with expectation of IME. For 9733 // hiding the mutations from IME, we need to set the new <br> element flag to 9734 // NS_PADDING_FOR_EMPTY_LAST_LINE. 9735 Result<CreateElementResult, nsresult> insertPaddingBRResultOrError = 9736 InsertBRElement(WithTransaction::Yes, 9737 BRElementType::PaddingForEmptyLastLine, 9738 pointToInsertPaddingBR); 9739 if (MOZ_UNLIKELY(insertPaddingBRResultOrError.isErr())) { 9740 NS_WARNING( 9741 "EditorBase::InsertBRElement(WithTransaction::Yes, " 9742 "BRElementType::PaddingForEmptyLastLine) failed"); 9743 return insertPaddingBRResultOrError.propagateErr(); 9744 } 9745 return CreateLineBreakResult(insertPaddingBRResultOrError.unwrap()); 9746 } 9747 9748 Result<EditorDOMPoint, nsresult> HTMLEditor::RemoveAlignFromDescendants( 9749 Element& aElement, const nsAString& aAlignType, EditTarget aEditTarget) { 9750 MOZ_ASSERT(IsEditActionDataAvailable()); 9751 MOZ_ASSERT(!aElement.IsHTMLElement(nsGkAtoms::table)); 9752 9753 const bool useCSS = IsCSSEnabled(); 9754 9755 EditorDOMPoint pointToPutCaret; 9756 9757 // Let's remove all alignment hints in the children of aNode; it can 9758 // be an ALIGN attribute (in case we just remove it) or a CENTER 9759 // element (here we have to remove the container and keep its 9760 // children). We break on tables and don't look at their children. 9761 nsCOMPtr<nsIContent> nextSibling; 9762 for (nsIContent* content = 9763 aEditTarget == EditTarget::NodeAndDescendantsExceptTable 9764 ? &aElement 9765 : aElement.GetFirstChild(); 9766 content; content = nextSibling) { 9767 // Get the next sibling before removing content from the DOM tree. 9768 // XXX If next sibling is removed from the parent and/or inserted to 9769 // different parent, we will behave unexpectedly. I think that 9770 // we should create child list and handle it with checking whether 9771 // it's still a child of expected parent. 9772 nextSibling = aEditTarget == EditTarget::NodeAndDescendantsExceptTable 9773 ? nullptr 9774 : content->GetNextSibling(); 9775 9776 if (content->IsHTMLElement(nsGkAtoms::center)) { 9777 OwningNonNull<Element> centerElement = *content->AsElement(); 9778 { 9779 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 9780 RemoveAlignFromDescendants(centerElement, aAlignType, 9781 EditTarget::OnlyDescendantsExceptTable); 9782 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 9783 NS_WARNING( 9784 "HTMLEditor::RemoveAlignFromDescendants(EditTarget::" 9785 "OnlyDescendantsExceptTable) failed"); 9786 return pointToPutCaretOrError; 9787 } 9788 if (pointToPutCaretOrError.inspect().IsSet()) { 9789 pointToPutCaret = pointToPutCaretOrError.unwrap(); 9790 } 9791 } 9792 9793 // We may have to insert a `<br>` element before first child of the 9794 // `<center>` element because it should be first element of a hard line 9795 // even after removing the `<center>` element. 9796 { 9797 Result<CreateElementResult, nsresult> 9798 maybeInsertBRElementBeforeFirstChildResult = 9799 EnsureHardLineBeginsWithFirstChildOf(centerElement); 9800 if (MOZ_UNLIKELY(maybeInsertBRElementBeforeFirstChildResult.isErr())) { 9801 NS_WARNING( 9802 "HTMLEditor::EnsureHardLineBeginsWithFirstChildOf() failed"); 9803 return maybeInsertBRElementBeforeFirstChildResult.propagateErr(); 9804 } 9805 CreateElementResult unwrappedResult = 9806 maybeInsertBRElementBeforeFirstChildResult.unwrap(); 9807 if (unwrappedResult.HasCaretPointSuggestion()) { 9808 pointToPutCaret = unwrappedResult.UnwrapCaretPoint(); 9809 } 9810 } 9811 9812 // We may have to insert a `<br>` element after last child of the 9813 // `<center>` element because it should be last element of a hard line 9814 // even after removing the `<center>` element. 9815 { 9816 Result<CreateElementResult, nsresult> 9817 maybeInsertBRElementAfterLastChildResult = 9818 EnsureHardLineEndsWithLastChildOf(centerElement); 9819 if (MOZ_UNLIKELY(maybeInsertBRElementAfterLastChildResult.isErr())) { 9820 NS_WARNING("HTMLEditor::EnsureHardLineEndsWithLastChildOf() failed"); 9821 return maybeInsertBRElementAfterLastChildResult.propagateErr(); 9822 } 9823 CreateElementResult unwrappedResult = 9824 maybeInsertBRElementAfterLastChildResult.unwrap(); 9825 if (unwrappedResult.HasCaretPointSuggestion()) { 9826 pointToPutCaret = unwrappedResult.UnwrapCaretPoint(); 9827 } 9828 } 9829 9830 { 9831 Result<EditorDOMPoint, nsresult> unwrapCenterElementResult = 9832 RemoveContainerWithTransaction(centerElement); 9833 if (MOZ_UNLIKELY(unwrapCenterElementResult.isErr())) { 9834 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed"); 9835 return unwrapCenterElementResult; 9836 } 9837 if (unwrapCenterElementResult.inspect().IsSet()) { 9838 pointToPutCaret = unwrapCenterElementResult.unwrap(); 9839 } 9840 } 9841 continue; 9842 } 9843 9844 if (!HTMLEditUtils::IsBlockElement(*content, 9845 BlockInlineCheck::UseHTMLDefaultStyle) && 9846 !content->IsHTMLElement(nsGkAtoms::hr)) { 9847 continue; 9848 } 9849 9850 const OwningNonNull<Element> blockOrHRElement = *content->AsElement(); 9851 if (HTMLEditUtils::IsAlignAttrSupported(blockOrHRElement)) { 9852 nsresult rv = 9853 RemoveAttributeWithTransaction(blockOrHRElement, *nsGkAtoms::align); 9854 if (NS_FAILED(rv)) { 9855 NS_WARNING( 9856 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::align) " 9857 "failed"); 9858 return Err(rv); 9859 } 9860 } 9861 if (useCSS) { 9862 if (blockOrHRElement->IsAnyOfHTMLElements(nsGkAtoms::table, 9863 nsGkAtoms::hr)) { 9864 nsresult rv = SetAttributeOrEquivalent( 9865 blockOrHRElement, nsGkAtoms::align, aAlignType, false); 9866 if (NS_WARN_IF(Destroyed())) { 9867 return Err(NS_ERROR_EDITOR_DESTROYED); 9868 } 9869 if (NS_FAILED(rv)) { 9870 NS_WARNING( 9871 "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed"); 9872 return Err(rv); 9873 } 9874 } else { 9875 nsStyledElement* styledBlockOrHRElement = 9876 nsStyledElement::FromNode(blockOrHRElement); 9877 if (NS_WARN_IF(!styledBlockOrHRElement)) { 9878 return Err(NS_ERROR_FAILURE); 9879 } 9880 // MOZ_KnownLive(*styledBlockOrHRElement): It's `blockOrHRElement 9881 // which is OwningNonNull. 9882 nsAutoString dummyCssValue; 9883 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 9884 CSSEditUtils::RemoveCSSInlineStyleWithTransaction( 9885 *this, MOZ_KnownLive(*styledBlockOrHRElement), 9886 nsGkAtoms::textAlign, dummyCssValue); 9887 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 9888 NS_WARNING( 9889 "CSSEditUtils::RemoveCSSInlineStyleWithTransaction(nsGkAtoms::" 9890 "textAlign) failed"); 9891 return pointToPutCaretOrError; 9892 } 9893 if (pointToPutCaretOrError.inspect().IsSet()) { 9894 pointToPutCaret = pointToPutCaretOrError.unwrap(); 9895 } 9896 } 9897 } 9898 if (!blockOrHRElement->IsHTMLElement(nsGkAtoms::table)) { 9899 // unless this is a table, look at children 9900 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 9901 RemoveAlignFromDescendants(blockOrHRElement, aAlignType, 9902 EditTarget::OnlyDescendantsExceptTable); 9903 if (pointToPutCaretOrError.isErr()) { 9904 NS_WARNING( 9905 "HTMLEditor::RemoveAlignFromDescendants(EditTarget::" 9906 "OnlyDescendantsExceptTable) failed"); 9907 return pointToPutCaretOrError; 9908 } 9909 if (pointToPutCaretOrError.inspect().IsSet()) { 9910 pointToPutCaret = pointToPutCaretOrError.unwrap(); 9911 } 9912 } 9913 } 9914 return pointToPutCaret; 9915 } 9916 9917 Result<CreateElementResult, nsresult> 9918 HTMLEditor::EnsureHardLineBeginsWithFirstChildOf( 9919 Element& aRemovingContainerElement) { 9920 MOZ_ASSERT(IsEditActionDataAvailable()); 9921 9922 nsIContent* firstEditableChild = HTMLEditUtils::GetFirstChild( 9923 aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode}); 9924 if (!firstEditableChild) { 9925 return CreateElementResult::NotHandled(); 9926 } 9927 9928 if (HTMLEditUtils::IsBlockElement( 9929 *firstEditableChild, BlockInlineCheck::UseComputedDisplayStyle) || 9930 firstEditableChild->IsHTMLElement(nsGkAtoms::br)) { 9931 return CreateElementResult::NotHandled(); 9932 } 9933 9934 nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousSibling( 9935 aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode}); 9936 if (!previousEditableContent) { 9937 return CreateElementResult::NotHandled(); 9938 } 9939 9940 if (HTMLEditUtils::IsBlockElement( 9941 *previousEditableContent, 9942 BlockInlineCheck::UseComputedDisplayStyle) || 9943 previousEditableContent->IsHTMLElement(nsGkAtoms::br)) { 9944 return CreateElementResult::NotHandled(); 9945 } 9946 9947 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 9948 InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement, 9949 EditorDOMPoint(&aRemovingContainerElement, 0u)); 9950 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 9951 NS_WARNING( 9952 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 9953 "LineBreakType::BRElement) failed"); 9954 return insertBRElementResultOrError.propagateErr(); 9955 } 9956 CreateLineBreakResult insertBRElementResult = 9957 insertBRElementResultOrError.unwrap(); 9958 return CreateElementResult(insertBRElementResult->BRElementRef(), 9959 insertBRElementResult.UnwrapCaretPoint()); 9960 } 9961 9962 Result<CreateElementResult, nsresult> 9963 HTMLEditor::EnsureHardLineEndsWithLastChildOf( 9964 Element& aRemovingContainerElement) { 9965 MOZ_ASSERT(IsEditActionDataAvailable()); 9966 9967 nsIContent* firstEditableContent = HTMLEditUtils::GetLastChild( 9968 aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode}); 9969 if (!firstEditableContent) { 9970 return CreateElementResult::NotHandled(); 9971 } 9972 9973 if (HTMLEditUtils::IsBlockElement( 9974 *firstEditableContent, BlockInlineCheck::UseComputedDisplayStyle) || 9975 firstEditableContent->IsHTMLElement(nsGkAtoms::br)) { 9976 return CreateElementResult::NotHandled(); 9977 } 9978 9979 nsIContent* nextEditableContent = HTMLEditUtils::GetPreviousSibling( 9980 aRemovingContainerElement, {WalkTreeOption::IgnoreNonEditableNode}); 9981 if (!nextEditableContent) { 9982 return CreateElementResult::NotHandled(); 9983 } 9984 9985 if (HTMLEditUtils::IsBlockElement( 9986 *nextEditableContent, BlockInlineCheck::UseComputedDisplayStyle) || 9987 nextEditableContent->IsHTMLElement(nsGkAtoms::br)) { 9988 return CreateElementResult::NotHandled(); 9989 } 9990 9991 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 9992 InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement, 9993 EditorDOMPoint::AtEndOf(aRemovingContainerElement)); 9994 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 9995 NS_WARNING( 9996 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 9997 "LineBreakType::BRElement) failed"); 9998 return insertBRElementResultOrError.propagateErr(); 9999 } 10000 CreateLineBreakResult insertBRElementResult = 10001 insertBRElementResultOrError.unwrap(); 10002 return CreateElementResult(insertBRElementResult->BRElementRef(), 10003 insertBRElementResult.UnwrapCaretPoint()); 10004 } 10005 10006 Result<EditorDOMPoint, nsresult> HTMLEditor::SetBlockElementAlign( 10007 Element& aBlockOrHRElement, const nsAString& aAlignType, 10008 EditTarget aEditTarget) { 10009 MOZ_ASSERT(IsEditActionDataAvailable()); 10010 MOZ_ASSERT(HTMLEditUtils::IsBlockElement( 10011 aBlockOrHRElement, BlockInlineCheck::UseHTMLDefaultStyle) || 10012 aBlockOrHRElement.IsHTMLElement(nsGkAtoms::hr)); 10013 MOZ_ASSERT(IsCSSEnabled() || 10014 HTMLEditUtils::IsAlignAttrSupported(aBlockOrHRElement)); 10015 10016 EditorDOMPoint pointToPutCaret; 10017 if (!aBlockOrHRElement.IsHTMLElement(nsGkAtoms::table)) { 10018 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 10019 RemoveAlignFromDescendants(aBlockOrHRElement, aAlignType, aEditTarget); 10020 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 10021 NS_WARNING("HTMLEditor::RemoveAlignFromDescendants() failed"); 10022 return pointToPutCaretOrError; 10023 } 10024 if (pointToPutCaretOrError.inspect().IsSet()) { 10025 pointToPutCaret = pointToPutCaretOrError.unwrap(); 10026 } 10027 } 10028 nsresult rv = SetAttributeOrEquivalent(&aBlockOrHRElement, nsGkAtoms::align, 10029 aAlignType, false); 10030 if (NS_WARN_IF(Destroyed())) { 10031 return Err(NS_ERROR_EDITOR_DESTROYED); 10032 } 10033 if (NS_FAILED(rv)) { 10034 NS_WARNING("HTMLEditor::SetAttributeOrEquivalent(nsGkAtoms::align) failed"); 10035 return Err(rv); 10036 } 10037 return pointToPutCaret; 10038 } 10039 10040 Result<EditorDOMPoint, nsresult> HTMLEditor::ChangeMarginStart( 10041 Element& aElement, ChangeMargin aChangeMargin, 10042 const Element& aEditingHost) { 10043 MOZ_ASSERT(IsEditActionDataAvailable()); 10044 10045 nsStaticAtom& marginProperty = MarginPropertyAtomForIndent(aElement); 10046 if (NS_WARN_IF(Destroyed())) { 10047 return Err(NS_ERROR_EDITOR_DESTROYED); 10048 } 10049 nsAutoString value; 10050 DebugOnly<nsresult> rvIgnored = 10051 CSSEditUtils::GetSpecifiedProperty(aElement, marginProperty, value); 10052 if (NS_WARN_IF(Destroyed())) { 10053 return Err(NS_ERROR_EDITOR_DESTROYED); 10054 } 10055 NS_WARNING_ASSERTION( 10056 NS_SUCCEEDED(rvIgnored), 10057 "CSSEditUtils::GetSpecifiedProperty() failed, but ignored"); 10058 float f; 10059 RefPtr<nsAtom> unit; 10060 CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit)); 10061 if (!f) { 10062 unit = nsGkAtoms::px; 10063 } 10064 int8_t multiplier = aChangeMargin == ChangeMargin::Increase ? 1 : -1; 10065 if (nsGkAtoms::in == unit) { 10066 f += NS_EDITOR_INDENT_INCREMENT_IN * multiplier; 10067 } else if (nsGkAtoms::cm == unit) { 10068 f += NS_EDITOR_INDENT_INCREMENT_CM * multiplier; 10069 } else if (nsGkAtoms::mm == unit) { 10070 f += NS_EDITOR_INDENT_INCREMENT_MM * multiplier; 10071 } else if (nsGkAtoms::pt == unit) { 10072 f += NS_EDITOR_INDENT_INCREMENT_PT * multiplier; 10073 } else if (nsGkAtoms::pc == unit) { 10074 f += NS_EDITOR_INDENT_INCREMENT_PC * multiplier; 10075 } else if (nsGkAtoms::em == unit) { 10076 f += NS_EDITOR_INDENT_INCREMENT_EM * multiplier; 10077 } else if (nsGkAtoms::ex == unit) { 10078 f += NS_EDITOR_INDENT_INCREMENT_EX * multiplier; 10079 } else if (nsGkAtoms::px == unit) { 10080 f += NS_EDITOR_INDENT_INCREMENT_PX * multiplier; 10081 } else if (nsGkAtoms::percentage == unit) { 10082 f += NS_EDITOR_INDENT_INCREMENT_PERCENT * multiplier; 10083 } 10084 10085 if (0 < f) { 10086 if (nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement)) { 10087 nsAutoString newValue; 10088 newValue.AppendFloat(f); 10089 newValue.Append(nsDependentAtomString(unit)); 10090 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must 10091 // be guaranteed by caller because of MOZ_CAN_RUN_SCRIPT method. 10092 // MOZ_KnownLive(merginProperty): It's nsStaticAtom. 10093 nsresult rv = CSSEditUtils::SetCSSPropertyWithTransaction( 10094 *this, MOZ_KnownLive(*styledElement), MOZ_KnownLive(marginProperty), 10095 newValue); 10096 if (rv == NS_ERROR_EDITOR_DESTROYED) { 10097 NS_WARNING( 10098 "CSSEditUtils::SetCSSPropertyWithTransaction() destroyed the " 10099 "editor"); 10100 return Err(NS_ERROR_EDITOR_DESTROYED); 10101 } 10102 NS_WARNING_ASSERTION( 10103 NS_SUCCEEDED(rv), 10104 "CSSEditUtils::SetCSSPropertyWithTransaction() failed, but ignored"); 10105 } 10106 return EditorDOMPoint(); 10107 } 10108 10109 if (nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement)) { 10110 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must 10111 // be guaranteed by caller because of MOZ_CAN_RUN_SCRIPT method. 10112 // MOZ_KnownLive(merginProperty): It's nsStaticAtom. 10113 nsresult rv = CSSEditUtils::RemoveCSSPropertyWithTransaction( 10114 *this, MOZ_KnownLive(*styledElement), MOZ_KnownLive(marginProperty), 10115 value); 10116 if (rv == NS_ERROR_EDITOR_DESTROYED) { 10117 NS_WARNING( 10118 "CSSEditUtils::RemoveCSSPropertyWithTransaction() destroyed the " 10119 "editor"); 10120 return Err(NS_ERROR_EDITOR_DESTROYED); 10121 } 10122 NS_WARNING_ASSERTION( 10123 NS_SUCCEEDED(rv), 10124 "CSSEditUtils::RemoveCSSPropertyWithTransaction() failed, but ignored"); 10125 } 10126 10127 // Remove unnecessary divs 10128 if (!aElement.IsHTMLElement(nsGkAtoms::div) || 10129 HTMLEditUtils::ElementHasAttribute(aElement)) { 10130 return EditorDOMPoint(); 10131 } 10132 // Don't touch editing host nor node which is outside of it. 10133 if (&aElement == &aEditingHost || 10134 !aElement.IsInclusiveDescendantOf(&aEditingHost)) { 10135 return EditorDOMPoint(); 10136 } 10137 10138 Result<EditorDOMPoint, nsresult> unwrapDivElementResult = 10139 RemoveContainerWithTransaction(aElement); 10140 NS_WARNING_ASSERTION(unwrapDivElementResult.isOk(), 10141 "HTMLEditor::RemoveContainerWithTransaction() failed"); 10142 return unwrapDivElementResult; 10143 } 10144 10145 Result<EditActionResult, nsresult> 10146 HTMLEditor::SetSelectionToAbsoluteAsSubAction(const Element& aEditingHost) { 10147 AutoPlaceholderBatch treatAsOneTransaction( 10148 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 10149 IgnoredErrorResult ignoredError; 10150 AutoEditSubActionNotifier startToHandleEditSubAction( 10151 *this, EditSubAction::eSetPositionToAbsolute, nsIEditor::eNext, 10152 ignoredError); 10153 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 10154 return Err(ignoredError.StealNSResult()); 10155 } 10156 NS_WARNING_ASSERTION( 10157 !ignoredError.Failed(), 10158 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 10159 10160 { 10161 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 10162 if (MOZ_UNLIKELY(result.isErr())) { 10163 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 10164 return result; 10165 } 10166 if (result.inspect().Canceled()) { 10167 return result; 10168 } 10169 } 10170 10171 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor(); 10172 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 10173 return Err(NS_ERROR_EDITOR_DESTROYED); 10174 } 10175 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 10176 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " 10177 "failed, but ignored"); 10178 10179 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) { 10180 nsresult rv = EnsureCaretNotAfterInvisibleBRElement(aEditingHost); 10181 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 10182 return Err(NS_ERROR_EDITOR_DESTROYED); 10183 } 10184 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 10185 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " 10186 "failed, but ignored"); 10187 if (NS_SUCCEEDED(rv)) { 10188 nsresult rv = PrepareInlineStylesForCaret(); 10189 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 10190 return Err(NS_ERROR_EDITOR_DESTROYED); 10191 } 10192 NS_WARNING_ASSERTION( 10193 NS_SUCCEEDED(rv), 10194 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); 10195 } 10196 } 10197 10198 auto EnsureCaretInElementIfCollapsedOutside = 10199 [&](Element& aElement) MOZ_CAN_RUN_SCRIPT { 10200 if (!SelectionRef().IsCollapsed() || !SelectionRef().RangeCount()) { 10201 return NS_OK; 10202 } 10203 const auto firstRangeStartPoint = 10204 GetFirstSelectionStartPoint<EditorRawDOMPoint>(); 10205 if (MOZ_UNLIKELY(!firstRangeStartPoint.IsSet())) { 10206 return NS_OK; 10207 } 10208 const Result<EditorRawDOMPoint, nsresult> pointToPutCaretOrError = 10209 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside< 10210 EditorRawDOMPoint>(aElement, firstRangeStartPoint); 10211 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 10212 NS_WARNING( 10213 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() " 10214 "failed, but ignored"); 10215 return NS_OK; 10216 } 10217 if (!pointToPutCaretOrError.inspect().IsSet()) { 10218 return NS_OK; 10219 } 10220 nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect()); 10221 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 10222 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 10223 return NS_ERROR_EDITOR_DESTROYED; 10224 } 10225 NS_WARNING_ASSERTION( 10226 NS_SUCCEEDED(rv), 10227 "EditorBase::CollapseSelectionTo() failed, but ignored"); 10228 return NS_OK; 10229 }; 10230 10231 const RefPtr<Element> focusElement = GetSelectionContainerElement(); 10232 if (focusElement && HTMLEditUtils::IsImageElement(*focusElement)) { 10233 nsresult rv = EnsureCaretInElementIfCollapsedOutside(*focusElement); 10234 if (NS_FAILED(rv)) { 10235 NS_WARNING("EnsureCaretInElementIfCollapsedOutside() failed"); 10236 return Err(rv); 10237 } 10238 return EditActionResult::HandledResult(); 10239 } 10240 10241 // XXX Why do we do this only when there is only one selection range? 10242 if (!SelectionRef().IsCollapsed() && SelectionRef().RangeCount() == 1u) { 10243 Result<EditorRawDOMRange, nsresult> extendedRange = 10244 GetRangeExtendedToHardLineEdgesForBlockEditAction( 10245 SelectionRef().GetRangeAt(0u), aEditingHost); 10246 if (MOZ_UNLIKELY(extendedRange.isErr())) { 10247 NS_WARNING( 10248 "HTMLEditor::GetRangeExtendedToHardLineEdgesForBlockEditAction() " 10249 "failed"); 10250 return extendedRange.propagateErr(); 10251 } 10252 // Note that end point may be prior to start point. So, we 10253 // cannot use Selection::SetStartAndEndInLimit() here. 10254 IgnoredErrorResult error; 10255 SelectionRef().SetBaseAndExtentInLimiter( 10256 extendedRange.inspect().StartRef().ToRawRangeBoundary(), 10257 extendedRange.inspect().EndRef().ToRawRangeBoundary(), error); 10258 if (NS_WARN_IF(Destroyed())) { 10259 return Err(NS_ERROR_EDITOR_DESTROYED); 10260 } 10261 if (MOZ_UNLIKELY(error.Failed())) { 10262 NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed"); 10263 return Err(error.StealNSResult()); 10264 } 10265 } 10266 10267 RefPtr<Element> divElement; 10268 rv = MoveSelectedContentsToDivElementToMakeItAbsolutePosition( 10269 address_of(divElement), aEditingHost); 10270 // MoveSelectedContentsToDivElementToMakeItAbsolutePosition() may restore 10271 // selection with AutoSelectionRestorer. Therefore, the editor might have 10272 // already been destroyed now. 10273 if (NS_WARN_IF(Destroyed())) { 10274 return Err(NS_ERROR_EDITOR_DESTROYED); 10275 } 10276 if (NS_FAILED(rv)) { 10277 NS_WARNING( 10278 "HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition()" 10279 " failed"); 10280 return Err(rv); 10281 } 10282 10283 if (IsSelectionRangeContainerNotContent()) { 10284 NS_WARNING("Mutation event listener might have changed the selection"); 10285 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 10286 } 10287 10288 if (SelectionRef().IsCollapsed()) { 10289 const auto caretPosition = 10290 EditorBase::GetFirstSelectionStartPoint<EditorDOMPoint>(); 10291 Result<CreateLineBreakResult, nsresult> 10292 insertPaddingBRElementResultOrError = 10293 InsertPaddingBRElementIfInEmptyBlock(caretPosition, eNoStrip); 10294 if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { 10295 NS_WARNING( 10296 "HTMLEditor::InsertPaddingBRElementIfInEmptyBlock(eNoStrip) failed"); 10297 return insertPaddingBRElementResultOrError.propagateErr(); 10298 } 10299 nsresult rv = 10300 insertPaddingBRElementResultOrError.unwrap().SuggestCaretPointTo( 10301 *this, {SuggestCaret::OnlyIfHasSuggestion, 10302 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 10303 SuggestCaret::AndIgnoreTrivialError}); 10304 if (NS_FAILED(rv)) { 10305 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 10306 return Err(rv); 10307 } 10308 NS_WARNING_ASSERTION( 10309 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 10310 "CaretPoint::SuggestCaretPointTo() failed, but ignored"); 10311 } 10312 10313 if (!divElement) { 10314 return EditActionResult::HandledResult(); 10315 } 10316 10317 rv = SetPositionToAbsoluteOrStatic(*divElement, true); 10318 if (NS_WARN_IF(Destroyed())) { 10319 return Err(NS_ERROR_EDITOR_DESTROYED); 10320 } 10321 if (NS_FAILED(rv)) { 10322 NS_WARNING("HTMLEditor::SetPositionToAbsoluteOrStatic() failed"); 10323 return Err(rv); 10324 } 10325 10326 rv = EnsureCaretInElementIfCollapsedOutside(*divElement); 10327 if (NS_FAILED(rv)) { 10328 NS_WARNING("EnsureCaretInElementIfCollapsedOutside() failed"); 10329 return Err(rv); 10330 } 10331 return EditActionResult::HandledResult(); 10332 } 10333 10334 nsresult HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition( 10335 RefPtr<Element>* aTargetElement, const Element& aEditingHost) { 10336 MOZ_ASSERT(IsEditActionDataAvailable()); 10337 MOZ_ASSERT(aTargetElement); 10338 10339 AutoSelectionRestorer restoreSelectionLater(this); 10340 10341 EditorDOMPoint pointToPutCaret; 10342 10343 // Use these ranges to construct a list of nodes to act on. 10344 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents; 10345 { 10346 AutoClonedSelectionRangeArray extendedSelectionRanges(SelectionRef()); 10347 extendedSelectionRanges.ExtendRangesToWrapLines( 10348 EditSubAction::eSetPositionToAbsolute, 10349 BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost); 10350 Result<EditorDOMPoint, nsresult> splitResult = 10351 extendedSelectionRanges 10352 .SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries( 10353 *this, BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost); 10354 if (MOZ_UNLIKELY(splitResult.isErr())) { 10355 NS_WARNING( 10356 "AutoClonedRangeArray::" 10357 "SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries() " 10358 "failed"); 10359 return splitResult.unwrapErr(); 10360 } 10361 if (splitResult.inspect().IsSet()) { 10362 pointToPutCaret = splitResult.unwrap(); 10363 } 10364 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes( 10365 *this, arrayOfContents, EditSubAction::eSetPositionToAbsolute, 10366 AutoClonedRangeArray::CollectNonEditableNodes::Yes); 10367 if (NS_FAILED(rv)) { 10368 NS_WARNING( 10369 "AutoClonedRangeArray::CollectEditTargetNodes(EditSubAction::" 10370 "eSetPositionToAbsolute, CollectNonEditableNodes::Yes) failed"); 10371 return rv; 10372 } 10373 } 10374 10375 Result<EditorDOMPoint, nsresult> splitAtBRElementsResult = 10376 MaybeSplitElementsAtEveryBRElement(arrayOfContents, 10377 EditSubAction::eSetPositionToAbsolute); 10378 if (MOZ_UNLIKELY(splitAtBRElementsResult.isErr())) { 10379 NS_WARNING( 10380 "HTMLEditor::MaybeSplitElementsAtEveryBRElement(EditSubAction::" 10381 "eSetPositionToAbsolute) failed"); 10382 return splitAtBRElementsResult.inspectErr(); 10383 } 10384 if (splitAtBRElementsResult.inspect().IsSet()) { 10385 pointToPutCaret = splitAtBRElementsResult.unwrap(); 10386 } 10387 10388 if (AllowsTransactionsToChangeSelection() && 10389 pointToPutCaret.IsSetAndValid()) { 10390 nsresult rv = CollapseSelectionTo(pointToPutCaret); 10391 if (NS_FAILED(rv)) { 10392 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 10393 return rv; 10394 } 10395 } 10396 10397 // If there is no visible and editable nodes in the edit targets, make an 10398 // empty block. 10399 // XXX Isn't this odd if there are only non-editable visible nodes? 10400 if (HTMLEditUtils::IsEmptyOneHardLine( 10401 arrayOfContents, BlockInlineCheck::UseHTMLDefaultStyle)) { 10402 const auto atCaret = 10403 EditorBase::GetFirstSelectionStartPoint<EditorDOMPoint>(); 10404 if (NS_WARN_IF(!atCaret.IsSet())) { 10405 return NS_ERROR_FAILURE; 10406 } 10407 10408 // Make sure we can put a block here. 10409 Result<CreateElementResult, nsresult> createNewDivElementResult = 10410 InsertElementWithSplittingAncestorsWithTransaction( 10411 *nsGkAtoms::div, atCaret, BRElementNextToSplitPoint::Keep, 10412 aEditingHost); 10413 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) { 10414 NS_WARNING( 10415 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(" 10416 "nsGkAtoms::div) failed"); 10417 return createNewDivElementResult.unwrapErr(); 10418 } 10419 CreateElementResult unwrappedCreateNewDivElementResult = 10420 createNewDivElementResult.unwrap(); 10421 // We'll update selection after deleting the content nodes and nobody 10422 // refers selection until then. Therefore, we don't need to update 10423 // selection here. 10424 unwrappedCreateNewDivElementResult.IgnoreCaretPointSuggestion(); 10425 RefPtr<Element> newDivElement = 10426 unwrappedCreateNewDivElementResult.UnwrapNewNode(); 10427 MOZ_ASSERT(newDivElement); 10428 // Delete anything that was in the list of nodes 10429 // XXX We don't need to remove items from the array. 10430 for (OwningNonNull<nsIContent>& curNode : arrayOfContents) { 10431 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive. 10432 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*curNode)); 10433 if (NS_FAILED(rv)) { 10434 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 10435 return rv; 10436 } 10437 } 10438 // Don't restore the selection 10439 restoreSelectionLater.Abort(); 10440 nsresult rv = CollapseSelectionToStartOf(*newDivElement); 10441 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 10442 "EditorBase::CollapseSelectionToStartOf() failed"); 10443 *aTargetElement = std::move(newDivElement); 10444 return rv; 10445 } 10446 10447 // `<div>` element to be positioned absolutely. This may have already 10448 // existed or newly created by this method. 10449 RefPtr<Element> targetDivElement; 10450 // Newly created list element for moving selected list item elements into 10451 // targetDivElement. I.e., this is created in the `<div>` element. 10452 RefPtr<Element> createdListElement; 10453 // If we handle a parent list item element, this is set to it. In such case, 10454 // we should handle its children again. 10455 RefPtr<Element> handledListItemElement; 10456 for (size_t i = 0; i < arrayOfContents.Length(); i++) { 10457 const OwningNonNull<nsIContent>& content = arrayOfContents[i]; 10458 10459 // Here's where we actually figure out what to do. 10460 EditorDOMPoint atContent(content); 10461 if (NS_WARN_IF(!atContent.IsInContentNode())) { 10462 return NS_ERROR_FAILURE; // XXX not continue?? 10463 } 10464 10465 // Ignore all non-editable nodes. Leave them be. 10466 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) { 10467 continue; 10468 } 10469 10470 // If current node is a child of a list element, we need another list 10471 // element in absolute-positioned `<div>` element to avoid non-selected 10472 // list items are moved into the `<div>` element. 10473 if (HTMLEditUtils::IsListElement(*atContent.ContainerAs<nsIContent>())) { 10474 // If we cannot move current node to created list element, we need a 10475 // list element in the target `<div>` element for the destination. 10476 // Therefore, duplicate same list element into the target `<div>` 10477 // element. 10478 nsIContent* previousEditableContent = 10479 createdListElement 10480 ? HTMLEditUtils::GetPreviousSibling( 10481 content, {WalkTreeOption::IgnoreNonEditableNode}) 10482 : nullptr; 10483 if (!createdListElement || 10484 (previousEditableContent && 10485 previousEditableContent != createdListElement)) { 10486 nsAtom* ULOrOLOrDLTagName = 10487 atContent.GetContainer()->NodeInfo()->NameAtom(); 10488 if (targetDivElement) { 10489 // XXX Do we need to split the container? Since we'll append new 10490 // element at end of the <div> element. 10491 Result<SplitNodeResult, nsresult> splitNodeResult = 10492 MaybeSplitAncestorsForInsertWithTransaction( 10493 MOZ_KnownLive(*ULOrOLOrDLTagName), atContent, aEditingHost); 10494 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 10495 NS_WARNING( 10496 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() " 10497 "failed"); 10498 return splitNodeResult.unwrapErr(); 10499 } 10500 // We'll update selection after creating a list element below. 10501 // Therefore, we don't need to touch selection here. 10502 splitNodeResult.inspect().IgnoreCaretPointSuggestion(); 10503 } else { 10504 // If we've not had a target <div> element yet, let's insert a <div> 10505 // element with splitting the ancestors. 10506 Result<CreateElementResult, nsresult> createNewDivElementResult = 10507 InsertElementWithSplittingAncestorsWithTransaction( 10508 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep, 10509 aEditingHost); 10510 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) { 10511 NS_WARNING( 10512 "HTMLEditor::" 10513 "InsertElementWithSplittingAncestorsWithTransaction(nsGkAtoms::" 10514 "div) failed"); 10515 return createNewDivElementResult.unwrapErr(); 10516 } 10517 // We'll update selection after creating a list element below. 10518 // Therefor, we don't need to touch selection here. 10519 createNewDivElementResult.inspect().IgnoreCaretPointSuggestion(); 10520 MOZ_ASSERT(createNewDivElementResult.inspect().GetNewNode()); 10521 targetDivElement = createNewDivElementResult.unwrap().UnwrapNewNode(); 10522 } 10523 Result<CreateElementResult, nsresult> createNewListElementResult = 10524 CreateAndInsertElement(WithTransaction::Yes, 10525 MOZ_KnownLive(*ULOrOLOrDLTagName), 10526 EditorDOMPoint::AtEndOf(targetDivElement)); 10527 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) { 10528 NS_WARNING( 10529 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) " 10530 "failed"); 10531 return createNewListElementResult.unwrapErr(); 10532 } 10533 nsresult rv = createNewListElementResult.inspect().SuggestCaretPointTo( 10534 *this, {SuggestCaret::OnlyIfHasSuggestion, 10535 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 10536 SuggestCaret::AndIgnoreTrivialError}); 10537 if (NS_FAILED(rv)) { 10538 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed"); 10539 return Err(rv); 10540 } 10541 NS_WARNING_ASSERTION( 10542 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 10543 "CreateElementResult::SuggestCaretPointTo() failed, but ignored"); 10544 createdListElement = 10545 createNewListElementResult.unwrap().UnwrapNewNode(); 10546 MOZ_ASSERT(createdListElement); 10547 } 10548 // Move current node (maybe, assumed as a list item element) into the 10549 // new list element in the target `<div>` element to be positioned 10550 // absolutely. 10551 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive. 10552 Result<MoveNodeResult, nsresult> moveNodeResult = 10553 MoveNodeToEndWithTransaction(MOZ_KnownLive(content), 10554 *createdListElement); 10555 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 10556 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); 10557 return moveNodeResult.propagateErr(); 10558 } 10559 nsresult rv = moveNodeResult.inspect().SuggestCaretPointTo( 10560 *this, {SuggestCaret::OnlyIfHasSuggestion, 10561 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 10562 SuggestCaret::AndIgnoreTrivialError}); 10563 if (NS_FAILED(rv)) { 10564 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed"); 10565 return Err(rv); 10566 } 10567 NS_WARNING_ASSERTION( 10568 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 10569 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored"); 10570 continue; 10571 } 10572 10573 // If contents in a list item element is selected, we should move current 10574 // node into the target `<div>` element with the list item element itself 10575 // because we want to keep indent level of the contents. 10576 if (RefPtr<Element> listItemElement = 10577 HTMLEditUtils::GetClosestInclusiveAncestorListItemElement( 10578 content, &aEditingHost)) { 10579 if (handledListItemElement == listItemElement) { 10580 // Current node has already been moved into the `<div>` element. 10581 continue; 10582 } 10583 // If we cannot move the list item element into created list element, 10584 // we need another list element in the target `<div>` element. 10585 nsIContent* previousEditableContent = 10586 createdListElement 10587 ? HTMLEditUtils::GetPreviousSibling( 10588 *listItemElement, {WalkTreeOption::IgnoreNonEditableNode}) 10589 : nullptr; 10590 if (!createdListElement || 10591 (previousEditableContent && 10592 previousEditableContent != createdListElement)) { 10593 EditorDOMPoint atListItem(listItemElement); 10594 if (NS_WARN_IF(!atListItem.IsSet())) { 10595 return NS_ERROR_FAILURE; 10596 } 10597 // XXX If content is the listItemElement and not in a list element, 10598 // we duplicate wrong element into the target `<div>` element. 10599 nsAtom* containerName = 10600 atListItem.GetContainer()->NodeInfo()->NameAtom(); 10601 if (targetDivElement) { 10602 // XXX Do we need to split the container? Since we'll append new 10603 // element at end of the <div> element. 10604 Result<SplitNodeResult, nsresult> splitNodeResult = 10605 MaybeSplitAncestorsForInsertWithTransaction( 10606 MOZ_KnownLive(*containerName), atListItem, aEditingHost); 10607 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 10608 NS_WARNING( 10609 "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() " 10610 "failed"); 10611 return splitNodeResult.unwrapErr(); 10612 } 10613 // We'll update selection after creating a list element below. 10614 // Therefore, we don't need to touch selection here. 10615 splitNodeResult.inspect().IgnoreCaretPointSuggestion(); 10616 } else { 10617 // If we've not had a target <div> element yet, let's insert a <div> 10618 // element with splitting the ancestors. 10619 Result<CreateElementResult, nsresult> createNewDivElementResult = 10620 InsertElementWithSplittingAncestorsWithTransaction( 10621 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep, 10622 aEditingHost); 10623 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) { 10624 NS_WARNING( 10625 "HTMLEditor::" 10626 "InsertElementWithSplittingAncestorsWithTransaction(" 10627 "nsGkAtoms::div) failed"); 10628 return createNewDivElementResult.unwrapErr(); 10629 } 10630 // We'll update selection after creating a list element below. 10631 // Therefore, we don't need to touch selection here. 10632 createNewDivElementResult.inspect().IgnoreCaretPointSuggestion(); 10633 MOZ_ASSERT(createNewDivElementResult.inspect().GetNewNode()); 10634 targetDivElement = createNewDivElementResult.unwrap().UnwrapNewNode(); 10635 } 10636 // XXX So, createdListElement may be set to a non-list element. 10637 Result<CreateElementResult, nsresult> createNewListElementResult = 10638 CreateAndInsertElement(WithTransaction::Yes, 10639 MOZ_KnownLive(*containerName), 10640 EditorDOMPoint::AtEndOf(targetDivElement)); 10641 if (MOZ_UNLIKELY(createNewListElementResult.isErr())) { 10642 NS_WARNING( 10643 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) " 10644 "failed"); 10645 return createNewListElementResult.unwrapErr(); 10646 } 10647 nsresult rv = createNewListElementResult.inspect().SuggestCaretPointTo( 10648 *this, {SuggestCaret::OnlyIfHasSuggestion, 10649 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 10650 SuggestCaret::AndIgnoreTrivialError}); 10651 if (NS_FAILED(rv)) { 10652 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed"); 10653 return Err(rv); 10654 } 10655 NS_WARNING_ASSERTION( 10656 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 10657 "CreateElementResult::SuggestCaretPointTo() failed, but ignored"); 10658 createdListElement = 10659 createNewListElementResult.unwrap().UnwrapNewNode(); 10660 MOZ_ASSERT(createdListElement); 10661 } 10662 // Move current list item element into the createdListElement (could be 10663 // non-list element due to the above bug) in a candidate `<div>` element 10664 // to be positioned absolutely. 10665 Result<MoveNodeResult, nsresult> moveListItemElementResult = 10666 MoveNodeToEndWithTransaction(*listItemElement, *createdListElement); 10667 if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) { 10668 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); 10669 return moveListItemElementResult.unwrapErr(); 10670 } 10671 nsresult rv = moveListItemElementResult.inspect().SuggestCaretPointTo( 10672 *this, {SuggestCaret::OnlyIfHasSuggestion, 10673 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 10674 SuggestCaret::AndIgnoreTrivialError}); 10675 if (NS_FAILED(rv)) { 10676 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed"); 10677 return Err(rv); 10678 } 10679 NS_WARNING_ASSERTION( 10680 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 10681 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored"); 10682 handledListItemElement = std::move(listItemElement); 10683 continue; 10684 } 10685 10686 if (!targetDivElement) { 10687 // If we meet a `<div>` element, use it as the absolute-position 10688 // container. 10689 // XXX This looks odd. If there are 2 or more `<div>` elements are 10690 // selected, first found `<div>` element will have all other 10691 // selected nodes. 10692 if (content->IsHTMLElement(nsGkAtoms::div)) { 10693 targetDivElement = content->AsElement(); 10694 MOZ_ASSERT(!createdListElement); 10695 MOZ_ASSERT(!handledListItemElement); 10696 continue; 10697 } 10698 // Otherwise, create new `<div>` element to be positioned absolutely 10699 // and to contain all selected nodes. 10700 Result<CreateElementResult, nsresult> createNewDivElementResult = 10701 InsertElementWithSplittingAncestorsWithTransaction( 10702 *nsGkAtoms::div, atContent, BRElementNextToSplitPoint::Keep, 10703 aEditingHost); 10704 if (MOZ_UNLIKELY(createNewDivElementResult.isErr())) { 10705 NS_WARNING( 10706 "HTMLEditor::InsertElementWithSplittingAncestorsWithTransaction(" 10707 "nsGkAtoms::div) failed"); 10708 return createNewDivElementResult.unwrapErr(); 10709 } 10710 nsresult rv = createNewDivElementResult.inspect().SuggestCaretPointTo( 10711 *this, {SuggestCaret::OnlyIfHasSuggestion, 10712 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 10713 if (NS_FAILED(rv)) { 10714 NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed"); 10715 return rv; 10716 } 10717 MOZ_ASSERT(createNewDivElementResult.inspect().GetNewNode()); 10718 targetDivElement = createNewDivElementResult.unwrap().UnwrapNewNode(); 10719 } 10720 10721 const OwningNonNull<nsIContent> lastContent = [&]() { 10722 nsIContent* lastContent = content; 10723 for (; i + 1 < arrayOfContents.Length(); i++) { 10724 const OwningNonNull<nsIContent>& nextContent = arrayOfContents[i + 1]; 10725 if (lastContent->GetNextSibling() == nextContent || 10726 HTMLEditUtils::IsListElement(*nextContent) || 10727 HTMLEditUtils::IsListItemElement(*nextContent) || 10728 !EditorUtils::IsEditableContent(content, EditorType::HTML)) { 10729 break; 10730 } 10731 lastContent = nextContent; 10732 } 10733 return OwningNonNull<nsIContent>(*lastContent); 10734 }(); 10735 10736 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive. 10737 Result<MoveNodeResult, nsresult> moveNodeResult = 10738 MoveSiblingsToEndWithTransaction(MOZ_KnownLive(content), lastContent, 10739 *targetDivElement); 10740 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 10741 NS_WARNING("HTMLEditor::MoveSiblingsToEndWithTransaction() failed"); 10742 return moveNodeResult.unwrapErr(); 10743 } 10744 nsresult rv = moveNodeResult.inspect().SuggestCaretPointTo( 10745 *this, {SuggestCaret::OnlyIfHasSuggestion, 10746 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 10747 SuggestCaret::AndIgnoreTrivialError}); 10748 if (NS_FAILED(rv)) { 10749 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed"); 10750 return rv; 10751 } 10752 NS_WARNING_ASSERTION( 10753 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 10754 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored"); 10755 // Forget createdListElement, if any 10756 createdListElement = nullptr; 10757 } 10758 *aTargetElement = std::move(targetDivElement); 10759 return NS_OK; 10760 } 10761 10762 Result<EditActionResult, nsresult> 10763 HTMLEditor::SetSelectionToStaticAsSubAction() { 10764 MOZ_ASSERT(IsEditActionDataAvailable()); 10765 10766 AutoPlaceholderBatch treatAsOneTransaction( 10767 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 10768 IgnoredErrorResult ignoredError; 10769 AutoEditSubActionNotifier startToHandleEditSubAction( 10770 *this, EditSubAction::eSetPositionToStatic, nsIEditor::eNext, 10771 ignoredError); 10772 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 10773 return Err(ignoredError.StealNSResult()); 10774 } 10775 NS_WARNING_ASSERTION( 10776 !ignoredError.Failed(), 10777 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 10778 10779 { 10780 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 10781 if (MOZ_UNLIKELY(result.isErr())) { 10782 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 10783 return result; 10784 } 10785 if (result.inspect().Canceled()) { 10786 return result; 10787 } 10788 } 10789 10790 const RefPtr<Element> editingHost = 10791 ComputeEditingHost(LimitInBodyElement::No); 10792 if (NS_WARN_IF(!editingHost)) { 10793 return Err(NS_ERROR_FAILURE); 10794 } 10795 10796 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor(); 10797 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 10798 return Err(NS_ERROR_EDITOR_DESTROYED); 10799 } 10800 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 10801 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " 10802 "failed, but ignored"); 10803 10804 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) { 10805 nsresult rv = EnsureCaretNotAfterInvisibleBRElement(*editingHost); 10806 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 10807 return Err(NS_ERROR_EDITOR_DESTROYED); 10808 } 10809 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 10810 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " 10811 "failed, but ignored"); 10812 if (NS_SUCCEEDED(rv)) { 10813 nsresult rv = PrepareInlineStylesForCaret(); 10814 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 10815 return Err(NS_ERROR_EDITOR_DESTROYED); 10816 } 10817 NS_WARNING_ASSERTION( 10818 NS_SUCCEEDED(rv), 10819 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); 10820 } 10821 } 10822 10823 RefPtr<Element> element = GetAbsolutelyPositionedSelectionContainer(); 10824 if (!element) { 10825 if (NS_WARN_IF(Destroyed())) { 10826 return Err(NS_ERROR_EDITOR_DESTROYED); 10827 } 10828 NS_WARNING( 10829 "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() returned " 10830 "nullptr"); 10831 return Err(NS_ERROR_FAILURE); 10832 } 10833 10834 { 10835 AutoSelectionRestorer restoreSelectionLater(this); 10836 10837 nsresult rv = SetPositionToAbsoluteOrStatic(*element, false); 10838 if (NS_WARN_IF(Destroyed())) { 10839 return Err(NS_ERROR_EDITOR_DESTROYED); 10840 } 10841 if (NS_FAILED(rv)) { 10842 NS_WARNING("HTMLEditor::SetPositionToAbsoluteOrStatic() failed"); 10843 return Err(rv); 10844 } 10845 } 10846 10847 // Restoring Selection might cause destroying the HTML editor. 10848 if (MOZ_UNLIKELY(Destroyed())) { 10849 NS_WARNING("Destroying AutoSelectionRestorer caused destroying the editor"); 10850 return Err(NS_ERROR_EDITOR_DESTROYED); 10851 } 10852 return EditActionResult::HandledResult(); 10853 } 10854 10855 Result<EditActionResult, nsresult> HTMLEditor::AddZIndexAsSubAction( 10856 int32_t aChange) { 10857 MOZ_ASSERT(IsEditActionDataAvailable()); 10858 10859 AutoPlaceholderBatch treatAsOneTransaction( 10860 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 10861 IgnoredErrorResult ignoredError; 10862 AutoEditSubActionNotifier startToHandleEditSubAction( 10863 *this, 10864 aChange < 0 ? EditSubAction::eDecreaseZIndex 10865 : EditSubAction::eIncreaseZIndex, 10866 nsIEditor::eNext, ignoredError); 10867 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 10868 return Err(ignoredError.StealNSResult()); 10869 } 10870 NS_WARNING_ASSERTION( 10871 !ignoredError.Failed(), 10872 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 10873 10874 { 10875 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 10876 if (MOZ_UNLIKELY(result.isErr())) { 10877 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 10878 return result; 10879 } 10880 if (result.inspect().Canceled()) { 10881 return result; 10882 } 10883 } 10884 10885 const RefPtr<Element> editingHost = 10886 ComputeEditingHost(LimitInBodyElement::No); 10887 if (NS_WARN_IF(!editingHost)) { 10888 return Err(NS_ERROR_FAILURE); 10889 } 10890 10891 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor(); 10892 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 10893 return Err(NS_ERROR_EDITOR_DESTROYED); 10894 } 10895 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 10896 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " 10897 "failed, but ignored"); 10898 10899 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) { 10900 nsresult rv = EnsureCaretNotAfterInvisibleBRElement(*editingHost); 10901 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 10902 return Err(NS_ERROR_EDITOR_DESTROYED); 10903 } 10904 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 10905 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " 10906 "failed, but ignored"); 10907 if (NS_SUCCEEDED(rv)) { 10908 nsresult rv = PrepareInlineStylesForCaret(); 10909 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 10910 return Err(NS_ERROR_EDITOR_DESTROYED); 10911 } 10912 NS_WARNING_ASSERTION( 10913 NS_SUCCEEDED(rv), 10914 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); 10915 } 10916 } 10917 10918 RefPtr<Element> absolutelyPositionedElement = 10919 GetAbsolutelyPositionedSelectionContainer(); 10920 if (!absolutelyPositionedElement) { 10921 if (NS_WARN_IF(Destroyed())) { 10922 return Err(NS_ERROR_EDITOR_DESTROYED); 10923 } 10924 NS_WARNING( 10925 "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() returned " 10926 "nullptr"); 10927 return Err(NS_ERROR_FAILURE); 10928 } 10929 10930 nsStyledElement* absolutelyPositionedStyledElement = 10931 nsStyledElement::FromNode(absolutelyPositionedElement); 10932 if (NS_WARN_IF(!absolutelyPositionedStyledElement)) { 10933 return Err(NS_ERROR_FAILURE); 10934 } 10935 10936 { 10937 AutoSelectionRestorer restoreSelectionLater(this); 10938 10939 // MOZ_KnownLive(*absolutelyPositionedStyledElement): It's 10940 // absolutelyPositionedElement whose type is RefPtr. 10941 Result<int32_t, nsresult> result = AddZIndexWithTransaction( 10942 MOZ_KnownLive(*absolutelyPositionedStyledElement), aChange); 10943 if (MOZ_UNLIKELY(result.isErr())) { 10944 NS_WARNING("HTMLEditor::AddZIndexWithTransaction() failed"); 10945 return result.propagateErr(); 10946 } 10947 } 10948 10949 // Restoring Selection might cause destroying the HTML editor. 10950 if (MOZ_UNLIKELY(Destroyed())) { 10951 NS_WARNING("Destroying AutoSelectionRestorer caused destroying the editor"); 10952 return Err(NS_ERROR_EDITOR_DESTROYED); 10953 } 10954 10955 return EditActionResult::HandledResult(); 10956 } 10957 10958 } // namespace mozilla