HTMLEditorInsertParagraphHandler.cpp (94903B)
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 "CSSEditUtils.h" 16 #include "EditAction.h" 17 #include "EditorDOMPoint.h" 18 #include "EditorLineBreak.h" 19 #include "EditorUtils.h" 20 #include "HTMLEditHelpers.h" 21 #include "HTMLEditUtils.h" 22 #include "PendingStyles.h" // for SpecifiedStyle 23 #include "WhiteSpaceVisibilityKeeper.h" 24 #include "WSRunScanner.h" 25 26 #include "ErrorList.h" 27 #include "mozilla/Assertions.h" 28 #include "mozilla/Attributes.h" 29 #include "mozilla/ContentIterator.h" 30 #include "mozilla/EditorForwards.h" 31 #include "mozilla/Maybe.h" 32 #include "mozilla/PresShell.h" 33 #include "mozilla/dom/Element.h" 34 #include "mozilla/dom/HTMLBRElement.h" 35 #include "mozilla/dom/Selection.h" 36 #include "nsAtom.h" 37 #include "nsContentUtils.h" 38 #include "nsDebug.h" 39 #include "nsError.h" 40 #include "nsGkAtoms.h" 41 #include "nsIContent.h" 42 #include "nsIFrame.h" 43 #include "nsINode.h" 44 #include "nsTArray.h" 45 #include "nsTextNode.h" 46 47 namespace mozilla { 48 49 using namespace dom; 50 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; 51 using EmptyCheckOptions = HTMLEditUtils::EmptyCheckOptions; 52 using LeafNodeType = HTMLEditUtils::LeafNodeType; 53 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; 54 using WalkTreeOption = HTMLEditUtils::WalkTreeOption; 55 56 Result<EditActionResult, nsresult> 57 HTMLEditor::InsertParagraphSeparatorAsSubAction(const Element& aEditingHost) { 58 if (NS_WARN_IF(!mInitSucceeded)) { 59 return Err(NS_ERROR_NOT_INITIALIZED); 60 } 61 62 { 63 Result<EditActionResult, nsresult> result = 64 CanHandleHTMLEditSubAction(CheckSelectionInReplacedElement::No); 65 if (MOZ_UNLIKELY(result.isErr())) { 66 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 67 return result; 68 } 69 if (result.inspect().Canceled()) { 70 return result; 71 } 72 } 73 74 // XXX This may be called by execCommand() with "insertParagraph". 75 // In such case, naming the transaction "TypingTxnName" is odd. 76 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName, 77 ScrollSelectionIntoView::Yes, 78 __FUNCTION__); 79 80 IgnoredErrorResult ignoredError; 81 AutoEditSubActionNotifier startToHandleEditSubAction( 82 *this, EditSubAction::eInsertParagraphSeparator, nsIEditor::eNext, 83 ignoredError); 84 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 85 return Err(ignoredError.StealNSResult()); 86 } 87 NS_WARNING_ASSERTION( 88 !ignoredError.Failed(), 89 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 90 91 UndefineCaretBidiLevel(); 92 93 // If the selection isn't collapsed, delete it. 94 if (!SelectionRef().IsCollapsed()) { 95 nsresult rv = 96 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip); 97 if (NS_FAILED(rv)) { 98 NS_WARNING( 99 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed"); 100 return Err(rv); 101 } 102 } 103 104 AutoInsertParagraphHandler insertParagraphHandler(*this, aEditingHost); 105 Result<EditActionResult, nsresult> insertParagraphResult = 106 insertParagraphHandler.Run(); 107 NS_WARNING_ASSERTION(insertParagraphResult.isOk(), 108 "AutoInsertParagraphHandler::Run() failed"); 109 return insertParagraphResult; 110 } 111 112 Result<EditActionResult, nsresult> 113 HTMLEditor::AutoInsertParagraphHandler::Run() { 114 MOZ_ASSERT(mHTMLEditor.IsEditActionDataAvailable()); 115 MOZ_ASSERT(mHTMLEditor.IsTopLevelEditSubActionDataAvailable()); 116 117 nsresult rv = mHTMLEditor.EnsureNoPaddingBRElementForEmptyEditor(); 118 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 119 return Err(NS_ERROR_EDITOR_DESTROYED); 120 } 121 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 122 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " 123 "failed, but ignored"); 124 125 if (NS_SUCCEEDED(rv) && mHTMLEditor.SelectionRef().IsCollapsed()) { 126 nsresult rv = 127 mHTMLEditor.EnsureCaretNotAfterInvisibleBRElement(mEditingHost); 128 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 129 return Err(NS_ERROR_EDITOR_DESTROYED); 130 } 131 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 132 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " 133 "failed, but ignored"); 134 if (NS_SUCCEEDED(rv)) { 135 nsresult rv = mHTMLEditor.PrepareInlineStylesForCaret(); 136 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 137 return Err(NS_ERROR_EDITOR_DESTROYED); 138 } 139 NS_WARNING_ASSERTION( 140 NS_SUCCEEDED(rv), 141 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); 142 } 143 } 144 145 AutoClonedSelectionRangeArray selectionRanges(mHTMLEditor.SelectionRef()); 146 selectionRanges.EnsureOnlyEditableRanges(mEditingHost); 147 148 auto pointToInsert = 149 selectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>(); 150 if (NS_WARN_IF(!pointToInsert.IsInContentNode())) { 151 return Err(NS_ERROR_FAILURE); 152 } 153 // If the element can have a <br> element (it means that the element or its 154 // container must be able to have <div> or <p> too), we can handle 155 // insertParagraph at the point. 156 pointToInsert = HTMLEditUtils::GetPossiblePointToInsert( 157 pointToInsert, *nsGkAtoms::br, mEditingHost); 158 if (NS_WARN_IF(!pointToInsert.IsSet())) { 159 return Err(NS_ERROR_FAILURE); 160 } 161 MOZ_ASSERT(pointToInsert.IsInContentNode()); 162 163 if (mHTMLEditor.IsMailEditor()) { 164 if (const RefPtr<Element> mailCiteElement = 165 mHTMLEditor.GetMostDistantAncestorMailCiteElement( 166 *pointToInsert.ContainerAs<nsIContent>())) { 167 // Split any mailcites in the way. Should we abort this if we encounter 168 // table cell boundaries? 169 Result<CaretPoint, nsresult> caretPointOrError = 170 HandleInMailCiteElement(*mailCiteElement, pointToInsert); 171 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 172 NS_WARNING( 173 "AutoInsertParagraphHandler::HandleInMailCiteElement() failed"); 174 return caretPointOrError.propagateErr(); 175 } 176 CaretPoint caretPoint = caretPointOrError.unwrap(); 177 MOZ_ASSERT(caretPoint.HasCaretPointSuggestion()); 178 MOZ_ASSERT(caretPoint.CaretPointRef().GetInterlinePosition() == 179 InterlinePosition::StartOfNextLine); 180 MOZ_ASSERT(caretPoint.CaretPointRef().GetChild()); 181 MOZ_ASSERT( 182 caretPoint.CaretPointRef().GetChild()->IsHTMLElement(nsGkAtoms::br)); 183 nsresult rv = caretPoint.SuggestCaretPointTo(mHTMLEditor, {}); 184 if (NS_FAILED(rv)) { 185 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed"); 186 return Err(rv); 187 } 188 return EditActionResult::HandledResult(); 189 } 190 } 191 192 // If the active editing host is an inline element, or if the active editing 193 // host is the block parent itself and we're configured to use <br> as a 194 // paragraph separator, just append a <br>. 195 // If the editing host parent element is editable, it means that the editing 196 // host must be a <body> element and the selection may be outside the body 197 // element. If the selection is outside the editing host, we should not 198 // insert new paragraph nor <br> element. 199 // XXX Currently, we don't support editing outside <body> element, but Blink 200 // does it. 201 if (mEditingHost.GetParentElement() && 202 HTMLEditUtils::IsSimplyEditableNode(*mEditingHost.GetParentElement()) && 203 !nsContentUtils::ContentIsFlattenedTreeDescendantOf( 204 pointToInsert.ContainerAs<nsIContent>(), &mEditingHost)) { 205 return Err(NS_ERROR_EDITOR_NO_EDITABLE_RANGE); 206 } 207 208 // Look for the nearest parent block. However, don't return error even if 209 // there is no block parent here because in such case, i.e., editing host 210 // is an inline element, we should insert <br> simply. 211 RefPtr<Element> editableBlockElement = 212 HTMLEditUtils::GetInclusiveAncestorElement( 213 *pointToInsert.ContainerAs<nsIContent>(), 214 HTMLEditUtils::ClosestEditableBlockElementOrButtonElement, 215 BlockInlineCheck::UseComputedDisplayOutsideStyle); 216 217 // If we cannot insert a <p>/<div> element at the selection, we should insert 218 // a <br> element or a linefeed instead. 219 if (ShouldInsertLineBreakInstead(editableBlockElement, pointToInsert)) { 220 const Maybe<LineBreakType> lineBreakType = 221 mHTMLEditor.GetPreferredLineBreakType( 222 *pointToInsert.ContainerAs<nsIContent>(), mEditingHost); 223 if (MOZ_UNLIKELY(!lineBreakType)) { 224 // Cannot insert a line break there. 225 return EditActionResult::IgnoredResult(); 226 } 227 if (lineBreakType.value() == LineBreakType::Linefeed) { 228 Result<EditActionResult, nsresult> insertLinefeedResultOrError = 229 HandleInsertLinefeed(pointToInsert); 230 NS_WARNING_ASSERTION( 231 insertLinefeedResultOrError.isOk(), 232 "AutoInsertParagraphHandler::HandleInsertLinefeed() failed"); 233 return insertLinefeedResultOrError; 234 } 235 Result<EditActionResult, nsresult> insertBRElementResultOrError = 236 HandleInsertBRElement(pointToInsert); 237 NS_WARNING_ASSERTION( 238 insertBRElementResultOrError.isOk(), 239 "AutoInsertParagraphHandler::HandleInsertBRElement() failed"); 240 return insertBRElementResultOrError; 241 } 242 243 RefPtr<Element> blockElementToPutCaret; 244 // If the default paragraph separator is not <br> and selection is not in 245 // a splittable block element, we should wrap selected contents in a new 246 // paragraph, then, split it. 247 if (!HTMLEditUtils::IsSplittableNode(*editableBlockElement) && 248 mDefaultParagraphSeparator != ParagraphSeparator::br) { 249 MOZ_ASSERT(mDefaultParagraphSeparator == ParagraphSeparator::div || 250 mDefaultParagraphSeparator == ParagraphSeparator::p); 251 // FIXME: If there is no splittable block element, the other browsers wrap 252 // the right nodes into new paragraph, but keep the left node as-is. 253 // We should follow them to make here simpler and better compatibility. 254 Result<RefPtr<Element>, nsresult> suggestBlockElementToPutCaretOrError = 255 mHTMLEditor.FormatBlockContainerWithTransaction( 256 selectionRanges, 257 MOZ_KnownLive(HTMLEditor::ToParagraphSeparatorTagName( 258 mDefaultParagraphSeparator)), 259 // For keeping the traditional behavior at insertParagraph command, 260 // let's use the XUL paragraph state command targets even if we're 261 // handling HTML insertParagraph command. 262 FormatBlockMode::XULParagraphStateCommand, mEditingHost); 263 if (MOZ_UNLIKELY(suggestBlockElementToPutCaretOrError.isErr())) { 264 NS_WARNING("HTMLEditor::FormatBlockContainerWithTransaction() failed"); 265 return suggestBlockElementToPutCaretOrError.propagateErr(); 266 } 267 if (selectionRanges.HasSavedRanges()) { 268 selectionRanges.RestoreFromSavedRanges(); 269 } 270 pointToInsert = selectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>(); 271 if (NS_WARN_IF(!pointToInsert.IsInContentNode())) { 272 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 273 } 274 MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); 275 276 editableBlockElement = HTMLEditUtils::GetInclusiveAncestorElement( 277 *pointToInsert.ContainerAs<nsIContent>(), 278 HTMLEditUtils::ClosestEditableBlockElementOrButtonElement, 279 BlockInlineCheck::UseComputedDisplayOutsideStyle); 280 if (NS_WARN_IF(!editableBlockElement)) { 281 return Err(NS_ERROR_UNEXPECTED); 282 } 283 if (NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(*editableBlockElement))) { 284 // Didn't create a new block for some reason, fall back to <br> 285 Result<EditActionResult, nsresult> insertBRElementResultOrError = 286 HandleInsertBRElement(pointToInsert, blockElementToPutCaret); 287 NS_WARNING_ASSERTION( 288 insertBRElementResultOrError.isOk(), 289 "AutoInsertParagraphHandler::HandleInsertBRElement() failed"); 290 return insertBRElementResultOrError; 291 } 292 // We want to collapse selection in the editable block element. 293 blockElementToPutCaret = editableBlockElement; 294 } 295 296 // If block is empty, populate with br. (For example, imagine a div that 297 // contains the word "text". The user selects "text" and types return. 298 // "Text" is deleted leaving an empty block. We want to put in one br to 299 // make block have a line. Then code further below will put in a second br.) 300 RefPtr<Element> insertedPaddingBRElement; 301 { 302 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 303 InsertBRElementIfEmptyBlockElement( 304 *editableBlockElement, InsertBRElementIntoEmptyBlock::End, 305 BlockInlineCheck::UseComputedDisplayOutsideStyle); 306 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 307 NS_WARNING( 308 "AutoInsertParagraphHandler::InsertBRElementIfEmptyBlockElement(" 309 "InsertBRElementIntoEmptyBlock::End, " 310 "BlockInlineCheck::UseComputedDisplayOutsideStyle) failed"); 311 return insertBRElementResultOrError.propagateErr(); 312 } 313 314 CreateLineBreakResult insertBRElementResult = 315 insertBRElementResultOrError.unwrap(); 316 insertBRElementResult.IgnoreCaretPointSuggestion(); 317 if (insertBRElementResult.Handled()) { 318 insertedPaddingBRElement = &insertBRElementResult->BRElementRef(); 319 } 320 321 pointToInsert = selectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>(); 322 if (NS_WARN_IF(!pointToInsert.IsInContentNode())) { 323 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 324 } 325 } 326 327 RefPtr<Element> maybeNonEditableListItem = 328 HTMLEditUtils::GetClosestInclusiveAncestorListItemElement( 329 *editableBlockElement, &mEditingHost); 330 if (maybeNonEditableListItem && 331 HTMLEditUtils::IsSplittableNode(*maybeNonEditableListItem)) { 332 Result<InsertParagraphResult, nsresult> insertParagraphInListItemResult = 333 HandleInListItemElement(*maybeNonEditableListItem, pointToInsert); 334 if (MOZ_UNLIKELY(insertParagraphInListItemResult.isErr())) { 335 if (NS_WARN_IF(insertParagraphInListItemResult.unwrapErr() == 336 NS_ERROR_EDITOR_DESTROYED)) { 337 return Err(NS_ERROR_EDITOR_DESTROYED); 338 } 339 NS_WARNING( 340 "AutoInsertParagraphHandler::HandleInListItemElement() failed, but " 341 "ignored"); 342 return EditActionResult::HandledResult(); 343 } 344 InsertParagraphResult unwrappedInsertParagraphInListItemResult = 345 insertParagraphInListItemResult.unwrap(); 346 MOZ_ASSERT(unwrappedInsertParagraphInListItemResult.Handled()); 347 MOZ_ASSERT(unwrappedInsertParagraphInListItemResult.GetNewNode()); 348 const RefPtr<Element> listItemOrParagraphElement = 349 unwrappedInsertParagraphInListItemResult.UnwrapNewNode(); 350 const EditorDOMPoint pointToPutCaret = 351 unwrappedInsertParagraphInListItemResult.UnwrapCaretPoint(); 352 nsresult rv = CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret( 353 pointToPutCaret, listItemOrParagraphElement, 354 {SuggestCaret::AndIgnoreTrivialError}); 355 if (NS_FAILED(rv)) { 356 NS_WARNING( 357 "AutoInsertParagraphHandler::" 358 "CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret() failed"); 359 return Err(rv); 360 } 361 NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 362 "CollapseSelection() failed, but ignored"); 363 return EditActionResult::HandledResult(); 364 } 365 366 if (HTMLEditUtils::IsHeadingElement(*editableBlockElement)) { 367 Result<InsertParagraphResult, nsresult> 368 insertParagraphInHeadingElementResult = 369 HandleInHeadingElement(*editableBlockElement, pointToInsert); 370 if (MOZ_UNLIKELY(insertParagraphInHeadingElementResult.isErr())) { 371 NS_WARNING( 372 "AutoInsertParagraphHandler::HandleInHeadingElement() failed, but " 373 "ignored"); 374 return EditActionResult::HandledResult(); 375 } 376 InsertParagraphResult unwrappedInsertParagraphInHeadingElementResult = 377 insertParagraphInHeadingElementResult.unwrap(); 378 if (unwrappedInsertParagraphInHeadingElementResult.Handled()) { 379 MOZ_ASSERT(unwrappedInsertParagraphInHeadingElementResult.GetNewNode()); 380 blockElementToPutCaret = 381 unwrappedInsertParagraphInHeadingElementResult.UnwrapNewNode(); 382 } 383 const EditorDOMPoint pointToPutCaret = 384 unwrappedInsertParagraphInHeadingElementResult.UnwrapCaretPoint(); 385 nsresult rv = CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret( 386 pointToPutCaret, blockElementToPutCaret, 387 {SuggestCaret::OnlyIfHasSuggestion, 388 SuggestCaret::AndIgnoreTrivialError}); 389 if (NS_FAILED(rv)) { 390 NS_WARNING( 391 "AutoInsertParagraphHandler::" 392 "CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret() failed"); 393 return Err(rv); 394 } 395 NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 396 "CollapseSelection() failed, but ignored"); 397 return EditActionResult::HandledResult(); 398 } 399 400 // XXX Ideally, we should take same behavior with both <p> container and 401 // <div> container. However, we are still using <br> as default 402 // paragraph separator (non-standard) and we've split only <p> container 403 // long time. Therefore, some web apps may depend on this behavior like 404 // Gmail. So, let's use traditional odd behavior only when the default 405 // paragraph separator is <br>. Otherwise, take consistent behavior 406 // between <p> container and <div> container. 407 if ((mDefaultParagraphSeparator == ParagraphSeparator::br && 408 editableBlockElement->IsHTMLElement(nsGkAtoms::p)) || 409 (mDefaultParagraphSeparator != ParagraphSeparator::br && 410 editableBlockElement->IsAnyOfHTMLElements(nsGkAtoms::p, 411 nsGkAtoms::div))) { 412 const EditorDOMPoint pointToSplit = GetBetterPointToSplitParagraph( 413 *editableBlockElement, insertedPaddingBRElement 414 ? EditorDOMPoint(insertedPaddingBRElement) 415 : pointToInsert); 416 if (ShouldCreateNewParagraph(*editableBlockElement, pointToSplit)) { 417 MOZ_ASSERT(pointToSplit.IsInContentNodeAndValidInComposedDoc()); 418 // Paragraphs: special rules to look for <br>s 419 Result<SplitNodeResult, nsresult> splitNodeResult = 420 SplitParagraphWithTransaction(*editableBlockElement, pointToSplit); 421 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 422 NS_WARNING("HTMLEditor::SplitParagraphWithTransaction() failed"); 423 return splitNodeResult.propagateErr(); 424 } 425 if (splitNodeResult.inspect().Handled()) { 426 SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap(); 427 const RefPtr<Element> rightParagraphElement = 428 unwrappedSplitNodeResult.DidSplit() 429 ? unwrappedSplitNodeResult.GetNextContentAs<Element>() 430 : blockElementToPutCaret.get(); 431 const EditorDOMPoint pointToPutCaret = 432 unwrappedSplitNodeResult.UnwrapCaretPoint(); 433 nsresult rv = CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret( 434 pointToPutCaret, rightParagraphElement, 435 {SuggestCaret::AndIgnoreTrivialError}); 436 if (NS_FAILED(rv)) { 437 NS_WARNING( 438 "AutoInsertParagraphHandler::" 439 "CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret() " 440 "failed"); 441 return Err(rv); 442 } 443 NS_WARNING_ASSERTION( 444 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 445 "AutoInsertParagraphHandler::" 446 "CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret() " 447 "failed, but ignored"); 448 return EditActionResult::HandledResult(); 449 } 450 MOZ_ASSERT(!splitNodeResult.inspect().HasCaretPointSuggestion()); 451 } 452 453 // Fall through, if we didn't handle it above. 454 } 455 456 // If nobody handles this edit action, let's insert new <br> at the selection. 457 Result<EditActionResult, nsresult> insertBRElementResultOrError = 458 HandleInsertBRElement(pointToInsert, blockElementToPutCaret); 459 NS_WARNING_ASSERTION( 460 insertBRElementResultOrError.isOk(), 461 "AutoInsertParagraphHandler::HandleInsertBRElement() failed"); 462 return insertBRElementResultOrError; 463 } 464 465 Result<EditActionResult, nsresult> 466 HTMLEditor::AutoInsertParagraphHandler::HandleInsertBRElement( 467 const EditorDOMPoint& aPointToInsert, 468 const Element* aBlockElementWhichShouldHaveCaret /* = nullptr */) { 469 Result<CreateElementResult, nsresult> insertBRElementResult = 470 InsertBRElement(aPointToInsert); 471 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) { 472 NS_WARNING("AutoInsertParagraphHandler::InsertBRElement() failed"); 473 return insertBRElementResult.propagateErr(); 474 } 475 const EditorDOMPoint pointToPutCaret = 476 insertBRElementResult.unwrap().UnwrapCaretPoint(); 477 if (MOZ_UNLIKELY(!pointToPutCaret.IsSet())) { 478 NS_WARNING( 479 "AutoInsertParagraphHandler::InsertBRElement() didn't suggest a " 480 "point to put caret"); 481 return Err(NS_ERROR_FAILURE); 482 } 483 nsresult rv = CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret( 484 pointToPutCaret, aBlockElementWhichShouldHaveCaret, {}); 485 if (NS_FAILED(rv)) { 486 NS_WARNING( 487 "AutoInsertParagraphHandler::" 488 "CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret() failed"); 489 return Err(rv); 490 } 491 return EditActionResult::HandledResult(); 492 } 493 494 Result<EditActionResult, nsresult> 495 HTMLEditor::AutoInsertParagraphHandler::HandleInsertLinefeed( 496 const EditorDOMPoint& aPointToInsert) { 497 Result<EditorDOMPoint, nsresult> insertLineFeedResult = 498 AutoInsertLineBreakHandler::InsertLinefeed(mHTMLEditor, aPointToInsert, 499 mEditingHost); 500 if (MOZ_UNLIKELY(insertLineFeedResult.isErr())) { 501 NS_WARNING("AutoInsertLineBreakHandler::InsertLinefeed() failed"); 502 return insertLineFeedResult.propagateErr(); 503 } 504 nsresult rv = mHTMLEditor.CollapseSelectionTo(insertLineFeedResult.inspect()); 505 if (NS_FAILED(rv)) { 506 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 507 return Err(rv); 508 } 509 return EditActionResult::HandledResult(); 510 } 511 512 bool HTMLEditor::AutoInsertParagraphHandler::ShouldInsertLineBreakInstead( 513 const Element* aEditableBlockElement, 514 const EditorDOMPoint& aCandidatePointToSplit) { 515 // If there is no block parent in the editing host, i.e., the editing 516 // host itself is also a non-block element, we should insert a line 517 // break. 518 if (!aEditableBlockElement) { 519 // XXX Chromium checks if the CSS box of the editing host is a block. 520 return true; 521 } 522 523 // If the editable block element is not splittable, e.g., it's an 524 // editing host, and the default paragraph separator is <br> or the 525 // element cannot contain a <p> element, we should insert a <br> 526 // element. 527 if (!HTMLEditUtils::IsSplittableNode(*aEditableBlockElement)) { 528 return mDefaultParagraphSeparator == ParagraphSeparator::br || 529 !HTMLEditUtils::CanElementContainParagraph(*aEditableBlockElement) || 530 (aCandidatePointToSplit.IsInContentNode() && 531 mHTMLEditor 532 .GetPreferredLineBreakType( 533 *aCandidatePointToSplit.ContainerAs<nsIContent>(), 534 mEditingHost) 535 .valueOr(LineBreakType::BRElement) == 536 LineBreakType::Linefeed && 537 HTMLEditUtils::IsDisplayOutsideInline(mEditingHost)); 538 } 539 540 // If the nearest block parent is a single-line container declared in 541 // the execCommand spec and not the editing host, we should separate the 542 // block even if the default paragraph separator is <br> element. 543 if (HTMLEditUtils::IsSingleLineContainer(*aEditableBlockElement)) { 544 return false; 545 } 546 547 // Otherwise, unless there is no block ancestor which can contain <p> 548 // element, we shouldn't insert a line break here. 549 for (const Element* editableBlockAncestor = aEditableBlockElement; 550 editableBlockAncestor; 551 editableBlockAncestor = HTMLEditUtils::GetAncestorElement( 552 *editableBlockAncestor, 553 HTMLEditUtils::ClosestEditableBlockElementOrButtonElement, 554 BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 555 if (HTMLEditUtils::CanElementContainParagraph(*editableBlockAncestor)) { 556 return false; 557 } 558 } 559 return true; 560 } 561 562 // static 563 nsresult HTMLEditor::AutoInsertParagraphHandler:: 564 CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret( 565 const EditorDOMPoint& aCandidatePointToPutCaret, 566 const Element* aBlockElementShouldHaveCaret, 567 const SuggestCaretOptions& aOptions) { 568 if (!aCandidatePointToPutCaret.IsSet()) { 569 if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion)) { 570 return NS_OK; 571 } 572 return aOptions.contains(SuggestCaret::AndIgnoreTrivialError) 573 ? NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR 574 : NS_ERROR_FAILURE; 575 } 576 EditorDOMPoint pointToPutCaret(aCandidatePointToPutCaret); 577 if (aBlockElementShouldHaveCaret) { 578 Result<EditorDOMPoint, nsresult> pointToPutCaretOrError = 579 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<EditorDOMPoint>( 580 *aBlockElementShouldHaveCaret, aCandidatePointToPutCaret); 581 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 582 NS_WARNING( 583 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() " 584 "failed, but ignored"); 585 } else if (pointToPutCaretOrError.inspect().IsSet()) { 586 pointToPutCaret = pointToPutCaretOrError.unwrap(); 587 } 588 } 589 nsresult rv = mHTMLEditor.CollapseSelectionTo(pointToPutCaret); 590 if (NS_FAILED(rv) && MOZ_LIKELY(rv != NS_ERROR_EDITOR_DESTROYED) && 591 aOptions.contains(SuggestCaret::AndIgnoreTrivialError)) { 592 rv = NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR; 593 } 594 return rv; 595 } 596 597 Result<CreateElementResult, nsresult> 598 HTMLEditor::AutoInsertParagraphHandler::InsertBRElement( 599 const EditorDOMPoint& aPointToBreak) { 600 MOZ_ASSERT(aPointToBreak.IsInContentNode()); 601 602 const bool editingHostIsEmpty = HTMLEditUtils::IsEmptyNode( 603 mEditingHost, {EmptyCheckOption::TreatNonEditableContentAsInvisible}); 604 const WSRunScanner wsRunScanner({WSRunScanner::Option::OnlyEditableNodes}, 605 aPointToBreak); 606 const WSScanResult backwardScanResult = 607 wsRunScanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPointToBreak); 608 if (MOZ_UNLIKELY(backwardScanResult.Failed())) { 609 NS_WARNING( 610 "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom() failed"); 611 return Err(NS_ERROR_FAILURE); 612 } 613 const bool brElementIsAfterBlock = 614 backwardScanResult.ReachedBlockBoundary() || 615 // FIXME: This is wrong considering because the inline editing host may 616 // be surrounded by visible inline content. However, WSRunScanner is 617 // not aware of block boundary around it and stopping this change causes 618 // starting to fail some WPT. Therefore, we need to keep doing this for 619 // now. 620 backwardScanResult.ReachedInlineEditingHostBoundary(); 621 const WSScanResult forwardScanResult = 622 wsRunScanner.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom( 623 aPointToBreak); 624 if (MOZ_UNLIKELY(forwardScanResult.Failed())) { 625 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed"); 626 return Err(NS_ERROR_FAILURE); 627 } 628 const bool brElementIsBeforeBlock = 629 forwardScanResult.ReachedBlockBoundary() || 630 // FIXME: See above comment 631 forwardScanResult.ReachedInlineEditingHostBoundary(); 632 633 // First, insert a <br> element. 634 RefPtr<Element> brElement; 635 if (mHTMLEditor.IsPlaintextMailComposer()) { 636 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 637 mHTMLEditor.InsertLineBreak(WithTransaction::Yes, 638 LineBreakType::BRElement, aPointToBreak); 639 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 640 NS_WARNING( 641 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 642 "LineBreakType::BRElement) failed"); 643 return insertBRElementResultOrError.propagateErr(); 644 } 645 CreateLineBreakResult insertBRElementResult = 646 insertBRElementResultOrError.unwrap(); 647 // We'll return with suggesting new caret position and nobody refers 648 // selection after here. So we don't need to update selection here. 649 insertBRElementResult.IgnoreCaretPointSuggestion(); 650 brElement = &insertBRElementResult->BRElementRef(); 651 } else { 652 EditorDOMPoint pointToBreak(aPointToBreak); 653 // If the container of the break is a link, we need to split it and 654 // insert new <br> between the split links. 655 RefPtr<Element> linkNode = 656 HTMLEditor::GetLinkElement(pointToBreak.GetContainer()); 657 if (linkNode) { 658 // FIXME: Normalize surrounding white-spaces before splitting the 659 // insertion point here. 660 Result<SplitNodeResult, nsresult> splitLinkNodeResult = 661 mHTMLEditor.SplitNodeDeepWithTransaction( 662 *linkNode, pointToBreak, 663 SplitAtEdges::eDoNotCreateEmptyContainer); 664 if (MOZ_UNLIKELY(splitLinkNodeResult.isErr())) { 665 NS_WARNING( 666 "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::" 667 "eDoNotCreateEmptyContainer) failed"); 668 return splitLinkNodeResult.propagateErr(); 669 } 670 // TODO: Some methods called by 671 // WhiteSpaceVisibilityKeeper::InsertLineBreak() use 672 // ComputeEditingHost() which depends on selection. Therefore, 673 // we cannot skip updating selection here. 674 nsresult rv = splitLinkNodeResult.inspect().SuggestCaretPointTo( 675 mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, 676 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 677 if (NS_FAILED(rv)) { 678 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed"); 679 return Err(rv); 680 } 681 pointToBreak = 682 splitLinkNodeResult.inspect().AtSplitPoint<EditorDOMPoint>(); 683 } 684 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 685 WhiteSpaceVisibilityKeeper::InsertLineBreak(LineBreakType::BRElement, 686 mHTMLEditor, pointToBreak); 687 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 688 NS_WARNING( 689 "WhiteSpaceVisibilityKeeper::InsertLineBreak(LineBreakType::" 690 "BRElement) failed"); 691 return insertBRElementResultOrError.propagateErr(); 692 } 693 CreateLineBreakResult insertBRElementResult = 694 insertBRElementResultOrError.unwrap(); 695 // We'll return with suggesting new caret position and nobody refers 696 // selection after here. So we don't need to update selection here. 697 insertBRElementResult.IgnoreCaretPointSuggestion(); 698 brElement = &insertBRElementResult->BRElementRef(); 699 } 700 701 if (MOZ_UNLIKELY(!brElement->GetParentNode())) { 702 NS_WARNING("Inserted <br> element was removed by the web app"); 703 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 704 } 705 auto afterBRElement = EditorDOMPoint::After(brElement); 706 707 const auto InsertAdditionalInvisibleLineBreak = 708 [this, &afterBRElement]() 709 MOZ_CAN_RUN_SCRIPT -> Result<CreateLineBreakResult, nsresult> { 710 // Empty last line is invisible if it's immediately before either parent or 711 // another block's boundary so that we need to put invisible <br> element 712 // here for making it visible. 713 Result<CreateLineBreakResult, nsresult> 714 insertPaddingBRElementResultOrError = 715 WhiteSpaceVisibilityKeeper::InsertLineBreak( 716 LineBreakType::BRElement, mHTMLEditor, afterBRElement); 717 NS_WARNING_ASSERTION(insertPaddingBRElementResultOrError.isOk(), 718 "WhiteSpaceVisibilityKeeper::InsertLineBreak(" 719 "LineBreakType::BRElement) failed"); 720 // afterBRElement points after the first <br> with referring an old child. 721 // Therefore, we need to update it with new child which is the new invisible 722 // <br>. 723 afterBRElement = insertPaddingBRElementResultOrError.inspect() 724 .AtLineBreak<EditorDOMPoint>(); 725 return insertPaddingBRElementResultOrError; 726 }; 727 728 if (brElementIsAfterBlock && brElementIsBeforeBlock) { 729 // We just placed a <br> between block boundaries. This is the one case 730 // where we want the selection to be before the br we just placed, as the 731 // br will be on a new line, rather than at end of prior line. 732 // XXX brElementIsAfterBlock and brElementIsBeforeBlock were set before 733 // modifying the DOM tree. So, now, the <br> element may not be 734 // between blocks. 735 EditorDOMPoint pointToPutCaret; 736 if (editingHostIsEmpty) { 737 Result<CreateLineBreakResult, nsresult> 738 insertPaddingBRElementResultOrError = 739 InsertAdditionalInvisibleLineBreak(); 740 if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { 741 return insertPaddingBRElementResultOrError.propagateErr(); 742 } 743 insertPaddingBRElementResultOrError.unwrap().IgnoreCaretPointSuggestion(); 744 pointToPutCaret = std::move(afterBRElement); 745 } else { 746 pointToPutCaret = 747 EditorDOMPoint(brElement, InterlinePosition::StartOfNextLine); 748 } 749 return CreateElementResult(std::move(brElement), 750 std::move(pointToPutCaret)); 751 } 752 753 const WSScanResult forwardScanFromAfterBRElementResult = 754 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 755 {WSRunScanner::Option::OnlyEditableNodes}, afterBRElement); 756 if (MOZ_UNLIKELY(forwardScanFromAfterBRElementResult.Failed())) { 757 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed"); 758 return Err(NS_ERROR_FAILURE); 759 } 760 if (forwardScanFromAfterBRElementResult.ReachedBRElement()) { 761 // The next thing after the break we inserted is another break. Move the 762 // second break to be the first break's sibling. This will prevent them 763 // from being in different inline nodes, which would break 764 // SetInterlinePosition(). It will also assure that if the user clicks 765 // away and then clicks back on their new blank line, they will still get 766 // the style from the line above. 767 if (brElement->GetNextSibling() != 768 forwardScanFromAfterBRElementResult.BRElementPtr()) { 769 MOZ_ASSERT(forwardScanFromAfterBRElementResult.BRElementPtr()); 770 Result<MoveNodeResult, nsresult> moveBRElementResult = 771 mHTMLEditor.MoveNodeWithTransaction( 772 MOZ_KnownLive( 773 *forwardScanFromAfterBRElementResult.BRElementPtr()), 774 afterBRElement); 775 if (MOZ_UNLIKELY(moveBRElementResult.isErr())) { 776 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed"); 777 return moveBRElementResult.propagateErr(); 778 } 779 nsresult rv = moveBRElementResult.inspect().SuggestCaretPointTo( 780 mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, 781 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 782 SuggestCaret::AndIgnoreTrivialError}); 783 if (NS_FAILED(rv)) { 784 NS_WARNING("MoveNodeResult::SuggestCaretPointTo() failed"); 785 return Err(rv); 786 } 787 NS_WARNING_ASSERTION( 788 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 789 "MoveNodeResult::SuggestCaretPointTo() failed, but ignored"); 790 // afterBRElement points after the first <br> with referring an old child. 791 // Therefore, we need to update it with new child which is the new 792 // invisible <br>. 793 afterBRElement.Set(forwardScanFromAfterBRElementResult.BRElementPtr()); 794 } 795 } else if ((forwardScanFromAfterBRElementResult.ReachedBlockBoundary() || 796 // FIXME: This is wrong considering because the inline editing 797 // host may be surrounded by visible inline content. However, 798 // WSRunScanner is not aware of block boundary around it and 799 // stopping this change causes starting to fail some WPT. 800 // Therefore, we need to keep doing this for now. 801 forwardScanFromAfterBRElementResult 802 .ReachedInlineEditingHostBoundary()) && 803 !brElementIsAfterBlock) { 804 Result<CreateLineBreakResult, nsresult> 805 insertPaddingBRElementResultOrError = 806 InsertAdditionalInvisibleLineBreak(); 807 if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { 808 return insertPaddingBRElementResultOrError.propagateErr(); 809 } 810 insertPaddingBRElementResultOrError.unwrap().IgnoreCaretPointSuggestion(); 811 } 812 813 // We want the caret to stick to whatever is past the break. This is because 814 // the break is on the same line we were on, but the next content will be on 815 // the following line. 816 817 // An exception to this is if the break has a next sibling that is a block 818 // node. Then we stick to the left to avoid an uber caret. 819 nsIContent* nextSiblingOfBRElement = brElement->GetNextSibling(); 820 afterBRElement.SetInterlinePosition( 821 nextSiblingOfBRElement && HTMLEditUtils::IsBlockElement( 822 *nextSiblingOfBRElement, 823 BlockInlineCheck::UseComputedDisplayStyle) 824 ? InterlinePosition::EndOfLine 825 : InterlinePosition::StartOfNextLine); 826 return CreateElementResult(std::move(brElement), afterBRElement); 827 } 828 829 Result<CaretPoint, nsresult> 830 HTMLEditor::AutoInsertParagraphHandler::HandleInMailCiteElement( 831 Element& aMailCiteElement, const EditorDOMPoint& aPointToSplit) { 832 MOZ_ASSERT(aPointToSplit.IsSet()); 833 NS_ASSERTION(!HTMLEditUtils::IsEmptyNode( 834 aMailCiteElement, 835 {EmptyCheckOption::TreatNonEditableContentAsInvisible}), 836 "The mail-cite element will be deleted, does it expected result " 837 "for you?"); 838 839 auto splitCiteElementResult = 840 SplitMailCiteElement(aPointToSplit, aMailCiteElement); 841 if (MOZ_UNLIKELY(splitCiteElementResult.isErr())) { 842 NS_WARNING("Failed to split a mail-cite element"); 843 return splitCiteElementResult.propagateErr(); 844 } 845 SplitNodeResult unwrappedSplitCiteElementResult = 846 splitCiteElementResult.unwrap(); 847 // When adding caret suggestion to SplitNodeResult, here didn't change 848 // selection so that just ignore it. 849 unwrappedSplitCiteElementResult.IgnoreCaretPointSuggestion(); 850 851 // Add an invisible <br> to the end of left cite node if it was a <span> of 852 // style="display: block". This is important, since when serializing the cite 853 // to plain text, the span which caused the visual break is discarded. So the 854 // added <br> will guarantee that the serializer will insert a break where the 855 // user saw one. 856 // FYI: unwrappedSplitCiteElementResult grabs the previous node and the next 857 // node with nsCOMPtr or EditorDOMPoint. So, it's safe to access 858 // leftCiteElement and rightCiteElement even after changing the DOM tree 859 // and/or selection even though it's raw pointer. 860 auto* const leftCiteElement = 861 unwrappedSplitCiteElementResult.GetPreviousContentAs<Element>(); 862 auto* const rightCiteElement = 863 unwrappedSplitCiteElementResult.GetNextContentAs<Element>(); 864 if (leftCiteElement && leftCiteElement->IsHTMLElement(nsGkAtoms::span) && 865 // XXX Oh, this depends on layout information of new element, and it's 866 // created by the hacky flush in DoSplitNode(). So we need to 867 // redesign around this for bug 1710784. 868 leftCiteElement->GetPrimaryFrame() && 869 leftCiteElement->GetPrimaryFrame()->IsBlockFrameOrSubclass()) { 870 nsIContent* lastChild = leftCiteElement->GetLastChild(); 871 if (lastChild && !lastChild->IsHTMLElement(nsGkAtoms::br)) { 872 Result<CreateLineBreakResult, nsresult> 873 insertPaddingBRElementResultOrError = mHTMLEditor.InsertLineBreak( 874 WithTransaction::Yes, LineBreakType::BRElement, 875 EditorDOMPoint::AtEndOf(*leftCiteElement)); 876 if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { 877 NS_WARNING( 878 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 879 "LineBreakType::BRElement) failed"); 880 return insertPaddingBRElementResultOrError.propagateErr(); 881 } 882 CreateLineBreakResult insertPaddingBRElementResult = 883 insertPaddingBRElementResultOrError.unwrap(); 884 MOZ_ASSERT(insertPaddingBRElementResult.Handled()); 885 // We don't need to update selection here because we'll do another 886 // InsertLineBreak call soon. 887 insertPaddingBRElementResult.IgnoreCaretPointSuggestion(); 888 } 889 } 890 891 // In most cases, <br> should be inserted after current cite. However, if 892 // left cite hasn't been created because the split point was start of the 893 // cite node, <br> should be inserted before the current cite. 894 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 895 mHTMLEditor.InsertLineBreak( 896 WithTransaction::Yes, LineBreakType::BRElement, 897 unwrappedSplitCiteElementResult.AtSplitPoint<EditorDOMPoint>()); 898 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 899 NS_WARNING( 900 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 901 "LineBreakType::BRElement) failed"); 902 return Err(insertBRElementResultOrError.unwrapErr()); 903 } 904 CreateLineBreakResult insertBRElementResult = 905 insertBRElementResultOrError.unwrap(); 906 MOZ_ASSERT(insertBRElementResult.Handled()); 907 // We'll return with suggesting caret position. Therefore, we don't need 908 // to update selection here. 909 insertBRElementResult.IgnoreCaretPointSuggestion(); 910 // if aMailCiteElement wasn't a block, we might also want another break before 911 // it. We need to examine the content both before the br we just added and 912 // also just after it. If we don't have another br or block boundary 913 // adjacent, then we will need a 2nd br added to achieve blank line that user 914 // expects. 915 { 916 nsresult rvOfInsertPaddingBRElement = 917 MaybeInsertPaddingBRElementToInlineMailCiteElement( 918 insertBRElementResult.AtLineBreak<EditorDOMPoint>(), 919 aMailCiteElement); 920 if (NS_FAILED(rvOfInsertPaddingBRElement)) { 921 NS_WARNING( 922 "Failed to insert additional <br> element before the inline right " 923 "mail-cite element"); 924 return Err(rvOfInsertPaddingBRElement); 925 } 926 } 927 928 if (leftCiteElement && 929 HTMLEditUtils::IsEmptyNode( 930 *leftCiteElement, 931 {EmptyCheckOption::TreatNonEditableContentAsInvisible})) { 932 // MOZ_KnownLive(leftCiteElement) because it's grabbed by 933 // unwrappedSplitCiteElementResult. 934 nsresult rv = 935 mHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(*leftCiteElement)); 936 if (NS_FAILED(rv)) { 937 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 938 return Err(rv); 939 } 940 } 941 942 if (rightCiteElement && 943 HTMLEditUtils::IsEmptyNode( 944 *rightCiteElement, 945 {EmptyCheckOption::TreatNonEditableContentAsInvisible})) { 946 // MOZ_KnownLive(rightCiteElement) because it's grabbed by 947 // unwrappedSplitCiteElementResult. 948 nsresult rv = 949 mHTMLEditor.DeleteNodeWithTransaction(MOZ_KnownLive(*rightCiteElement)); 950 if (NS_FAILED(rv)) { 951 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 952 return Err(rv); 953 } 954 } 955 956 if (NS_WARN_IF(!insertBRElementResult.LineBreakIsInComposedDoc())) { 957 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 958 } 959 auto pointToPutCaret = insertBRElementResult.AtLineBreak<EditorDOMPoint>(); 960 pointToPutCaret.SetInterlinePosition(InterlinePosition::StartOfNextLine); 961 return CaretPoint(std::move(pointToPutCaret)); 962 } 963 964 Result<SplitNodeResult, nsresult> 965 HTMLEditor::AutoInsertParagraphHandler::SplitMailCiteElement( 966 const EditorDOMPoint& aPointToSplit, Element& aMailCiteElement) { 967 EditorDOMPoint pointToSplit(aPointToSplit); 968 969 // If our selection is just before a break, nudge it to be just after 970 // it. This does two things for us. It saves us the trouble of having 971 // to add a break here ourselves to preserve the "blockness" of the 972 // inline span mailquote (in the inline case), and : it means the break 973 // won't end up making an empty line that happens to be inside a 974 // mailquote (in either inline or block case). The latter can confuse a 975 // user if they click there and start typing, because being in the 976 // mailquote may affect wrapping behavior, or font color, etc. 977 const WSScanResult forwardScanFromPointToSplitResult = 978 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 979 {WSRunScanner::Option::OnlyEditableNodes, 980 WSRunScanner::Option::ReferHTMLDefaultStyle}, 981 pointToSplit); 982 if (forwardScanFromPointToSplitResult.Failed()) { 983 return Err(NS_ERROR_FAILURE); 984 } 985 // If selection start point is before a break and it's inside the 986 // mailquote, let's split it after the visible node. 987 if (forwardScanFromPointToSplitResult.ReachedBRElement() && 988 forwardScanFromPointToSplitResult.BRElementPtr() != &aMailCiteElement && 989 aMailCiteElement.Contains( 990 forwardScanFromPointToSplitResult.BRElementPtr())) { 991 pointToSplit = forwardScanFromPointToSplitResult 992 .PointAfterReachedContent<EditorDOMPoint>(); 993 } 994 995 if (NS_WARN_IF(!pointToSplit.IsInContentNode())) { 996 return Err(NS_ERROR_FAILURE); 997 } 998 999 Result<EditorDOMPoint, nsresult> pointToSplitOrError = 1000 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt( 1001 mHTMLEditor, pointToSplit, 1002 {WhiteSpaceVisibilityKeeper::NormalizeOption:: 1003 StopIfPrecedingWhiteSpacesEndsWithNBP, 1004 WhiteSpaceVisibilityKeeper::NormalizeOption:: 1005 StopIfFollowingWhiteSpacesStartsWithNBSP}); 1006 if (MOZ_UNLIKELY(pointToSplitOrError.isErr())) { 1007 NS_WARNING( 1008 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt() " 1009 "failed"); 1010 return pointToSplitOrError.propagateErr(); 1011 } 1012 pointToSplit = pointToSplitOrError.unwrap(); 1013 if (NS_WARN_IF(!pointToSplit.IsInContentNode())) { 1014 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1015 } 1016 1017 Result<SplitNodeResult, nsresult> splitResult = 1018 mHTMLEditor.SplitNodeDeepWithTransaction( 1019 aMailCiteElement, pointToSplit, 1020 SplitAtEdges::eDoNotCreateEmptyContainer); 1021 if (MOZ_UNLIKELY(splitResult.isErr())) { 1022 NS_WARNING( 1023 "HTMLEditor::SplitNodeDeepWithTransaction(aMailCiteElement, " 1024 "SplitAtEdges::eDoNotCreateEmptyContainer) failed"); 1025 return splitResult; 1026 } 1027 // FIXME: We should make the caller handle `Selection`. 1028 nsresult rv = splitResult.inspect().SuggestCaretPointTo( 1029 mHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion, 1030 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 1031 if (NS_FAILED(rv)) { 1032 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed"); 1033 return Err(rv); 1034 } 1035 return splitResult; 1036 } 1037 1038 nsresult HTMLEditor::AutoInsertParagraphHandler:: 1039 MaybeInsertPaddingBRElementToInlineMailCiteElement( 1040 const EditorDOMPoint& aPointToInsertBRElement, 1041 Element& aMailCiteElement) { 1042 if (!HTMLEditUtils::IsInlineContent(aMailCiteElement, 1043 BlockInlineCheck::UseHTMLDefaultStyle)) { 1044 return NS_SUCCESS_DOM_NO_OPERATION; 1045 } 1046 // XXX Cannot we replace this complicated check with just a call of 1047 // HTMLEditUtils::IsVisibleBRElement with 1048 // resultOfInsertingBRElement.inspect()? 1049 const WSScanResult backwardScanFromPointToCreateNewBRElementResult = 1050 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 1051 {WSRunScanner::Option::OnlyEditableNodes, 1052 WSRunScanner::Option::ReferHTMLDefaultStyle}, 1053 aPointToInsertBRElement); 1054 if (MOZ_UNLIKELY(backwardScanFromPointToCreateNewBRElementResult.Failed())) { 1055 NS_WARNING( 1056 "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary() " 1057 "failed"); 1058 return NS_ERROR_FAILURE; 1059 } 1060 if (!backwardScanFromPointToCreateNewBRElementResult 1061 .InVisibleOrCollapsibleCharacters() && 1062 !backwardScanFromPointToCreateNewBRElementResult 1063 .ReachedSpecialContent()) { 1064 return NS_SUCCESS_DOM_NO_OPERATION; 1065 } 1066 const WSScanResult forwardScanFromPointAfterNewBRElementResult = 1067 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 1068 {WSRunScanner::Option::OnlyEditableNodes, 1069 WSRunScanner::Option::ReferHTMLDefaultStyle}, 1070 EditorRawDOMPoint::After(aPointToInsertBRElement)); 1071 if (MOZ_UNLIKELY(forwardScanFromPointAfterNewBRElementResult.Failed())) { 1072 NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed"); 1073 return NS_ERROR_FAILURE; 1074 } 1075 if (!forwardScanFromPointAfterNewBRElementResult 1076 .InVisibleOrCollapsibleCharacters() && 1077 !forwardScanFromPointAfterNewBRElementResult.ReachedSpecialContent() && 1078 // In case we're at the very end. 1079 !forwardScanFromPointAfterNewBRElementResult 1080 .ReachedCurrentBlockBoundary()) { 1081 return NS_SUCCESS_DOM_NO_OPERATION; 1082 } 1083 Result<CreateLineBreakResult, nsresult> insertAnotherBRElementResultOrError = 1084 mHTMLEditor.InsertLineBreak(WithTransaction::Yes, 1085 LineBreakType::BRElement, 1086 aPointToInsertBRElement); 1087 if (MOZ_UNLIKELY(insertAnotherBRElementResultOrError.isErr())) { 1088 NS_WARNING( 1089 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 1090 "LineBreakType::BRElement) failed"); 1091 return insertAnotherBRElementResultOrError.unwrapErr(); 1092 } 1093 CreateLineBreakResult insertAnotherBRElementResult = 1094 insertAnotherBRElementResultOrError.unwrap(); 1095 MOZ_ASSERT(insertAnotherBRElementResult.Handled()); 1096 insertAnotherBRElementResult.IgnoreCaretPointSuggestion(); 1097 return NS_OK; 1098 } 1099 1100 Result<InsertParagraphResult, nsresult> 1101 HTMLEditor::AutoInsertParagraphHandler::HandleInHeadingElement( 1102 Element& aHeadingElement, const EditorDOMPoint& aPointToSplit) { 1103 // Don't preserve empty link at the end of the left heading element nor the 1104 // start of the right one. 1105 const EditorDOMPoint pointToSplit = 1106 GetBetterPointToSplitParagraph(aHeadingElement, aPointToSplit); 1107 MOZ_ASSERT(pointToSplit.IsInContentNodeAndValidInComposedDoc()); 1108 1109 // If the split point is end of the heading element, we should not touch the 1110 // heading element and insert a default paragraph next to the heading element. 1111 if (SplitPointIsEndOfSplittingBlock(aHeadingElement, pointToSplit, 1112 IgnoreBlockBoundaries::Yes)) { 1113 Result<InsertParagraphResult, nsresult> 1114 handleAtEndOfHeadingElementResultOrError = 1115 HandleAtEndOfHeadingElement(aHeadingElement); 1116 NS_WARNING_ASSERTION( 1117 handleAtEndOfHeadingElementResultOrError.isOk(), 1118 "AutoInsertParagraphHandler::HandleAtEndOfHeadingElement() failed"); 1119 return handleAtEndOfHeadingElementResultOrError; 1120 } 1121 1122 Result<SplitNodeResult, nsresult> splitHeadingResultOrError = 1123 SplitParagraphWithTransaction(aHeadingElement, pointToSplit); 1124 if (MOZ_UNLIKELY(splitHeadingResultOrError.isErr())) { 1125 NS_WARNING( 1126 "AutoInsertParagraphHandler::SplitParagraphWithTransaction() failed"); 1127 return splitHeadingResultOrError.propagateErr(); 1128 } 1129 SplitNodeResult splitHeadingResult = splitHeadingResultOrError.unwrap(); 1130 splitHeadingResult.IgnoreCaretPointSuggestion(); 1131 if (MOZ_UNLIKELY(!splitHeadingResult.DidSplit())) { 1132 NS_WARNING( 1133 "AutoInsertParagraphHandler::SplitParagraphWithTransaction() didn't " 1134 "split aHeadingElement"); 1135 return Err(NS_ERROR_FAILURE); 1136 } 1137 1138 // Put caret at start of the right head element if it's not empty. 1139 auto* const rightHeadingElement = 1140 splitHeadingResult.GetNextContentAs<Element>(); 1141 MOZ_ASSERT(rightHeadingElement, 1142 "SplitNodeResult::GetNextContent() should return something if " 1143 "DidSplit() returns true"); 1144 return InsertParagraphResult(*rightHeadingElement, 1145 splitHeadingResult.UnwrapCaretPoint()); 1146 } 1147 1148 Result<InsertParagraphResult, nsresult> 1149 HTMLEditor::AutoInsertParagraphHandler::HandleAtEndOfHeadingElement( 1150 Element& aHeadingElement) { 1151 // XXX This makes HTMLEditor instance stateful. So, we should move this out 1152 // from AutoInsertParagraphHandler with adding a method which HTMLEditor can 1153 // consider to do this. 1154 mHTMLEditor.TopLevelEditSubActionDataRef().mCachedPendingStyles->Clear(); 1155 mHTMLEditor.mPendingStylesToApplyToNewContent->ClearAllStyles(); 1156 1157 // Create a paragraph if the right heading element is not followed by an 1158 // editable <br> element. 1159 nsStaticAtom& newParagraphTagName = 1160 &mDefaultParagraphSeparatorTagName == nsGkAtoms::br 1161 ? *nsGkAtoms::p 1162 : mDefaultParagraphSeparatorTagName; 1163 // We want a wrapper element even if we separate with a <br>. 1164 // FIXME: Chrome does not preserve the style coming from the heading element. 1165 // However, Chrome preserves the inline ancestors at the split point. 1166 // Perhaps, we should follow them. 1167 // MOZ_KnownLive(newParagraphTagName) because it's available until shutdown. 1168 Result<CreateElementResult, nsresult> createNewParagraphElementResult = 1169 mHTMLEditor.CreateAndInsertElement(WithTransaction::Yes, 1170 MOZ_KnownLive(newParagraphTagName), 1171 EditorDOMPoint::After(aHeadingElement), 1172 HTMLEditor::InsertNewBRElement); 1173 if (MOZ_UNLIKELY(createNewParagraphElementResult.isErr())) { 1174 NS_WARNING( 1175 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed"); 1176 return createNewParagraphElementResult.propagateErr(); 1177 } 1178 CreateElementResult unwrappedCreateNewParagraphElementResult = 1179 createNewParagraphElementResult.unwrap(); 1180 // Put caret at the <br> element in the following paragraph. 1181 unwrappedCreateNewParagraphElementResult.IgnoreCaretPointSuggestion(); 1182 MOZ_ASSERT(unwrappedCreateNewParagraphElementResult.GetNewNode()); 1183 EditorDOMPoint pointToPutCaret( 1184 unwrappedCreateNewParagraphElementResult.GetNewNode(), 0u); 1185 return InsertParagraphResult( 1186 unwrappedCreateNewParagraphElementResult.UnwrapNewNode(), 1187 std::move(pointToPutCaret)); 1188 } 1189 1190 // static 1191 bool HTMLEditor::AutoInsertParagraphHandler:: 1192 IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( 1193 const dom::HTMLBRElement* aBRElement) { 1194 return !aBRElement || HTMLEditUtils::IsInvisibleBRElement(*aBRElement) || 1195 EditorUtils::IsPaddingBRElementForEmptyLastLine(*aBRElement); 1196 } 1197 1198 bool HTMLEditor::AutoInsertParagraphHandler::ShouldCreateNewParagraph( 1199 Element& aParentDivOrP, const EditorDOMPoint& aPointToSplit) const { 1200 MOZ_ASSERT(aPointToSplit.IsInContentNodeAndValidInComposedDoc()); 1201 1202 if (MOZ_LIKELY(mHTMLEditor.GetReturnInParagraphCreatesNewParagraph())) { 1203 // We should always create a new paragraph by default. 1204 return true; 1205 } 1206 if (aPointToSplit.GetContainer() == &aParentDivOrP) { 1207 // We are trying to split only the current paragraph, let's do it. 1208 return true; 1209 } 1210 if (aPointToSplit.IsInTextNode()) { 1211 if (aPointToSplit.IsStartOfContainer()) { 1212 // If we're splitting the paragraph at start of a `Text` and it does 1213 // not follow a <br> or follows an invisible <br>, we should not create a 1214 // new paragraph. 1215 // XXX It seems that here assumes that the paragraph has only this `Text`. 1216 const auto* const precedingBRElement = 1217 HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetPreviousSibling( 1218 *aPointToSplit.ContainerAs<Text>(), 1219 {WalkTreeOption::IgnoreNonEditableNode})); 1220 return !IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( 1221 precedingBRElement); 1222 } 1223 if (aPointToSplit.IsEndOfContainer()) { 1224 // If we're splitting the paragraph at end of a `Text` and it's not 1225 // followed by a <br> or is followed by an invisible <br>, we should not 1226 // create a new paragraph. 1227 // XXX It seems that here assumes that the paragraph has only this `Text`. 1228 const auto* const followingBRElement = 1229 HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetNextSibling( 1230 *aPointToSplit.ContainerAs<Text>(), 1231 {WalkTreeOption::IgnoreNonEditableNode})); 1232 return !IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( 1233 followingBRElement); 1234 } 1235 // If we're splitting the paragraph at middle of a `Text`, we should create 1236 // a new paragraph. 1237 return true; 1238 } 1239 1240 // If we're splitting in a child element of the paragraph and it does not 1241 // follow a <br> or follows an invisible <br>, maybe we should not create a 1242 // new paragraph. 1243 // XXX Why? We probably need to do this if we're splitting in an inline 1244 // element which and whose parents provide some styles, we should put 1245 // the <br> element for making a placeholder in the left paragraph for 1246 // moving to the caret, but I think that this could be handled in fewer 1247 // cases than this. 1248 const auto* const precedingBRElement = 1249 HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetPreviousContent( 1250 aPointToSplit, {WalkTreeOption::IgnoreNonEditableNode}, 1251 BlockInlineCheck::Unused, &mEditingHost)); 1252 if (!IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( 1253 precedingBRElement)) { 1254 return true; 1255 } 1256 // If we're splitting in a child element of the paragraph and it's not 1257 // followed by a <br> or followed by an invisible <br>, we should not create a 1258 // new paragraph. 1259 const auto* followingBRElement = 1260 HTMLBRElement::FromNodeOrNull(HTMLEditUtils::GetNextContent( 1261 aPointToSplit, {WalkTreeOption::IgnoreNonEditableNode}, 1262 BlockInlineCheck::Unused, &mEditingHost)); 1263 return !IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( 1264 followingBRElement); 1265 } 1266 1267 // static 1268 EditorDOMPoint 1269 HTMLEditor::AutoInsertParagraphHandler::GetBetterPointToSplitParagraph( 1270 const Element& aBlockElementToSplit, 1271 const EditorDOMPoint& aCandidatePointToSplit) { 1272 EditorDOMPoint pointToSplit = [&]() MOZ_NEVER_INLINE_DEBUG { 1273 // We shouldn't create new anchor element which has non-empty href unless 1274 // splitting middle of it because we assume that users don't want to create 1275 // *same* anchor element across two or more paragraphs in most cases. 1276 // So, adjust selection start if it's edge of anchor element(s). 1277 { 1278 const WSScanResult prevVisibleThing = 1279 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 1280 {}, aCandidatePointToSplit, &aBlockElementToSplit); 1281 if (prevVisibleThing.GetContent() && 1282 // Only if the previous thing is not in the same container. 1283 prevVisibleThing.GetContent() != 1284 aCandidatePointToSplit.GetContainer() && 1285 // Only if the previous thing is a preceding node of closest inclusive 1286 // ancestor element at the split point. 1287 !prevVisibleThing.GetContent()->IsInclusiveDescendantOf( 1288 aCandidatePointToSplit.GetContainerOrContainerParentElement())) { 1289 EditorRawDOMPoint candidatePointToSplit = 1290 aCandidatePointToSplit.To<EditorRawDOMPoint>(); 1291 const Element* const commonAncestor = 1292 Element::FromNode(nsContentUtils::GetClosestCommonInclusiveAncestor( 1293 candidatePointToSplit.GetContainerOrContainerParentElement(), 1294 prevVisibleThing.GetContent())); 1295 MOZ_ASSERT(commonAncestor); 1296 for (const Element* container = 1297 candidatePointToSplit.GetContainerOrContainerParentElement(); 1298 container && container != commonAncestor; 1299 container = container->GetParentElement()) { 1300 if (!HTMLEditUtils::IsHyperlinkElement(*container)) { 1301 continue; 1302 } 1303 // Found link should be only in right node. So, we shouldn't split 1304 // it. 1305 candidatePointToSplit.Set(container); 1306 // Even if we found an anchor element, don't break because DOM API 1307 // allows to nest anchor elements. 1308 } 1309 return candidatePointToSplit.To<EditorDOMPoint>(); 1310 } 1311 } 1312 WSScanResult nextVisibleThing = 1313 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 1314 {}, aCandidatePointToSplit, &aBlockElementToSplit); 1315 if (nextVisibleThing.ReachedInvisibleBRElement()) { 1316 nextVisibleThing = 1317 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 1318 {}, 1319 nextVisibleThing.PointAfterReachedContent<EditorRawDOMPoint>(), 1320 &aBlockElementToSplit); 1321 } 1322 if (nextVisibleThing.GetContent() && 1323 // Only if the next thing is not in the same container. 1324 nextVisibleThing.GetContent() != 1325 aCandidatePointToSplit.GetContainer() && 1326 // Only if the next thing is a preceding node of closest inclusive 1327 // ancestor element at the split point. 1328 !nextVisibleThing.GetContent()->IsInclusiveDescendantOf( 1329 aCandidatePointToSplit.GetContainerOrContainerParentElement())) { 1330 EditorRawDOMPoint candidatePointToSplit = 1331 aCandidatePointToSplit.To<EditorRawDOMPoint>(); 1332 const Element* const commonAncestor = 1333 Element::FromNode(nsContentUtils::GetClosestCommonInclusiveAncestor( 1334 candidatePointToSplit.GetContainerOrContainerParentElement(), 1335 nextVisibleThing.GetContent())); 1336 MOZ_ASSERT(commonAncestor); 1337 for (const Element* container = 1338 candidatePointToSplit.GetContainerOrContainerParentElement(); 1339 container && container != commonAncestor; 1340 container = container->GetParentElement()) { 1341 if (!HTMLEditUtils::IsHyperlinkElement(*container)) { 1342 continue; 1343 } 1344 // Found link should be only in left node. So, we shouldn't split it. 1345 candidatePointToSplit.SetAfter(container); 1346 // Even if we found an anchor element, don't break because DOM API 1347 // allows to nest anchor elements. 1348 } 1349 return candidatePointToSplit.To<EditorDOMPoint>(); 1350 } 1351 1352 // Okay, split the ancestors as-is. 1353 return aCandidatePointToSplit; 1354 }(); 1355 1356 // If the candidate split point is not in a splittable node, let's move the 1357 // point after the parent. 1358 for (const nsIContent* container = pointToSplit.ContainerAs<nsIContent>(); 1359 container && container != &aBlockElementToSplit && 1360 !HTMLEditUtils::IsSplittableNode(*container); 1361 container = container->GetParent()) { 1362 pointToSplit = pointToSplit.ParentPoint(); 1363 } 1364 return pointToSplit; 1365 } 1366 1367 Result<EditorDOMPoint, nsresult> HTMLEditor::AutoInsertParagraphHandler:: 1368 EnsureNoInvisibleLineBreakBeforePointToSplit( 1369 const Element& aBlockElementToSplit, 1370 const EditorDOMPoint& aPointToSplit) { 1371 const WSScanResult nextVisibleThing = 1372 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 1373 {}, aPointToSplit, &aBlockElementToSplit); 1374 if (!nextVisibleThing.ReachedBlockBoundary()) { 1375 return aPointToSplit; 1376 } 1377 const WSScanResult prevVisibleThing = 1378 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 1379 {}, aPointToSplit, &aBlockElementToSplit); 1380 Maybe<EditorLineBreak> precedingInvisibleLineBreak; 1381 if (prevVisibleThing.ReachedBRElement()) { 1382 precedingInvisibleLineBreak.emplace(*prevVisibleThing.BRElementPtr()); 1383 } else if (prevVisibleThing.ReachedPreformattedLineBreak()) { 1384 precedingInvisibleLineBreak.emplace(*prevVisibleThing.TextPtr(), 1385 prevVisibleThing.Offset_Deprecated()); 1386 } else { 1387 return aPointToSplit; 1388 } 1389 EditorDOMPoint pointToSplit = aPointToSplit; 1390 { 1391 // FIXME: Once bug 1951041 is fixed in the layout level, we don't need to 1392 // treat collapsible white-spaces before invisible <br> elements here. 1393 AutoTrackDOMPoint trackPointToSplit(mHTMLEditor.RangeUpdaterRef(), 1394 &pointToSplit); 1395 Result<EditorDOMPoint, nsresult> 1396 normalizePrecedingWhiteSpacesResultOrError = 1397 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore( 1398 mHTMLEditor, precedingInvisibleLineBreak->To<EditorDOMPoint>(), 1399 {}); 1400 if (MOZ_UNLIKELY(normalizePrecedingWhiteSpacesResultOrError.isErr())) { 1401 NS_WARNING( 1402 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesBefore() failed"); 1403 return normalizePrecedingWhiteSpacesResultOrError.propagateErr(); 1404 } 1405 } 1406 if (NS_WARN_IF(!pointToSplit.IsInContentNodeAndValidInComposedDoc()) || 1407 NS_WARN_IF(!pointToSplit.GetContainer()->IsInclusiveDescendantOf( 1408 &aBlockElementToSplit))) { 1409 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1410 } 1411 { 1412 AutoTrackDOMPoint trackPointToSplit(mHTMLEditor.RangeUpdaterRef(), 1413 &pointToSplit); 1414 Result<EditorDOMPoint, nsresult> deleteInvisibleLineBreakResult = 1415 mHTMLEditor.DeleteLineBreakWithTransaction(*precedingInvisibleLineBreak, 1416 nsIEditor::eNoStrip, 1417 aBlockElementToSplit); 1418 if (MOZ_UNLIKELY(deleteInvisibleLineBreakResult.isErr())) { 1419 NS_WARNING("HTMLEditor::DeleteLineBreakWithTransaction() failed"); 1420 return deleteInvisibleLineBreakResult.propagateErr(); 1421 } 1422 } 1423 if (NS_WARN_IF(!pointToSplit.IsInContentNodeAndValidInComposedDoc()) || 1424 NS_WARN_IF(!pointToSplit.GetContainer()->IsInclusiveDescendantOf( 1425 &aBlockElementToSplit))) { 1426 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1427 } 1428 return pointToSplit; 1429 } 1430 1431 Result<EditorDOMPoint, nsresult> HTMLEditor::AutoInsertParagraphHandler:: 1432 MaybeInsertFollowingBRElementToPreserveRightBlock( 1433 const Element& aBlockElementToSplit, 1434 const EditorDOMPoint& aPointToSplit) { 1435 MOZ_ASSERT(HTMLEditUtils::IsSplittableNode(aBlockElementToSplit)); 1436 MOZ_ASSERT(aPointToSplit.ContainerAs<nsIContent>()->IsInclusiveDescendantOf( 1437 &aBlockElementToSplit)); 1438 1439 const Element* const closestContainerElement = 1440 HTMLEditUtils::GetInclusiveAncestorElement( 1441 *aPointToSplit.ContainerAs<nsIContent>(), 1442 HTMLEditUtils::ClosestContainerElementOrVoidAncestorLimiter, 1443 BlockInlineCheck::UseComputedDisplayOutsideStyle, 1444 &aBlockElementToSplit); 1445 MOZ_ASSERT(closestContainerElement); 1446 MOZ_ASSERT(HTMLEditUtils::IsSplittableNode(*closestContainerElement)); 1447 1448 // If we're at end of the paragraph and there are some inline container 1449 // elements, we want to preserve the inline containers to preserve their 1450 // styles. 1451 Maybe<EditorLineBreak> unnecessaryLineBreak; 1452 const EditorDOMPoint pointToInsertFollowingBRElement = 1453 [&]() MOZ_NEVER_INLINE_DEBUG { 1454 const WSScanResult nextVisibleThing = 1455 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 1456 {}, aPointToSplit, &aBlockElementToSplit); 1457 if (nextVisibleThing.ReachedBRElement() || 1458 nextVisibleThing.ReachedPreformattedLineBreak()) { 1459 // If it's followed by a line break in the closest ancestor container 1460 // element, we can use it. 1461 if ((nextVisibleThing.ReachedBRElement() && 1462 nextVisibleThing.BRElementPtr()->GetParentNode() == 1463 closestContainerElement) || 1464 (nextVisibleThing.ReachedPreformattedLineBreak() && 1465 nextVisibleThing.TextPtr()->GetParentNode() == 1466 closestContainerElement)) { 1467 return EditorDOMPoint(); 1468 } 1469 const WSScanResult nextVisibleThingAfterLineBreak = 1470 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 1471 {}, 1472 nextVisibleThing 1473 .PointAfterReachedContent<EditorRawDOMPoint>(), 1474 &aBlockElementToSplit); 1475 // If the line break is visible, we don't need to insert a padding 1476 // <br> element for the right paragraph because it'll have some 1477 // visible content. 1478 if (!nextVisibleThingAfterLineBreak.ReachedCurrentBlockBoundary()) { 1479 return EditorDOMPoint(); 1480 } 1481 } 1482 // If it's not directly followed by current block boundary, we don't 1483 // need to insert a padding <br> element for the right paragraph because 1484 // it'll have some visible content. 1485 else if (!nextVisibleThing.ReachedCurrentBlockBoundary()) { 1486 return EditorDOMPoint(); 1487 } 1488 // We want to insert a padding <br> into the closest ancestor container 1489 // element to preserve the style provided by it. 1490 EditorDOMPoint candidatePoint = aPointToSplit; 1491 for (; candidatePoint.GetContainer() != closestContainerElement; 1492 candidatePoint = candidatePoint.AfterContainer()) { 1493 MOZ_ASSERT(candidatePoint.GetContainer() != &aBlockElementToSplit); 1494 } 1495 // If we reached invisible line break which is not in the closest 1496 // container element, we don't want it anymore once we put invisible 1497 // <br> element into the closest container element. 1498 if (nextVisibleThing.ReachedBRElement()) { 1499 unnecessaryLineBreak.emplace(*nextVisibleThing.BRElementPtr()); 1500 } else if (nextVisibleThing.ReachedPreformattedLineBreak()) { 1501 unnecessaryLineBreak.emplace(*nextVisibleThing.TextPtr(), 1502 nextVisibleThing.Offset_Deprecated()); 1503 } 1504 return candidatePoint; 1505 }(); 1506 1507 if (unnecessaryLineBreak) { 1508 Result<EditorDOMPoint, nsresult> deleteLineBreakResultOrError = 1509 mHTMLEditor.DeleteLineBreakWithTransaction( 1510 *unnecessaryLineBreak, nsIEditor::eNoStrip, aBlockElementToSplit); 1511 if (MOZ_UNLIKELY(deleteLineBreakResultOrError.isErr())) { 1512 NS_WARNING("HTMLEditor::DeleteLineBreakWithTransaction() failed"); 1513 return deleteLineBreakResultOrError.propagateErr(); 1514 } 1515 if (NS_WARN_IF(!aPointToSplit.IsSetAndValidInComposedDoc())) { 1516 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1517 } 1518 if (pointToInsertFollowingBRElement.IsSet() && 1519 (NS_WARN_IF(!pointToInsertFollowingBRElement 1520 .IsInContentNodeAndValidInComposedDoc()) || 1521 NS_WARN_IF(!pointToInsertFollowingBRElement.GetContainer() 1522 ->IsInclusiveDescendantOf(&aBlockElementToSplit)))) { 1523 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1524 } 1525 } 1526 EditorDOMPoint pointToSplit(aPointToSplit); 1527 if (pointToInsertFollowingBRElement.IsSet()) { 1528 Maybe<AutoTrackDOMPoint> trackPointToSplit; 1529 if (pointToSplit.GetContainer() == 1530 pointToInsertFollowingBRElement.GetContainer()) { 1531 trackPointToSplit.emplace(mHTMLEditor.RangeUpdaterRef(), &pointToSplit); 1532 } 1533 Result<CreateElementResult, nsresult> insertPaddingBRElementResultOrError = 1534 mHTMLEditor.InsertBRElement( 1535 WithTransaction::Yes, 1536 // XXX We don't want to expose the <br> for IME, but the plaintext 1537 // serializer requires this. See bug 1385905. 1538 BRElementType::Normal, pointToInsertFollowingBRElement); 1539 if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { 1540 return insertPaddingBRElementResultOrError.propagateErr(); 1541 } 1542 insertPaddingBRElementResultOrError.unwrap().IgnoreCaretPointSuggestion(); 1543 } 1544 if (NS_WARN_IF(!pointToSplit.IsInContentNodeAndValidInComposedDoc()) || 1545 NS_WARN_IF(!pointToSplit.GetContainer()->IsInclusiveDescendantOf( 1546 &aBlockElementToSplit))) { 1547 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1548 } 1549 if (mHTMLEditor.GetDefaultParagraphSeparator() != ParagraphSeparator::br) { 1550 return pointToSplit; 1551 } 1552 // If we're in the legacy mode, we don't want the right paragraph start with 1553 // an empty line. So, if the right paragraph now starts with 2 <br> elements, 1554 // remove the second one. (The first one is in the closest container element, 1555 // so, we want to keep it.) 1556 const WSScanResult nextVisibleThing = 1557 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 1558 {}, pointToSplit, &aBlockElementToSplit); 1559 if (!nextVisibleThing.ReachedBRElement()) { 1560 return pointToSplit; 1561 } 1562 const WSScanResult nextVisibleThingAfterFirstBRElement = 1563 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 1564 {}, nextVisibleThing.PointAfterReachedContent<EditorRawDOMPoint>(), 1565 &aBlockElementToSplit); 1566 if (!nextVisibleThingAfterFirstBRElement.ReachedBRElement()) { 1567 return pointToSplit; 1568 } 1569 nsresult rv = mHTMLEditor.DeleteNodeWithTransaction( 1570 MOZ_KnownLive(*nextVisibleThingAfterFirstBRElement.BRElementPtr())); 1571 if (NS_FAILED(rv)) { 1572 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 1573 return Err(rv); 1574 } 1575 if (NS_WARN_IF(!pointToSplit.IsInContentNodeAndValidInComposedDoc()) || 1576 NS_WARN_IF(!pointToSplit.GetContainer()->IsInclusiveDescendantOf( 1577 &aBlockElementToSplit))) { 1578 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1579 } 1580 return pointToSplit; 1581 } 1582 1583 Result<SplitNodeResult, nsresult> 1584 HTMLEditor::AutoInsertParagraphHandler::SplitParagraphWithTransaction( 1585 Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit) { 1586 // First, maybe the split point follows an invisible <br>. E.g., when 1587 // `<p><a href=foo>foo[]<br></a></p>`, 1588 // GetBetterSplitPointToAvoidToContinueLink() adjusted the split point as 1589 // `<p><a href=foo>foo<br></a>{}</p>`. Then, we shouldn't insert another 1590 // <br> to end of the left <p> to make the last line visible. Even though we 1591 // need to insert an invisible <br> element later, let's delete the invisible 1592 // line break first to make this method simpler. 1593 Result<EditorDOMPoint, nsresult> deleteInvisibleLineBreakResultOrError = 1594 EnsureNoInvisibleLineBreakBeforePointToSplit(aBlockElementToSplit, 1595 aPointToSplit); 1596 if (MOZ_UNLIKELY(deleteInvisibleLineBreakResultOrError.isErr())) { 1597 NS_WARNING( 1598 "AutoInsertParagraphHandler::SplitParagraphWithTransaction() failed"); 1599 return deleteInvisibleLineBreakResultOrError.propagateErr(); 1600 } 1601 EditorDOMPoint pointToSplit = deleteInvisibleLineBreakResultOrError.unwrap(); 1602 MOZ_ASSERT(pointToSplit.IsInContentNodeAndValidInComposedDoc()); 1603 MOZ_ASSERT(pointToSplit.GetContainer()->IsInclusiveDescendantOf( 1604 &aBlockElementToSplit)); 1605 1606 // Then, we need to keep the visibility of the surrounding collapsible 1607 // white-spaces at the split point. 1608 Result<EditorDOMPoint, nsresult> preparationResult = 1609 WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement( 1610 mHTMLEditor, aPointToSplit, aBlockElementToSplit); 1611 if (MOZ_UNLIKELY(preparationResult.isErr())) { 1612 NS_WARNING( 1613 "WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() failed"); 1614 return preparationResult.propagateErr(); 1615 } 1616 pointToSplit = preparationResult.unwrap(); 1617 MOZ_ASSERT(pointToSplit.IsInContentNodeAndValidInComposedDoc()); 1618 MOZ_ASSERT(pointToSplit.GetContainer()->IsInclusiveDescendantOf( 1619 &aBlockElementToSplit)); 1620 1621 // Next, if there are some inline elements which we will split and we're 1622 // splitting the deepest one at end of it, we need to put invisible <br> 1623 // before splitting to preserve the cloned inline elements in the new 1624 // paragraph. 1625 { 1626 Result<EditorDOMPoint, nsresult> insertPaddingBRElementResultOrError = 1627 MaybeInsertFollowingBRElementToPreserveRightBlock(aBlockElementToSplit, 1628 pointToSplit); 1629 if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { 1630 NS_WARNING( 1631 "AutoInsertParagraphHandler::" 1632 "MaybeInsertFollowingBRElementToPreserveRightBlock() failed"); 1633 return insertPaddingBRElementResultOrError.propagateErr(); 1634 } 1635 pointToSplit = insertPaddingBRElementResultOrError.unwrap(); 1636 if (NS_WARN_IF(!pointToSplit.IsInContentNodeAndValidInComposedDoc()) || 1637 NS_WARN_IF(!pointToSplit.GetContainer()->IsInclusiveDescendantOf( 1638 &aBlockElementToSplit))) { 1639 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1640 } 1641 } 1642 1643 // Then, split current paragraph. 1644 const RefPtr<Element> deepestContainerElementToSplit = 1645 HTMLEditUtils::GetInclusiveAncestorElement( 1646 *pointToSplit.ContainerAs<nsIContent>(), 1647 HTMLEditUtils::ClosestContainerElementOrVoidAncestorLimiter, 1648 BlockInlineCheck::UseComputedDisplayOutsideStyle, 1649 &aBlockElementToSplit); 1650 if (NS_WARN_IF(!deepestContainerElementToSplit)) { 1651 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1652 } 1653 Result<SplitNodeResult, nsresult> splitDivOrPResultOrError = 1654 mHTMLEditor.SplitNodeDeepWithTransaction( 1655 aBlockElementToSplit, pointToSplit, 1656 SplitAtEdges::eAllowToCreateEmptyContainer); 1657 if (MOZ_UNLIKELY(splitDivOrPResultOrError.isErr())) { 1658 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed"); 1659 return splitDivOrPResultOrError; 1660 } 1661 SplitNodeResult splitDivOrPResult = splitDivOrPResultOrError.unwrap(); 1662 if (MOZ_UNLIKELY(!splitDivOrPResult.DidSplit())) { 1663 NS_WARNING( 1664 "HTMLEditor::SplitNodeDeepWithTransaction() didn't split any nodes"); 1665 return splitDivOrPResult; 1666 } 1667 1668 // We'll compute caret suggestion later. So the simple result is not needed. 1669 splitDivOrPResult.IgnoreCaretPointSuggestion(); 1670 1671 auto* const leftDivOrParagraphElement = 1672 splitDivOrPResult.GetPreviousContentAs<Element>(); 1673 MOZ_ASSERT(leftDivOrParagraphElement, 1674 "SplitNodeResult::GetPreviousContent() should return something if " 1675 "DidSplit() returns true"); 1676 auto* const rightDivOrParagraphElement = 1677 splitDivOrPResult.GetNextContentAs<Element>(); 1678 MOZ_ASSERT(rightDivOrParagraphElement, 1679 "SplitNodeResult::GetNextContent() should return something if " 1680 "DidSplit() returns true"); 1681 1682 // Remove ID attribute on the paragraph from the right node. 1683 // MOZ_KnownLive(rightDivOrParagraphElement) because it's grabbed by 1684 // splitDivOrPResult. 1685 nsresult rv = mHTMLEditor.RemoveAttributeWithTransaction( 1686 MOZ_KnownLive(*rightDivOrParagraphElement), *nsGkAtoms::id); 1687 if (NS_FAILED(rv)) { 1688 NS_WARNING( 1689 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::id) failed"); 1690 return Err(rv); 1691 } 1692 1693 // Finally, we need to ensure that both paragraphs are visible even if they 1694 // are empty. Note that we need to use padding <br> element for the empty 1695 // last line as usual because it won't appear as a line break when serialized 1696 // by ContentEventHandler. Thus, if we were using normal <br> elements, 1697 // disappearing following line break of composition string would make IME 1698 // confused. 1699 if (NS_WARN_IF(!deepestContainerElementToSplit->IsInclusiveDescendantOf( 1700 leftDivOrParagraphElement))) { 1701 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1702 } 1703 const EditorDOMPoint pointToInsertBRElement = [&]() MOZ_NEVER_INLINE_DEBUG { 1704 // If we split the paragraph immediately after a block boundary or a line 1705 // break, we need to put a padding <br> to make an empty line. 1706 const WSScanResult prevVisibleThing = 1707 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 1708 {}, EditorRawDOMPoint::AtEndOf(*deepestContainerElementToSplit), 1709 leftDivOrParagraphElement); 1710 if (prevVisibleThing.ReachedLineBoundary()) { 1711 return EditorDOMPoint::AtEndOf(*deepestContainerElementToSplit); 1712 } 1713 // If we split a descendant element and it's empty, we need to put a padding 1714 // <br> element into it to preserve the style of the element. 1715 if (deepestContainerElementToSplit == leftDivOrParagraphElement) { 1716 return EditorDOMPoint(); 1717 } 1718 const WSScanResult nextVisibleThing = 1719 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 1720 {}, EditorRawDOMPoint(deepestContainerElementToSplit, 0), 1721 leftDivOrParagraphElement); 1722 return nextVisibleThing.ReachedCurrentBlockBoundary() 1723 ? EditorDOMPoint::AtEndOf(*deepestContainerElementToSplit) 1724 : EditorDOMPoint(); 1725 }(); 1726 if (pointToInsertBRElement.IsSet()) { 1727 Result<CreateElementResult, nsresult> insertPaddingBRElementResultOrError = 1728 mHTMLEditor.InsertBRElement( 1729 WithTransaction::Yes, 1730 // XXX We don't want to expose the <br> for IME, but the plaintext 1731 // serializer requires this. See bug 1385905. 1732 BRElementType::Normal, pointToInsertBRElement); 1733 if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { 1734 return insertPaddingBRElementResultOrError.propagateErr(); 1735 } 1736 insertPaddingBRElementResultOrError.unwrap().IgnoreCaretPointSuggestion(); 1737 } 1738 1739 // The right paragraph should not be empty because 1740 // MaybeInsertFollowingBRElementToPreserveRightBlock() should've already put a 1741 // padding <br> before splitting the paragraph. 1742 if (NS_WARN_IF(HTMLEditUtils::IsEmptyNode( 1743 *rightDivOrParagraphElement, 1744 {EmptyCheckOption::TreatSingleBRElementAsVisible, 1745 EmptyCheckOption::TreatListItemAsVisible, 1746 EmptyCheckOption::TreatTableCellAsVisible}))) { 1747 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1748 } 1749 1750 // Let's put caret at start of the first leaf container. 1751 nsIContent* child = HTMLEditUtils::GetFirstLeafContent( 1752 *rightDivOrParagraphElement, {LeafNodeType::LeafNodeOrChildBlock}, 1753 BlockInlineCheck::UseComputedDisplayStyle); 1754 if (MOZ_UNLIKELY(!child)) { 1755 return SplitNodeResult(std::move(splitDivOrPResult), 1756 EditorDOMPoint(rightDivOrParagraphElement, 0u)); 1757 } 1758 1759 return child->IsText() || HTMLEditUtils::IsContainerNode(*child) 1760 ? SplitNodeResult(std::move(splitDivOrPResult), 1761 EditorDOMPoint(child, 0u)) 1762 : SplitNodeResult(std::move(splitDivOrPResult), 1763 EditorDOMPoint(child)); 1764 } 1765 1766 Result<CreateLineBreakResult, nsresult> 1767 HTMLEditor::AutoInsertParagraphHandler::InsertBRElementIfEmptyBlockElement( 1768 Element& aMaybeBlockElement, 1769 InsertBRElementIntoEmptyBlock aInsertBRElementIntoEmptyBlock, 1770 BlockInlineCheck aBlockInlineCheck) { 1771 if (!HTMLEditUtils::IsBlockElement(aMaybeBlockElement, aBlockInlineCheck)) { 1772 return CreateLineBreakResult::NotHandled(); 1773 } 1774 1775 if (!HTMLEditUtils::IsEmptyNode( 1776 aMaybeBlockElement, 1777 {EmptyCheckOption::TreatSingleBRElementAsVisible})) { 1778 return CreateLineBreakResult::NotHandled(); 1779 } 1780 1781 // XXX: Probably, we should use 1782 // InsertPaddingBRElementForEmptyLastLineWithTransaction here, and 1783 // if there are some empty inline container, we should put the <br> 1784 // into the last one. 1785 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 1786 mHTMLEditor.InsertLineBreak( 1787 WithTransaction::Yes, LineBreakType::BRElement, 1788 aInsertBRElementIntoEmptyBlock == InsertBRElementIntoEmptyBlock::Start 1789 ? EditorDOMPoint(&aMaybeBlockElement, 0u) 1790 : EditorDOMPoint::AtEndOf(aMaybeBlockElement)); 1791 NS_WARNING_ASSERTION(insertBRElementResultOrError.isOk(), 1792 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 1793 "LineBreakType::BRElement) failed"); 1794 return insertBRElementResultOrError; 1795 } 1796 1797 // static 1798 Element* HTMLEditor::AutoInsertParagraphHandler:: 1799 GetDeepestFirstChildInlineContainerElement(Element& aBlockElement) { 1800 // XXX Should we ignore invisible children like empty Text, Comment, etc? 1801 Element* result = nullptr; 1802 for (Element* maybeDeepestInlineContainer = 1803 Element::FromNodeOrNull(aBlockElement.GetFirstChild()); 1804 maybeDeepestInlineContainer && 1805 HTMLEditUtils::IsInlineContent( 1806 *maybeDeepestInlineContainer, 1807 BlockInlineCheck::UseComputedDisplayStyle) && 1808 HTMLEditUtils::IsContainerNode(*maybeDeepestInlineContainer); 1809 // FIXME: There may be visible node before first element child, so, here 1810 // is obviously wrong. 1811 maybeDeepestInlineContainer = 1812 maybeDeepestInlineContainer->GetFirstElementChild()) { 1813 result = maybeDeepestInlineContainer; 1814 } 1815 return result; 1816 } 1817 1818 Result<InsertParagraphResult, nsresult> 1819 HTMLEditor::AutoInsertParagraphHandler::HandleInListItemElement( 1820 Element& aListItemElement, const EditorDOMPoint& aPointToSplit) { 1821 MOZ_ASSERT(HTMLEditUtils::IsListItemElement(aListItemElement)); 1822 1823 // If aListItemElement is empty, then we want to outdent its content. 1824 if (&mEditingHost != aListItemElement.GetParentElement() && 1825 HTMLEditUtils::IsEmptyBlockElement( 1826 aListItemElement, 1827 {EmptyCheckOption::TreatNonEditableContentAsInvisible}, 1828 BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 1829 RefPtr<Element> leftListElement = aListItemElement.GetParentElement(); 1830 // If the given list item element is not the last list item element of 1831 // its parent nor not followed by sub list elements, split the parent 1832 // before it. 1833 if (!HTMLEditUtils::IsLastChild(aListItemElement, 1834 {WalkTreeOption::IgnoreNonEditableNode})) { 1835 Result<SplitNodeResult, nsresult> splitListItemParentResult = 1836 mHTMLEditor.SplitNodeWithTransaction( 1837 EditorDOMPoint(&aListItemElement)); 1838 if (MOZ_UNLIKELY(splitListItemParentResult.isErr())) { 1839 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed"); 1840 return splitListItemParentResult.propagateErr(); 1841 } 1842 SplitNodeResult unwrappedSplitListItemParentResult = 1843 splitListItemParentResult.unwrap(); 1844 if (MOZ_UNLIKELY(!unwrappedSplitListItemParentResult.DidSplit())) { 1845 NS_WARNING( 1846 "HTMLEditor::SplitNodeWithTransaction() didn't split the parent of " 1847 "aListItemElement"); 1848 MOZ_ASSERT( 1849 !unwrappedSplitListItemParentResult.HasCaretPointSuggestion()); 1850 return Err(NS_ERROR_FAILURE); 1851 } 1852 unwrappedSplitListItemParentResult.IgnoreCaretPointSuggestion(); 1853 leftListElement = 1854 unwrappedSplitListItemParentResult.GetPreviousContentAs<Element>(); 1855 MOZ_DIAGNOSTIC_ASSERT(leftListElement); 1856 } 1857 1858 auto afterLeftListElement = EditorDOMPoint::After(leftListElement); 1859 if (MOZ_UNLIKELY(!afterLeftListElement.IsInContentNode())) { 1860 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1861 } 1862 1863 // If aListItemElement is in an invalid sub-list element, move it into 1864 // the grand parent list element in order to outdent. 1865 if (HTMLEditUtils::IsListElement( 1866 *afterLeftListElement.ContainerAs<nsIContent>())) { 1867 Result<MoveNodeResult, nsresult> moveListItemElementResult = 1868 mHTMLEditor.MoveNodeWithTransaction(aListItemElement, 1869 afterLeftListElement); 1870 if (MOZ_UNLIKELY(moveListItemElementResult.isErr())) { 1871 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed"); 1872 return moveListItemElementResult.propagateErr(); 1873 } 1874 moveListItemElementResult.inspect().IgnoreCaretPointSuggestion(); 1875 return InsertParagraphResult(aListItemElement, 1876 EditorDOMPoint(&aListItemElement, 0u)); 1877 } 1878 1879 // Otherwise, replace the empty aListItemElement with a new paragraph. 1880 nsresult rv = mHTMLEditor.DeleteNodeWithTransaction(aListItemElement); 1881 if (NS_FAILED(rv)) { 1882 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 1883 return Err(rv); 1884 } 1885 nsStaticAtom& newParagraphTagName = 1886 &mDefaultParagraphSeparatorTagName == nsGkAtoms::br 1887 ? *nsGkAtoms::p 1888 : mDefaultParagraphSeparatorTagName; 1889 // MOZ_KnownLive(newParagraphTagName) because it's available until shutdown. 1890 Result<CreateElementResult, nsresult> createNewParagraphElementResult = 1891 mHTMLEditor.CreateAndInsertElement( 1892 WithTransaction::Yes, MOZ_KnownLive(newParagraphTagName), 1893 afterLeftListElement, HTMLEditor::InsertNewBRElement); 1894 if (MOZ_UNLIKELY(createNewParagraphElementResult.isErr())) { 1895 NS_WARNING( 1896 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed"); 1897 return createNewParagraphElementResult.propagateErr(); 1898 } 1899 createNewParagraphElementResult.inspect().IgnoreCaretPointSuggestion(); 1900 MOZ_ASSERT(createNewParagraphElementResult.inspect().GetNewNode()); 1901 EditorDOMPoint pointToPutCaret( 1902 createNewParagraphElementResult.inspect().GetNewNode(), 0u); 1903 return InsertParagraphResult( 1904 *createNewParagraphElementResult.inspect().GetNewNode(), 1905 std::move(pointToPutCaret)); 1906 } 1907 1908 const EditorDOMPoint pointToSplit = 1909 GetBetterPointToSplitParagraph(aListItemElement, aPointToSplit); 1910 MOZ_ASSERT(pointToSplit.IsInContentNodeAndValidInComposedDoc()); 1911 1912 // If insertParagraph at end of <dt> or <dd>, we should put opposite type list 1913 // item without copying the style of end of aListItemElement. 1914 // FIXME: Chrome does not do this. So, we should stop doing this at least on 1915 // Firefox later. 1916 if (aListItemElement.IsAnyOfHTMLElements(nsGkAtoms::dt, nsGkAtoms::dd) && 1917 SplitPointIsEndOfSplittingBlock(aListItemElement, pointToSplit, 1918 IgnoreBlockBoundaries::Yes) && 1919 // However, don't do that if we're handling it in empty list item. 1920 !SplitPointIsStartOfSplittingBlock(aListItemElement, pointToSplit, 1921 IgnoreBlockBoundaries::Yes)) { 1922 nsStaticAtom& oppositeTypeListItemTag = 1923 aListItemElement.IsHTMLElement(nsGkAtoms::dt) ? *nsGkAtoms::dd 1924 : *nsGkAtoms::dt; 1925 // MOZ_KnownLive(oppositeTypeListItemTag) because it's available 1926 // until shutdown. 1927 Result<CreateElementResult, nsresult> 1928 insertOppositeTypeListItemResultOrError = 1929 mHTMLEditor.CreateAndInsertElement( 1930 WithTransaction::Yes, MOZ_KnownLive(oppositeTypeListItemTag), 1931 EditorDOMPoint::After(aListItemElement), 1932 HTMLEditor::InsertNewBRElement); 1933 if (MOZ_UNLIKELY(insertOppositeTypeListItemResultOrError.isErr())) { 1934 NS_WARNING( 1935 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed"); 1936 return insertOppositeTypeListItemResultOrError.propagateErr(); 1937 } 1938 CreateElementResult insertOppositeTypeListItemResult = 1939 insertOppositeTypeListItemResultOrError.unwrap(); 1940 insertOppositeTypeListItemResult.IgnoreCaretPointSuggestion(); 1941 RefPtr<Element> oppositeTypeListItemElement = 1942 insertOppositeTypeListItemResult.UnwrapNewNode(); 1943 EditorDOMPoint startOfOppositeTypeListItem(oppositeTypeListItemElement, 0u); 1944 MOZ_ASSERT(oppositeTypeListItemElement); 1945 return InsertParagraphResult(std::move(oppositeTypeListItemElement), 1946 std::move(startOfOppositeTypeListItem)); 1947 } 1948 1949 // If aListItemElement has some content or aListItemElement is empty but it's 1950 // a child of editing host, we want a new list item at the same list level. 1951 // First, sort out white-spaces. 1952 Result<SplitNodeResult, nsresult> splitListItemResultOrError = 1953 SplitParagraphWithTransaction(aListItemElement, pointToSplit); 1954 if (MOZ_UNLIKELY(splitListItemResultOrError.isErr())) { 1955 NS_WARNING( 1956 "AutoInsertParagraphHandler::SplitParagraphWithTransaction() failed"); 1957 return splitListItemResultOrError.propagateErr(); 1958 } 1959 SplitNodeResult splitListItemElement = splitListItemResultOrError.unwrap(); 1960 EditorDOMPoint pointToPutCaret = splitListItemElement.UnwrapCaretPoint(); 1961 if (MOZ_UNLIKELY(!aListItemElement.GetParent())) { 1962 NS_WARNING("Somebody disconnected the target listitem from the parent"); 1963 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1964 } 1965 1966 // If aListItemElement is not replaced, we should not do anything anymore. 1967 if (MOZ_UNLIKELY(!splitListItemElement.DidSplit()) || 1968 NS_WARN_IF(!splitListItemElement.GetNewContentAs<Element>()) || 1969 NS_WARN_IF(!splitListItemElement.GetOriginalContentAs<Element>())) { 1970 NS_WARNING( 1971 "AutoInsertParagraphHandler::SplitParagraphWithTransaction() didn't " 1972 "split the listitem"); 1973 return Err(NS_ERROR_FAILURE); 1974 } 1975 auto* const rightListItemElement = 1976 splitListItemElement.GetNextContentAs<Element>(); 1977 return InsertParagraphResult(*rightListItemElement, 1978 std::move(pointToPutCaret)); 1979 } 1980 1981 // static 1982 bool HTMLEditor::AutoInsertParagraphHandler::SplitPointIsStartOfSplittingBlock( 1983 const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit, 1984 IgnoreBlockBoundaries aIgnoreBlockBoundaries) { 1985 EditorRawDOMPoint pointToSplit = aPointToSplit.To<EditorRawDOMPoint>(); 1986 while (true) { 1987 const WSScanResult prevVisibleThing = 1988 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary({}, pointToSplit); 1989 if (!prevVisibleThing.ReachedCurrentBlockBoundary()) { 1990 return false; 1991 } 1992 if (prevVisibleThing.ElementPtr() == &aBlockElementToSplit) { 1993 return true; 1994 } 1995 if (!static_cast<bool>(aIgnoreBlockBoundaries)) { 1996 return false; 1997 } 1998 pointToSplit = pointToSplit.ParentPoint(); 1999 } 2000 } 2001 2002 // static 2003 bool HTMLEditor::AutoInsertParagraphHandler::SplitPointIsEndOfSplittingBlock( 2004 const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit, 2005 IgnoreBlockBoundaries aIgnoreBlockBoundaries) { 2006 bool maybeFollowedByInvisibleBRElement = true; 2007 EditorRawDOMPoint pointToSplit = aPointToSplit.To<EditorRawDOMPoint>(); 2008 while (true) { 2009 WSScanResult nextVisibleThing = 2010 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 2011 {}, pointToSplit, &aBlockElementToSplit); 2012 if (maybeFollowedByInvisibleBRElement && 2013 (nextVisibleThing.ReachedBRElement() || 2014 nextVisibleThing.ReachedPreformattedLineBreak())) { 2015 nextVisibleThing = 2016 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 2017 {}, 2018 nextVisibleThing.PointAfterReachedContent<EditorRawDOMPoint>(), 2019 &aBlockElementToSplit); 2020 } 2021 if (!nextVisibleThing.ReachedCurrentBlockBoundary()) { 2022 return false; 2023 } 2024 if (nextVisibleThing.ElementPtr() == &aBlockElementToSplit) { 2025 return true; 2026 } 2027 if (!static_cast<bool>(aIgnoreBlockBoundaries)) { 2028 return false; 2029 } 2030 pointToSplit = pointToSplit.AfterContainer(); 2031 // <br> element after other block boundary creates an empty line so that 2032 // it's always visible. 2033 maybeFollowedByInvisibleBRElement = false; 2034 } 2035 } 2036 2037 } // namespace mozilla